/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */ /* vi: set ts=4 sw=4 expandtab: (add to ~/.vimrc: set modeline modelines=5) */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "avmplus.h" #include "BuiltinNatives.h" #ifdef VMCFG_NANOJIT #include "CodegenLIR.h" #endif //GCC only allows intrinsics if sse2 is enabled #if (defined(_MSC_VER) || (defined(__GNUC__) && defined(__SSE2__))) && (defined(AVMPLUS_IA32) || defined(AVMPLUS_AMD64)) #include #endif #include "api-versions.cpp" #include "avmplus-tracers.hh" /* * TUNABLE PARAMETER: OSR_THRESHOLD_DEFAULT * * The number of method invocations or loop iterations to trigger compilation when OSR is enabled. * * A value of zero is interpreted specially, indicating that static initializers should always * be interpreted and other methods compiled eagerly, identical to the policy when AVMFEATURE_OSR * is not configured. The default value may be overridden by runtime configuration, e.g., * the "-osr" option to avmshell. Additionally, the globally-configured threshold may be overridden * on a per-method basis via the ExecPolicy attribute, allowing content to opt-in or opt-out independently * of the default global OSR policy. For diagnostic purposes, we may wish to definitively suppress OSR * regardless of content opt-in. By special dispensation, the switch "-osr=0" disables OSR globally and * cannot be overridden. Programmatically, setting config.osr_threshold to zero does not disable opt-in, * rather, it is also necessary to set config.osr_enabled to false. * * The present threshold value has been set based on preliminary startup-time studies on AIR Android. */ #ifdef VMCFG_OSR // All methods subject to OSR (where supported) using a default threshold of 17. // Per-method ExecPolicy attributes may alter the threshold, or disable OSR by using threshold 0. static const bool OSR_ENABLED_DEFAULT = true; static const int OSR_THRESHOLD_DEFAULT = 17; // tuned #else // All methods use threshold 0 by default, which selects the legacy non-OSR compilation policy. // Per-method ExecPolicy attributes may set a non-zero threshold, however, allowing content to // opt-in to OSR on a per-method basis as desired. static const bool OSR_ENABLED_DEFAULT = true; static const int OSR_THRESHOLD_DEFAULT = 0; // legacy policy #endif #define AVMPLUS_STRING_DELETED ((Stringp)(1)) #ifndef MARK_SECURITY_CHANGE // http://frpbugapp.macromedia.com/bugapp/detail.asp?ID=999999 /* This exists solely to ensure that tamarin-security differs from tamarin-redux/tamarin-central, and that the canonical form for the MARK_SECURITY_CHANGE macro remains in place. Please do not ever remove this ifdef or this comment. (srj) */ #endif namespace avmplus { using namespace MMgc; // Regex compilation cache. RegexCache::RegexCache() : m_timestamp(0) , m_wasted(0) , m_useful(0) , m_disabled(false) { } RegexCacheEntry& RegexCache::findCachedRegex(bool& found, String* source, String* options) { if (m_disabled) { found = false; return m_entries[0]; } size_t smallest = 0; for ( size_t i=0 ; i < ARRAY_SIZE(m_entries) ; i++ ) { RegexCacheEntry& it = m_entries[i]; if (it.match(source, options)) { it.timestamp = ++m_timestamp; it.hits++; found = true; return it; } if (it.timestamp < m_entries[smallest].timestamp) smallest = i; } // A miss. Evict an entry and account for its utility. RegexCacheEntry& it = m_entries[smallest]; // If a cache entry was used just once then it was wasted work; // if wasted work dominates then disable the cache. if (it.hits == 1) m_wasted++; else if (it.hits > 1) m_useful+=it.hits-1; if (m_timestamp > warmupTicks && m_wasted > m_useful*wastedWorkMultiplier) { //printf("DISABLED!\n"); m_disabled = true; } // New entry it.clear(); it.timestamp = ++m_timestamp; it.hits++; found = false; return it; } bool RegexCache::testCachedRegex(String* source, String* options) { if (m_disabled) return false; for ( size_t i=0 ; i < ARRAY_SIZE(m_entries) ; i++ ) if (m_entries[i].match(source, options)) return true; return false; } void RegexCache::clear() { for ( size_t i=0 ; i < ARRAY_SIZE(m_entries) ; i++ ) m_entries[i].clear(); m_timestamp = 0; m_wasted = 0; m_useful = 0; m_disabled = false; } #ifdef DEBUG // This is called from the Atom code in vmbase bool testIsObject(avmplus::Atom atom) { return AvmCore::isObject(atom); } #endif void AvmCore::setCacheSizes(const CacheSizes& cs) { #ifdef AVMPLUS_VERBOSE if (isVerbose(VB_traits)) { console << "setCacheSize: bindings " << hexAddr(cs.bindings) << " metadata " << hexAddr(cs.metadata) << '\n'; } #endif //AvmLog("setCacheSize: bindings %d metadata %d\n",cs.bindings,cs.metadata); m_tbCache->resize(cs.bindings); m_tmCache->resize(cs.metadata); m_msCache->resize(cs.methods); } void AvmCore::handleAbcUnloaded() { m_tbCache->flush(); m_tmCache->flush(); m_msCache->flush(); } const uint32_t AvmCore::verbose_default = 0; // all off const bool AvmCore::methodNames_default = true; const bool AvmCore::oldVectorMethodNames_default = true; const bool AvmCore::verifyall_default = false; const bool AvmCore::verifyonly_default = false; const bool AvmCore::verifyquiet_default = false; const Runmode AvmCore::runmode_default = RM_mixed; const bool AvmCore::osr_enabled_default = OSR_ENABLED_DEFAULT; const uint32_t AvmCore::osr_threshold_default = OSR_THRESHOLD_DEFAULT; const uint32_t AvmCore::jitprof_level_default = 0; // no logging. const bool AvmCore::interrupts_default = false; const bool AvmCore::jitordie_default = false; #ifdef AVMPLUS_VERBOSE #ifdef VMCFG_NANOJIT const uint32_t AvmCore::DEFAULT_VERBOSE_ON = (uint32_t)~0 & ~(nanojit::LC_FragProfile); // LC_FragProfile changes generated code!?! #else const uint32_t AvmCore::DEFAULT_VERBOSE_ON = (uint32_t)~0; #endif static bool substrMatches(const char* pattern, const char* p, const char* e) { ptrdiff_t const patlen = VMPI_strlen(pattern); return (e-p) >= patlen && !VMPI_strncmp(p, pattern, patlen); } /*static*/ uint32_t AvmCore::parseVerboseFlags(const char* p, char*& badFlag) { uint32_t r = 0; badFlag = 0; for (;;) { const char* e = p; // stop on null or end-of-line... use >=32 to catch those plus other unlikely/uninteresting cases while (*e >= 32 && *e != ',') e++; if (substrMatches("parse", p, e)) r |= VB_parse; else if (substrMatches("verify", p, e)) r |= VB_verify; else if (substrMatches("interp", p, e)) r |= VB_interp; else if (substrMatches("traits", p, e)) r |= VB_traits; else if (substrMatches("builtins", p, e)) r |= VB_builtins; else if (substrMatches("execpolicy", p, e)) r |= VB_execpolicy; else if (substrMatches("memstats", p, e)) MMgc::GCHeap::GetGCHeap()->Config().gcstats = true; else if (substrMatches("sweep", p, e)) MMgc::GCHeap::GetGCHeap()->Config().autoGCStats = true; else if (substrMatches("occupancy", p, e)) MMgc::GCHeap::GetGCHeap()->Config().verbose = true; #ifdef VMCFG_NANOJIT else if (substrMatches("jit", p, e)) r |= VB_jit | LC_AfterDCE | LC_Native; else if (substrMatches("lircfg-bb", p, e)) r |= VB_jit | VB_lircfg | VB_lircfg_bb; else if (substrMatches("lircfg-ins", p, e)) r |= VB_jit | VB_lircfg | VB_lircfg_ins; else if (substrMatches("lircfg", p, e)) r |= VB_jit | VB_lircfg; // unfortunately we have to turn on VB_jit to get names @todo fix that! else if (substrMatches("opt", p, e)) r |= VB_jit | LC_Liveness | LC_ReadLIR | LC_AfterSF; else if (substrMatches("lir", p, e)) r |= VB_jit | LC_ReadLIR; else if (substrMatches("regs", p, e)) r |= VB_jit | LC_Activation | LC_RegAlloc; else if (substrMatches("raw", p, e)) r |= VB_raw; else if (substrMatches("bytes", p, e)) r |= VB_jit | LC_Native | LC_Bytes; #endif /* VMCFG_NANOJIT */ else badFlag = (char*)p; if (*e < 32) break; p = e+1; } return r; } const char* AvmCore::identifyDomain(Domain* domain) { return domain == builtinDomain ? "builtin" : NULL; } static void parseVerboseRestrictions(const char* s, MethodRecognizerSet* list, PrintWriter& console) { while(*s) { s = (*s == '\"' || *s == ',') ? s+1 : s; if (!*s) break; MethodRecognizer* r = MethodRecognizer::parse(&s, ','); list->add( r ); if (r != NULL) console << "verbose only for " << r << "\n"; else console << "warning: not able to parse verbose only rule :" << s << "\n"; if (*s) s++; } } /** * If verbosity is restricted per-method, then scan each MethodRecognizer seeing * seeing if we get a match , otherwise just check the bit. */ bool AvmCore::isVerbose(uint32_t b, MethodInfo* info) { bool v = (b & config.verbose_vb) ? true : false; if (v && info && config.verboseOnlyString) { MethodRecognizerSet* list = &_verboseRestrictedTo; if (list->isEmpty()) parseVerboseRestrictions(config.verboseOnlyString, list, console); // no good way to init it prior to this call since verboseOnlyString update occurs post-ctor // disabled by default v = false; for(uint32_t i=0,n=list->length(); !v && iget(i); v = (r) ? r->matches(info) : false; } } return v; } #endif // a single string with characters 0x00...0x7f (inclusive) static const char* const k_cachedChars = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F" "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2A\x2B\x2C\x2D\x2E\x2F" "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x3E\x3F" "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F" "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5A\x5B\x5C\x5D\x5E\x5F" "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6A\x6B\x6C\x6D\x6E\x6F" "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7A\x7B\x7C\x7D\x7E\x7F"; AvmCore::AvmCore(GC* g, ApiVersionSeries apiVersionSeries) : GCRoot(g) , enterEventLoop(false) , inEventLoop(false) #ifdef DEBUGGER , _sampler(NULL) #endif , currentMethodFrame(NULL) , livePools(NULL) , m_activeApiVersionSeries(apiVersionSeries) , m_activeApiVersionSeriesMask(1 << apiVersionSeries) #ifdef VMCFG_NANOJIT , m_flushBindingCachesNextSweep(false) #endif , console(NULL) , gc(g) #ifdef VMCFG_STACK_METRICS , minStack(~uintptr_t(0)) , maxStack(0) #endif #ifdef _DEBUG , codeContextThread(VMPI_currentThread()) #endif , bugzilla444630(false) #ifdef DEBUGGER , langID(-1) , passAllExceptionsToDebugger(false) #endif , exec(NULL) , gcInterface(g) #ifdef DEBUGGER , _debugger(NULL) , _profiler(NULL) #endif , m_tbCache(QCache::create(CacheSizes::DEFAULT_BINDINGS, g)) , m_tmCache(QCache::create(CacheSizes::DEFAULT_METADATA, g)) , m_msCache(QCache::create(CacheSizes::DEFAULT_METHODS, g)) , m_domainMgr(NULL) , m_domainEnvs(gc, 1) #ifdef AVMPLUS_VERBOSE , _verboseRestrictedTo(gc,0) #endif { // sanity check for all our types MMGC_STATIC_ASSERT(sizeof(int8_t) == 1); MMGC_STATIC_ASSERT(sizeof(uint8_t) == 1); MMGC_STATIC_ASSERT(sizeof(int16_t) == 2); MMGC_STATIC_ASSERT(sizeof(uint16_t) == 2); MMGC_STATIC_ASSERT(sizeof(int32_t) == 4); MMGC_STATIC_ASSERT(sizeof(uint32_t) == 4); MMGC_STATIC_ASSERT(sizeof(int64_t) == 8); MMGC_STATIC_ASSERT(sizeof(uint64_t) == 8); MMGC_STATIC_ASSERT(sizeof(intptr_t) == sizeof(void *)); MMGC_STATIC_ASSERT(sizeof(uintptr_t) == sizeof(void *)); #ifdef AVMPLUS_64BIT MMGC_STATIC_ASSERT(sizeof(intptr_t) == 8); MMGC_STATIC_ASSERT(sizeof(uintptr_t) == 8); #else MMGC_STATIC_ASSERT(sizeof(intptr_t) == 4); MMGC_STATIC_ASSERT(sizeof(uintptr_t) == 4); #endif #ifdef VMCFG_TELEMETRY_SAMPLER // intialize the sampler, off by default samplerEnabled = false; sampler = NULL; sampleTicks = 0; #endif // set default mode flags config.verbose_vb = verbose_default; config.verboseOnlyString = NULL; // policy rule overrides 0=none config.compilePolicyRules = NULL; // default to recording method names, if possible. // (subclass might change this in its ctor if it wants to conserve memory.) config.methodNames = methodNames_default; config.oldVectorMethodNames = oldVectorMethodNames_default; config.verifyall = verifyall_default; config.verifyonly = verifyonly_default; // jit flag forces use of jit-compiler instead of interpreter config.runmode = runmode_default; config.osr_enabled = osr_enabled_default; config.osr_threshold = osr_threshold_default; config.jitprof_level = jitprof_level_default; config.jitordie = jitordie_default; config.interrupts = interrupts_default; gcInterface.SetCore(this); xmlEntities = NULL; exceptionFrame = NULL; exceptionAddr = NULL; builtinPool = NULL; builtinDomain = NULL; builtinBugCompatibility = NULL; m_versionedURIs = HeapHashtable::create(gc); #ifdef _DEBUG m_unversionedURIs = HeapHashtable::create(gc); #endif m_domainMgr = new(gc) DomainMgr(this); GetGC()->SetGCContextVariable(MMgc::GC::GCV_AVMCORE, this); minstack = 0; #ifdef DEBUGGER callStack = NULL; #endif interrupted = NotInterrupted; pending_interrupt = NotInterrupted; strings = NULL; numStrings = 0; namespaces = NULL; numNamespaces = 0; stringCount = 0; deletedCount = 0; nsCount = 0; currentMethodInfoCount = 0; numStrings = 1024; // power of 2 numNamespaces = 1024; // power of 2 strings = mmfx_new_array(GCRoot::GCMember, numStrings); namespaces = mmfx_new_array(GCRoot::GCMember, numNamespaces); console.setCore(this); kconstructor = internConstantStringLatin1("constructor"); kundefined = internConstantStringLatin1("undefined"); knull = internConstantStringLatin1("null"); ktrue = internConstantStringLatin1("true"); kfalse = internConstantStringLatin1("false"); ktoString = internConstantStringLatin1("toString"); ktoLocaleString = internConstantStringLatin1("toLocaleString"); kvalueOf = internConstantStringLatin1("valueOf"); kvalue = internConstantStringLatin1("value"); klength = internConstantStringLatin1("length"); kobject = internConstantStringLatin1("object"); kboolean = internConstantStringLatin1("boolean"); kvoid = internConstantStringLatin1("void"); knumber = internConstantStringLatin1("number"); #ifdef VMCFG_FLOAT kfloat = internConstantStringLatin1("float"); kfloat4 = internConstantStringLatin1("float4"); knumeric = internConstantStringLatin1("numeric[INTERNAL]"); #endif kstring = internConstantStringLatin1("string"); kxml = internConstantStringLatin1("xml"); kfunction = internConstantStringLatin1("function"); kglobal = internConstantStringLatin1("global"); kcallee = internConstantStringLatin1("callee"); kuri = internConstantStringLatin1("uri"); kprefix = internConstantStringLatin1("prefix"); kNaN = doubleToAtom(MathUtils::kNaN); #ifdef VMCFG_FLOAT kFltNaN = floatToAtom( (float) MathUtils::kNaN ); kFltOne = floatToAtom( 1.0f ); kFltMinusOne = floatToAtom( -1.0f ); kFlt4NaN = float4Atom( AtomConstants::undefinedAtom ); #endif kNeedsDxns = internConstantStringLatin1("NeedsDxns"); kVersion = internConstantStringLatin1("Version"); kanonymousFunc = newConstantStringLatin1(""); klittleEndian = internConstantStringLatin1("littleEndian"); kbigEndian = internConstantStringLatin1("bigEndian"); kparent = internConstantStringLatin1("parent"); kattribute = internConstantStringLatin1("attribute"); kcomment = internConstantStringLatin1("comment"); kprocessing_instruction = internConstantStringLatin1("processing-instruction"); kelement = internConstantStringLatin1("element"); ktext = internConstantStringLatin1("text"); kattributeAdded = internConstantStringLatin1("attributeAdded"); kattributeRemoved = internConstantStringLatin1("attributeRemoved"); kattributeChanged = internConstantStringLatin1("attributeChanged"); knodeAdded = internConstantStringLatin1("nodeAdded"); knodeRemoved = internConstantStringLatin1("nodeRemoved"); knodeChanged = internConstantStringLatin1("nodeChanged"); knamespaceAdded = internConstantStringLatin1("namespaceAdded"); knamespaceRemoved = internConstantStringLatin1("namespaceRemoved"); knamespaceSet = internConstantStringLatin1("namespaceSet"); knameSet = internConstantStringLatin1("nameSet"); ktextSet = internConstantStringLatin1("textSet"); klocalName = internConstantStringLatin1("localName"); kindex = internConstantStringLatin1("index"); kinput = internConstantStringLatin1("input"); kemptyCtor = internConstantStringLatin1("emptyCtor"); kMath = internConstantStringLatin1("Math"); kNumber = internConstantStringLatin1("Number"); kAsterisk = internConstantStringLatin1("*"); kColon = internConstantStringLatin1(":"); kUnderscore = internConstantStringLatin1("_"); kXML1998NS = internConstantStringLatin1("http://www.w3.org/XML/1998/namespace"); kzero = internConstantStringLatin1("0"); kClassS = internConstantStringLatin1("Class$"); #ifdef VMCFG_FLOAT kVectorFloat = internConstantStringLatin1("Vector."); kVectorFloat4 = internConstantStringLatin1("Vector."); #endif kVectorNumber = internConstantStringLatin1("Vector."); kVectorint = internConstantStringLatin1("Vector."); kVectoruint = internConstantStringLatin1("Vector."); kVectorAny = internConstantStringLatin1("Vector.<*>"); #ifdef VMCFG_FLOAT kx = internConstantStringLatin1("x"); ky = internConstantStringLatin1("y"); kz = internConstantStringLatin1("z"); kw = internConstantStringLatin1("w"); #endif kExecPolicy = internConstantStringLatin1("ExecPolicy"); kOSR = internConstantStringLatin1("OSR"); for (int i = 0; i < 128; i++) { AvmAssert(k_cachedChars[i] == i); // call String::createLatin1() with an explicit length of 1; required // when singleChar==0, because in that case we need a string // which is a single character with value 0 cachedChars[i] = internString(String::createLatin1(this, &k_cachedChars[i], 1)); } workerStates[0] = internConstantStringLatin1("none"); workerStates[1] = internConstantStringLatin1("new"); workerStates[2] = internConstantStringLatin1("canstart"); workerStates[3] = internConstantStringLatin1("starting"); workerStates[4] = internConstantStringLatin1("running"); workerStates[5] = internConstantStringLatin1("finishing"); workerStates[6] = internConstantStringLatin1("terminated"); workerStates[7] = internConstantStringLatin1("failed"); workerStates[8] = internConstantStringLatin1("aborted"); workerStates[9] = internConstantStringLatin1("exception"); booleanStrings[0] = kfalse; booleanStrings[1] = ktrue; // init kEmptyString last, so that StringObject can use it as a sentinel for // determining if all the cached strings are valid. kEmptyString = internConstantStringLatin1(""); // create public namespace -- note that this publicNamespace = internNamespace(newNamespace(kEmptyString, Namespace::NS_Public, kApiVersion_VM_ALLVERSIONS)); _emptySupertypeList = Traits::allocSupertypeList(gc, 0); // the public namespace (empty uri) is always versioned. this->m_versionedURIs->get_ht()->add(kEmptyString->atom(), trueAtom); // cache public namespaces. (This creates namespaces that aren't in the active // series, but that's ok.) this->publicNamespaces = NamespaceSet::_create(GetGC(), kApiVersion_count); for (int i = 0; i < kApiVersion_count; ++i) { Namespacep ns = this->internNamespace(this->newNamespace(kEmptyString, Namespace::NS_Public, ApiVersion(i))); publicNamespaces->_initNsAt(i, ns); } } AvmCore* AvmCore::getActiveCore() { return GC::GetActiveGC()->core(); } AvmCore::~AvmCore() { #ifdef DEBUGGER if (gc) { gc->SetAttachedSampler(NULL); } delete _sampler; _sampler = NULL; #endif m_tbCache->flush(); m_tmCache->flush(); m_msCache->flush(); m_tbCache = NULL; m_tmCache = NULL; m_msCache = NULL; delete exec; delete m_domainMgr; // Free the numbers and strings tables mmfx_delete_array(strings); if (gc) { gc->SetGCContextVariable(GC::GCV_AVMCORE, NULL); } strings = NULL; // important for potential callback invocation of presweep numStrings = 0; mmfx_delete_array(namespaces); namespaces = NULL; numNamespaces = 0; #ifdef DEBUGGER delete _profiler; _profiler = NULL; #endif LivePoolNode* node = livePools; while (node) { LivePoolNode* next = node->next; delete node; node = next; } livePools = NULL; #ifdef DEBUGGER while (callStack != NULL) { // note that reset() sets callStack to callStack->next, // so this loop will terminate if (callStack->reset() == false) { break; } } #endif #ifdef AVMPLUS_VERBOSE while(!_verboseRestrictedTo.isEmpty()) mmfx_delete( _verboseRestrictedTo.removeLast() ); #endif } void AvmCore::initBuiltinPool( #ifdef DEBUGGER int tracelevel #endif ) { // Create the singleton ExecMgr instance. exec = new (gc) BaseExecMgr(this); #ifdef DEBUGGER _debugger = createDebugger(tracelevel); _profiler = createProfiler(); #endif builtinDomain = Domain::newDomain(this, NULL); // builtins always use BugCompatibility::kLatest builtinBugCompatibility = createBugCompatibility(BugCompatibility::kLatest); #ifdef VMCFG_AOT NativeInitializer ninit(this, avmplus::NativeID::builtin_versioned_uris, &builtin_aotInfo, avmplus::NativeID::builtin_abc_method_count, avmplus::NativeID::builtin_abc_class_count); ninit.fillInClasses(avmplus::NativeID::builtin_classEntries); ninit.fillInMethods(avmplus::NativeID::builtin_methodEntries); builtinPool = ninit.parseBuiltinABC(builtinDomain); #else builtinPool = AVM_INIT_BUILTIN_ABC(builtin, this); #endif AvmAssert(builtinPool->isBuiltin); // Set the the non-interruptable bit on all builtin functions. for(int i=0, size=builtinPool->methodCount(); igetMethodInfo(i)->makeNonInterruptible(); for(int i=0, size=builtinPool->classCount(); igetClassTraits(i)->init->makeNonInterruptible(); for(int i=0, size=builtinPool->scriptCount(); igetScriptTraits(i)->init->makeNonInterruptible(); #if defined(DEBUGGER) && !defined(VMCFG_AOT) // sampling can begin now, requires builtinPool if (_debugger) { _sampler = createSampler(); AvmAssert(_sampler != NULL); _sampler->initSampling(); } #endif } #ifdef DEBUGGER Debugger* AvmCore::createDebugger(int /*tracelevel*/) { return NULL; } Profiler* AvmCore::createProfiler() { return NULL; } Sampler* AvmCore::createSampler() { return (new Sampler(this)); } #endif /*virtual*/ void AvmCore::setStackBase() { // nothing } #ifdef _DEBUG /*virtual*/ void AvmCore::tryHook() { // nothing } #endif /*virtual*/ void AvmCore::catchHook(CatchAction action) { (void)action; // nothing } /*static*/ void AvmCore::readOperands(const uint8_t* &pc, unsigned int& imm32, int& imm24, unsigned int& imm32b, int& imm8 ) { AbcOpcode opcode = (AbcOpcode)*pc++; int op_count = opcodeInfo[opcode].operandCount; imm8 = pc[0]; if( opcode == OP_pushbyte || opcode == OP_debug ) { // these two operands have a byte as their first operand, which is not encoded // with the variable length encoding scheme for bigger numbers, because it will // never be larger than a byte. --op_count; pc++; } if( op_count > 0 ) { if( opcode >= OP_ifnlt && opcode <= OP_lookupswitch ) { imm24 = AvmCore::readS24(pc); pc += 3; } else { imm32 = AvmCore::readU32(pc); } if( opcode == OP_debug ) { --op_count; //OP_debug has a third operand of a byte pc++; } if( op_count > 1 ) { imm32b = AvmCore::readU32(pc); } } } static void initScriptActivationTraits(AvmCore* core, Toplevel* toplevel, MethodInfo* method) { #ifdef VMCFG_AOT PoolObject* pool = method->pool(); const AOTInfo* aotInfo = pool->aotInfo; if (method->needActivation()) { Traits* activationTraits = method->activationTraits(); AvmAssert(activationTraits != NULL); AvmAssert(method->method_id() < (int)aotInfo->nActivationTraits); aotInfo->activationTraits[method->method_id()] = activationTraits; if (aotInfo->activationInfo[method->method_id()].initHandler != NULL) { // NativeMethodInfo.handler is a union of // pointer to function and pointer to member function. // Zero the structure so that the entire thing is // initialized. // See bugzilla#647660 NativeMethodInfo compiledMethodInfo = {{0}, 0, 0}; compiledMethodInfo.thunker = aotThunker; AvmThunkNativeHandler nhandler; nhandler.function = aotInfo->activationInfo[method->method_id()].initHandler; activationTraits->init = MethodInfo::create(core->GetGC(), MethodInfo::kInitMethodStub, activationTraits, &compiledMethodInfo, nhandler, aotInfo->activationInfo[method->method_id()].initMethodId); } method->activationTraits()->resolveSignatures(toplevel); } method->declaringTraits()->initActivationTraits(); #else (void)core; (void)toplevel; (void)method; #endif } ScriptEnv* AvmCore::initOneScript(Toplevel* toplevel, AbcEnv* abcEnv, Traits* scriptTraits) { VTable* scriptVTable = this->newVTable(scriptTraits, toplevel->objectClass->ivtable(), toplevel); const ScopeTypeChain* scriptSTC = scriptTraits->declaringScope(); if (!scriptSTC) { scriptSTC = ScopeTypeChain::createEmpty(this->GetGC(), scriptTraits); scriptTraits->setDeclaringScopes(scriptSTC); } ScopeChain* scriptScope = ScriptEnv::createScriptScope(scriptSTC, scriptVTable, abcEnv); ScriptEnv* scriptEnv = ScriptEnv::create(this->GetGC(), scriptTraits->init, scriptScope); scriptVTable->init = scriptEnv; initScriptActivationTraits(this, toplevel, scriptTraits->init); return scriptEnv; } ScriptEnv* AvmCore::initAllScripts(Toplevel* toplevel, AbcEnv* abcEnv) { PoolObject* pool = abcEnv->pool(); // The list of ScriptEnvs that correspond to the MethodInfos the DomainMgr // already knows about for the Pool/Domain that correspond to the AbcEnv/DomainEnv. // Note that the order of the list doesn't matter (DomainMgr will use a hashtable // to do the relevant association). GCList envs(GetGC(), pool->scriptCount()); // some code relies on the final script being initialized first, so we // must continue that behavior uint32_t const last = pool->scriptCount()-1; ScriptEnv* main = initOneScript(toplevel, abcEnv, pool->getScriptTraits(last)); envs.add(main); // skip the final one, it's already been done for (uint32_t i=0; i < last; i++) { ScriptEnv* scriptEnv = initOneScript(toplevel, abcEnv, pool->getScriptTraits(i)); envs.add(scriptEnv); } this->domainMgr()->addNamedScriptEnvs(abcEnv, envs); exec->notifyAbcPrepared(toplevel, abcEnv); return main; } Atom AvmCore::callScriptEnvEntryPoint(ScriptEnv* main) { main->initGlobal(); Atom result = 0; // init to zero to make GCC happy #ifndef DEBUGGER result = main->coerceEnter(main->global->atom()); #else TRY(this, kCatchAction_Rethrow) { result = main->coerceEnter(main->global->atom()); } CATCH(Exception* exception) { // Re-throw exception Isolate* isolate = getIsolate(); if (isolate->getAggregate()->queryState(isolate) == Isolate::FINISHING) { // Don't rethrow, we're finishing and probably not setup to handle the exception. } else { this->throwException(exception); } } END_CATCH END_TRY #endif return result; } Toplevel* AvmCore::initToplevel(ICodeContextCreator& codeContextCreator) { DomainEnv* builtinDomainEnv = DomainEnv::newDomainEnv(this, builtinDomain, NULL); // Use codeContextCreator so that subclasses can construct a variant if appropriate; // if they are, we can't use a "naked" CodeContext anywhere, as debugger code might walk // thru it and all CodeContexts that it finds are the subclass. CodeContext* builtinCodeContext = codeContextCreator.create(builtinDomainEnv, builtinBugCompatibility); AvmAssert(builtinPool != NULL); AvmAssert(builtinPool->scriptCount() != 0); AbcEnv* abcEnv = AbcEnv::create(GetGC(), builtinPool, builtinCodeContext); Toplevel* toplevel = createToplevel(abcEnv); // save toplevel since it was initially null builtinDomainEnv->setToplevel(toplevel); AvmCore* core = this; // adds clarity to what is usually just global$init() SAMPLE_FRAME("[newclass]", core); MMgc::GC* gc = core->GetGC(); PoolObject* pool = abcEnv->pool(); AvmAssert(pool == core->builtinPool); Namespace* dxns = core->dxns(); AvmAssert(dxns->isPublic()); uint32_t const last = pool->scriptCount()-1; Traits* scriptTraits = pool->getScriptTraits(last); Traits* object_ctraits = core->traits.object_ctraits; Traits* class_ctraits = core->traits.class_ctraits; AvmAssert(object_ctraits->init != NULL); AvmAssert(class_ctraits->init != NULL); Traits* object_itraits = object_ctraits->itraits; Traits* class_itraits = class_ctraits->itraits; AvmAssert(object_itraits == core->traits.object_itraits); AvmAssert(class_itraits == core->traits.class_itraits); AvmAssert(object_itraits->init != NULL); AvmAssert(class_itraits->init != NULL); // ------------------ construct the vtables // Object derives from... nothing VTable* object_ivtable = core->newVTable(object_itraits, NULL, toplevel); // Class derives from Object VTable* class_ivtable = core->newVTable(class_itraits, object_ivtable, toplevel); // Object$ derives from Class VTable* object_cvtable = core->newVTable(object_ctraits, class_ivtable, toplevel); object_cvtable->ivtable = object_ivtable; // Class$ derives from Class VTable* class_cvtable = core->newVTable(class_ctraits, class_ivtable, toplevel); class_cvtable->ivtable = class_ivtable; // global script derives from Object VTable* scriptVTable = this->newVTable(scriptTraits, object_ivtable, toplevel); // ------------------ construct the ScopeTypeChains and ScopeChains const ScopeTypeChain* scriptSTC = ScopeTypeChain::createEmpty(gc, scriptTraits); ScopeChain* scriptScope = ScriptEnv::createScriptScope(scriptSTC, scriptVTable, abcEnv); Traits* object_cstc_types[] = { scriptTraits, object_ctraits, NULL }; const ScopeTypeChain* object_cstc = ScopeTypeChain::createExplicit(gc, object_ctraits, object_cstc_types); ScopeChain* object_cscope = ScopeChain::create(gc, object_cvtable, abcEnv, object_cstc, NULL, dxns); Traits* object_istc_types[] = { scriptTraits, object_ctraits, object_itraits, NULL }; const ScopeTypeChain* object_istc = ScopeTypeChain::createExplicit(gc, object_itraits, object_istc_types); ScopeChain* object_iscope = ScopeChain::create(gc, object_ivtable, abcEnv, object_istc, NULL, dxns); Traits* class_cstc_types[] = { scriptTraits, object_ctraits, class_ctraits, NULL }; const ScopeTypeChain* class_cstc = ScopeTypeChain::createExplicit(gc, class_ctraits, class_cstc_types); ScopeChain* class_cscope = ScopeChain::create(gc, class_cvtable, abcEnv, class_cstc, NULL, dxns); Traits* class_istc_types[] = { scriptTraits, object_ctraits, class_ctraits, class_itraits, NULL }; const ScopeTypeChain* class_istc = ScopeTypeChain::createExplicit(gc, class_itraits, class_istc_types); ScopeChain* class_iscope = ScopeChain::create(gc, class_ivtable, abcEnv, class_istc, NULL, dxns); // ------------------ resolve all the vtables object_ivtable->resolveSignatures(object_iscope); class_ivtable->resolveSignatures(class_iscope); object_cvtable->resolveSignatures(object_cscope); class_cvtable->resolveSignatures(class_cscope); scriptVTable->resolveSignatures(scriptScope); // ------------------ create Object$ and Class$ instances AvmAssert(object_ctraits->getCreateClassClosureProc() == ObjectClass::createClassClosure); ClassClosure* object_class = ObjectClass::createClassClosure(object_cvtable); object_class->initPrototypeConstructor(); AvmAssert(class_ctraits->getCreateClassClosureProc() == ClassClass::createClassClosure); ClassClosure* class_class = ClassClass::createClassClosure(class_cvtable); class_class->initPrototypeConstructor(); object_class->setDelegate(class_class->prototypePtr()); class_class->prototypePtr()->setDelegate(object_class->prototypePtr()); class_class->setDelegate(class_class->prototypePtr()); // ------------------ create global script instance ScriptObject* global = ScriptObject::create(gc, scriptVTable, object_class->prototypePtr()); // ------------------ fix up the scopechains appropriately Atom global_atom = global->atom(); object_cscope->setScope(gc, 0, global_atom); object_iscope->setScope(gc, 0, global_atom); object_iscope->setScope(gc, 1, object_class->atom()); class_cscope->setScope(gc, 0, global_atom); class_cscope->setScope(gc, 1, object_class->atom()); class_iscope->setScope(gc, 0, global_atom); class_iscope->setScope(gc, 1, object_class->atom()); class_iscope->setScope(gc, 2, class_class->atom()); // ------------------ run the init-methods for Object$ and Class$ bool deferredInterrupt = false; #ifdef VMCFG_VERIFYALL if (!config.verifyonly && !config.verifyall) #endif { Isolate* isolate = getIsolate(); if (isolate != NULL) { Isolate::State state = isolate->getAggregate()->queryState(isolate); if (state == Isolate::STARTING) { isolate->getAggregate()->stateTransition(isolate, Isolate::RUNNING); } else if (isolate->isInterrupted()) { // We'll still run the initialization in interrupted state so that // whe have a properly initialized error object to throw. deferredInterrupt = true; } } object_cvtable->init->coerceEnter(object_class->atom()); class_cvtable->init->coerceEnter(class_class->atom()); } // ------------------ set up the init-method for the global script ScriptEnv* main = ScriptEnv::create(gc, scriptTraits->init, scriptScope); main->global = global; scriptVTable->init = main; #ifdef VMCFG_AOT initScriptActivationTraits(this, toplevel, scriptTraits->init); #endif // ------------------ register the names in the DomainMgr (must be done before calling the init method) GCList envs(GetGC(), pool->scriptCount()); envs.add(main); // skip the final one: it's already been done for (uint32_t i=0; i < last; i++) { ScriptEnv* scriptEnv = initOneScript(toplevel, abcEnv, pool->getScriptTraits(i)); envs.add(scriptEnv); } this->domainMgr()->addNamedScriptEnvs(abcEnv, envs); // ------------------ create the builtin class manifest toplevel->init_mainEntryPoint(main, builtinClassManifest::create(main)); // ------------------ run the init-method for global #ifdef VMCFG_VERIFYALL if (!config.verifyonly && !config.verifyall) #endif { main->coerceEnter(global->atom()); } // Force the built-in error classes to be initialized before any // non-builtin (user) code can be executed. #ifdef VMCFG_VERIFYALL if (!config.verifyonly && !config.verifyall) #endif (void)toplevel->errorClass(); #ifdef VMCFG_VERIFYALL if (config.verifyall) { // We have to jump thru some hoops here in an ugly way: // in verifyall mode, the MethodInfo invokers aren't filled in // properly, and require notifyAbcPrepared to be called first. // This breaks the sequenced bootstrapping above, but it turns out // that we don't need the sequence above in verifyall mode. exec->notifyAbcPrepared(toplevel, abcEnv); object_cvtable->init->coerceEnter(object_class->atom()); class_cvtable->init->coerceEnter(class_class->atom()); main->coerceEnter(global->atom()); } else #endif { // We don't really need to call this, since the only implementation // is a no-op when !config.verifyall... but let's do it anyway, in case // additional work is done in the future. exec->notifyAbcPrepared(toplevel, abcEnv); } return toplevel; } Atom AvmCore::handleActionPool(PoolObject* pool, Toplevel* toplevel, CodeContext* codeContext) { AvmAssert(toplevel != NULL); if (pool->scriptCount() == 0) toplevel->throwVerifyError(kMissingEntryPointError); AvmAssert(codeContext != NULL); AvmAssert(codeContext->domainEnv() != NULL); AvmAssert(codeContext->domainEnv()->domain() == pool->domain); AbcEnv* abcEnv = AbcEnv::create(GetGC(), pool, codeContext); ScriptEnv* main = initAllScripts(toplevel, abcEnv); #ifdef VMCFG_VERIFYALL AvmAssert(config.verifyonly || toplevel->objectClass != NULL); #else AvmAssert(toplevel->objectClass != NULL); #endif return callScriptEnvEntryPoint(main); } PoolObject* AvmCore::parseActionBlock(ScriptBuffer code, int /*start*/, Toplevel* toplevel, Domain* domain, const NativeInitializer* ninit, ApiVersion apiVersion, Stringp name) { (void)name; // parse constants and attributes. PoolObject* pool = AbcParser::decodeAbc(this, code, toplevel, domain, ninit, apiVersion); #ifdef DEBUGGER if (_debugger) { _debugger->processAbc(pool, code, name); } #endif return pool; } ScriptEnv* AvmCore::prepareActionBlock(ScriptBuffer code, Toplevel* toplevel, Domain* domain, CodeContext* codeContext, ApiVersion apiVersion) { PoolObject* pool = parseActionBlock(code, 0, toplevel, domain, NULL, apiVersion); if (pool->scriptCount() == 0) { toplevel->throwVerifyError(kMissingEntryPointError); } AvmAssert(codeContext != NULL); AvmAssert(codeContext->domainEnv() != NULL); AvmAssert(codeContext->domainEnv()->domain() == pool->domain); AbcEnv* abcEnv = AbcEnv::create(GetGC(), pool, codeContext); ScriptEnv* entryPoint = initAllScripts(toplevel, abcEnv); return entryPoint; } Atom AvmCore::handleActionBlock(ScriptBuffer code, int start, Toplevel* toplevel, const NativeInitializer* ninit, CodeContext *codeContext, ApiVersion apiVersion, Stringp name) { AvmAssert(codeContext != NULL); AvmAssert(toplevel != NULL); Domain* domain = codeContext->domainEnv()->domain(); // parse constants and attributes. PoolObject *pool = parseActionBlock(code, start, toplevel, domain, ninit, apiVersion, name); return handleActionPool(pool, toplevel, codeContext); } int AvmCore::evaluateScriptBuffer(ScriptBuffer& buffer, bool enter_debugger_on_enter) { (void)buffer; (void)enter_debugger_on_enter; AvmAssert(false); // override me return -1; } #ifdef VMCFG_EVAL Atom AvmCore::handleActionSource(String* code, String* filename, Toplevel* toplevel, const NativeInitializer* ninit, CodeContext *codeContext, ApiVersion apiVersion) { AvmAssert(toplevel != NULL); ScriptBuffer buffer = avmplus::compileProgram(this, toplevel, code, filename); return handleActionBlock(buffer, 0, toplevel, ninit, codeContext, apiVersion); } #endif // VMCFG_EVAL #ifdef VMCFG_AOT void AvmCore::handleAOT(Toplevel* toplevel, CodeContext* codeContext) { DomainEnv* domainEnv = codeContext->domainEnv(); Domain* domain = domainEnv->domain(); for(uint32_t i=0; i_equals(rhs); else if ((rtype == kObjectType) && (isXMLList(rhs))) return atomToXMLList (rhs)->_equals(lhs); if (ltype == rtype) { // same type switch (ltype) { case 0: // kUnusedAtomTag return trueAtom; case kSpecialBibopType: { #ifdef VMCFG_FLOAT // if either is undefined, return false if (lhs == AtomConstants::undefinedAtom || rhs == AtomConstants::undefinedAtom) return lhs==rhs ? trueAtom : falseAtom; uint8_t lt = bibopKind(lhs) , rt = bibopKind(rhs) ; if ((lt== kBibopFloatType) && (rt== kBibopFloatType)) { float l = atomToFloat(lhs); float r = atomToFloat(rhs); // if either arg is NaN, C++ == returns false, which matches ECMA. return l == r ? trueAtom : falseAtom; } AvmAssertMsg(lt==kBibopFloat4Type || rt == kBibopFloat4Type, "Unhandled bibopKind"); float4_decl_v(lhs); float4_decl_v(rhs); return f4_eq_i(lhsv, rhsv) ? trueAtom:falseAtom; #else return trueAtom; #endif // VMCFG_FLOAT } case kStringType: if (lhs == rhs) return trueAtom; return (*atomToString(lhs) == *atomToString(rhs)) ? trueAtom : falseAtom; case kBooleanType: case kIntptrType: return lhs == rhs ? trueAtom : falseAtom; case kNamespaceType: // E4X 11.5.1, pg 53 return atomToNamespace(lhs)->EqualTo(atomToNamespace(rhs))? trueAtom : falseAtom; case kObjectType: { // E4X 11.5.1, pg 53 if (lhs == rhs) return trueAtom; if (isXML(lhs) && isXML(rhs)) { XMLObject *x = atomToXMLObject (lhs); XMLObject *y = atomToXMLObject (rhs); if ((((x->getClass() & (E4XNode::kText | E4XNode::kCDATA | E4XNode::kAttribute))) && y->hasSimpleContent()) || (((y->getClass() & (E4XNode::kText | E4XNode::kCDATA | E4XNode::kAttribute))) && x->hasSimpleContent())) { return ((*string(lhs) == *string(rhs)) ? trueAtom : falseAtom); } else { return x->getNode()->_equals(x->toplevel(), this, y->getNode()) ? trueAtom : falseAtom; } } else if (isQName(lhs) && isQName(rhs)) { QNameObject *qn1 = atomToQName (lhs); QNameObject *qn2 = atomToQName (rhs); return (((qn1->getURI() == qn2->getURI()) && (qn1->get_localName() == qn2->get_localName()))? trueAtom : falseAtom); } else { return falseAtom; } } case kDoubleType: // C++ portability note -- if either arg is NaN, java == returns false, which matches ECMA. return atomToDouble(lhs) == atomToDouble(rhs) ? trueAtom : falseAtom; } } else { if (isNullOrUndefined(lhs) && isNullOrUndefined(rhs)) return trueAtom; #ifdef VMCFG_FLOAT if( isFloat4(lhs) || isFloat4(rhs) ){ if (isNullOrUndefined(lhs) || isNullOrUndefined(rhs)) return falseAtom; float4_decl_v(lhs); float4_decl_v(rhs); return f4_eq_i(lhsv, rhsv) ? trueAtom : falseAtom; } #endif if (ltype == kIntptrType && rtype == kDoubleType) return ((double)atomGetIntptr(lhs)) == atomToDouble(rhs) ? trueAtom : falseAtom; if (ltype == kDoubleType && rtype == kIntptrType) return atomToDouble(lhs) == ((double)atomGetIntptr(rhs)) ? trueAtom : falseAtom; #ifdef VMCFG_FLOAT if(isNumberOrFloat(lhs) && isNumberOrFloat(rhs)) { AvmAssert(isFloat(lhs) || isFloat(rhs)); return number(lhs) == number(rhs) ? trueAtom : falseAtom; } #endif // 16. If Type(x) is Numeric and Type(y) is String, // return the result of the comparison x == ToNumeric(y). // Note: while it's tempting to blindly follow the spec and recursively call equals(), // the truth is that ToNumeric(string) == ToNumber(string), and we always end up doing // a number-to-number comparison. // (important remark: lhs can't be a float4, it would've been already handled) if (isNumeric(lhs) && isString(rhs)) { #ifdef VMCFG_FLOAT if(isFloat(lhs)) return ((double)atomToFloat(lhs)) == number(rhs) ? trueAtom : falseAtom; else #endif // VMCFG_FLOAT return number_d(lhs) == number(rhs) ? trueAtom : falseAtom; } // 17. If Type(x) is String and Type(y) is Numeric, // return the result of the comparison ToNumeric(x) == y. // See note on the previous point if (isString(lhs) && isNumeric(rhs)) { #ifdef VMCFG_FLOAT if(isFloat(rhs)) return ((double)atomToFloat(rhs)) == number(lhs) ? trueAtom : falseAtom; else #endif // VMCFG_FLOAT return number(lhs) == number_d(rhs) ? trueAtom : falseAtom; } // E4X 11.5.1, step 4. Placed slightly lower then in the spec // to handle quicker cases earlier. No cases above should be comparing // an object to a non-object if (((ltype == kObjectType) && isXML(lhs) && atomToXMLObject(lhs)->hasSimpleContent()) || ((rtype == kObjectType) && isXML(rhs) && atomToXMLObject(rhs)->hasSimpleContent())) { return ((*string(lhs) == *string(rhs)) ? trueAtom : falseAtom); } // 18. If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y. if (ltype == kBooleanType) return equals((lhs&~7)|kIntptrType, rhs); // equal(toInteger(lhs), rhs) // 19. If Type(y) is Boolean, return the result of the comparison x == ToNumber(y). if (rtype == kBooleanType) return equals(lhs, (rhs&~7)|kIntptrType); // equal(lhs, toInteger(rhs)) // 20. If Type(x) is either String or Number and Type(y) is Object, // return the result of the comparison x == ToPrimitive(y). if ((isString(lhs) || isNumeric(lhs)) && rtype == kObjectType) return equals(lhs, atomToScriptObject(rhs)->defaultValue()); // 21. If Type(x) is Object and Type(y) is either String or Number, // return the result of the comparison ToPrimitive(x) == y. if ((isString(rhs) || isNumeric(rhs)) && ltype == kObjectType) return equals(atomToScriptObject(lhs)->defaultValue(), rhs); } return falseAtom; } /** * this is the abstract relational comparison algorithm according to ECMA 262 11.8.5 * @param lhs * @param rhs * @return trueAtom, falseAtom, or undefinedAtom */ #if defined(_MSC_VER) && defined(AVMPLUS_IA32) // Force this function to have a frame pointer so the doubles used are 8-byte aligned #pragma optimize("y", off) #endif Atom AvmCore::compare(Atom lhs, Atom rhs) { // fixme - toprimitive must take number hint, so "7" becomes 7 if (atomIsBothIntptr(lhs, rhs)) { // fast path for integers return lhs < rhs ? trueAtom : falseAtom; } lhs = primitive(lhs); rhs = primitive(rhs); if (isString(lhs) && isString(rhs)) { // string compare. todo optimize! // we already know they are strings, call atomToString() rather than string() return *atomToString(lhs) < *atomToString(rhs) ? trueAtom : falseAtom; } // numeric compare // We only do float comparison if both LHS and RHS are floats, // but in this case, float and double comparison are identical. // Float4s are compared as Numbers (i.e. converted to NaN). double dx = number(lhs); double dy = number(rhs); if (MathUtils::isNaN(dx)) return undefinedAtom; if (MathUtils::isNaN(dy)) return undefinedAtom; return dx < dy ? trueAtom : falseAtom; } #if defined(_MSC_VER) && defined(AVMPLUS_IA32) #pragma optimize("", on) #endif Atom AvmCore::stricteq(Atom lhs, Atom rhs) { if (isNull(lhs)) return isNull(rhs) ? trueAtom : falseAtom; if (isNull(rhs)) return falseAtom; // We already know that lhs is not null int ltype = atomKind(lhs); int rtype = atomKind(rhs); if (ltype == rtype) { // same type switch (ltype) { case kSpecialBibopType: if(lhs == undefinedAtom && rhs == undefinedAtom) return trueAtom; #ifdef VMCFG_FLOAT if(isFloat(lhs) && isFloat(rhs)) return atomToFloat(lhs) == atomToFloat(rhs) ? trueAtom : falseAtom; if(isFloat4(lhs) && isFloat4(rhs)) return f4_eq_i(atomToFloat4(lhs), atomToFloat4(rhs)) ? trueAtom : falseAtom; #endif // VMCFG_FLOAT return falseAtom; // one bibop, other "undefined"; or one float, the other float4. case kStringType: return (lhs==rhs || *atomToString(lhs) == *atomToString(rhs)) ? trueAtom : falseAtom; case kBooleanType: case kIntptrType: case kNamespaceType: return lhs == rhs ? trueAtom : falseAtom; case kObjectType: { if (lhs == rhs) return trueAtom; if (isXML(lhs) && isXML(rhs)) { E4XNode *lhn = atomToXML(lhs); E4XNode *rhn = atomToXML(rhs); return ((lhn == rhn) ? trueAtom : falseAtom); } return falseAtom; } case kDoubleType: // C++ portability note -- if either arg is NaN, java == returns false, which matches ECMA. return atomToDouble(lhs) == atomToDouble(rhs) ? trueAtom : falseAtom; } } // Sometimes ints can hide in double atoms (neg zero for one); also, we may have comparison with float else if (isNumber(lhs) && isNumber(rhs)) { return number(lhs) == number(rhs) ? trueAtom : falseAtom; } return falseAtom; } /** * throwException will throw an exception. This function * never returns. The exception to be thrown is specified * by exception. */ void AvmCore::throwException(Exception *exception) { #ifdef DEBUGGER if (_debugger && !(exception->flags & Exception::SEEN_BY_DEBUGGER)) { // I'm going to set the SEEN_BY_DEBUGGER flag now, before calling // filterException(), just to avoid reentrancy problems (we don't // want to end up back here with the same Exception object and get // stuck in an infinite loop). exception->flags |= Exception::SEEN_BY_DEBUGGER; bool willBeCaught = willExceptionBeCaught(exception); if (passAllExceptionsToDebugger || !willBeCaught) { // filterException() returns 'true' if it somehow let the user know // about the exception, 'false' if it ignored the exception. if (_debugger->filterException(exception, willBeCaught)) exception->flags |= Exception::SEEN_BY_DEBUGGER; else exception->flags &= ~Exception::SEEN_BY_DEBUGGER; } else { exception->flags &= ~Exception::SEEN_BY_DEBUGGER; } } #endif #ifdef VMCFG_TELEMETRY_SAMPLER // Send the exception info along with the stack trace over telemetry if (!(exception->flags & Exception::SUPPRESS_ERROR_REPORT) && getTelemetry() && getTelemetry()->IsActive()) { StringBuffer exceptionStringBuffer(this); bool wroteErrString = false; if (atomKind(exception->atom) == kObjectType) { // string() is not very explanatory for most Error objects. // Get the full class name including the namespace instead. ScriptObject *so = atomToScriptObject(exception->atom); if (so) { so->traits()->print(exceptionStringBuffer, true); wroteErrString = true; } } if (!wroteErrString) { // backup, just convert atom to a string, it will say "Error" exceptionStringBuffer << StUTF8String(string(exception->atom)).c_str(); } exceptionStringBuffer << '\n'; // Get the stack trace GetStackTrace(exceptionStringBuffer); // Send the metric as a string TELEMETRY_STRING(getTelemetry(), ".as.exception", exceptionStringBuffer.c_str()); } #endif // exceptionFrame should not be NULL; if it is, // you are missing a TRY/CATCH block around // a call to an AVM+ method that throws an // exception. AvmAssert(exceptionFrame != NULL); exceptionFrame->throwException(exception); } /** * throwAtom will throw an exception. This function * never returns. The exception to be thrown is specified * by atom. */ void AvmCore::throwAtom(Atom atom) { throwException(new (GetGC()) Exception(this, atom)); } #ifdef DEBUGGER bool AvmCore::willExceptionBeCaught(Exception* exception) { ExceptionFrame* ef; for (ef = exceptionFrame; ef != NULL; ef = ef->prevFrame) { switch (ef->catchAction) { // The CATCH block will consume any exception that occurs, and will not treat it as // an error, so exceptions should not be reported to the debugger. case kCatchAction_Ignore: return true; // The CATCH block will rethrow any exception that occurs; so, we will 'continue', // which will take us back to the 'for' loop to keep going up the exception stack, // until we find a frame with some other value. case kCatchAction_Rethrow: continue; // return to the 'for' loop // The CATCH block will treat any exception that occurs as an error -- probably by // calling uncaughtException, but possibly by some other means. So, exceptions // should be reported to the debugger. case kCatchAction_ReportAsError: return false; // This is the unfortunate, but rare, case where we can't tell in advance how the // CATCH block will handle exceptions. In this case, we wil not report the // exception to the debugger. case kCatchAction_Unknown: return true; // The CATCH block will walk up the exception frames that were defined in // ActionScript code -- e.g. "try { } catch (e:Error) { }" -- looking for one // which catch this exception. So, we will do the same thing, and only report // the error to the debugger if there is no catch block for it. case kCatchAction_SearchForActionScriptExceptionHandler: { CallStackNode* callStackNode; // Walk all the way up the stack, one frame at a time, looking for // one which will catch this exception. for (callStackNode = callStack; callStackNode; callStackNode = callStackNode->next()) { MethodInfo* info = callStackNode->info(); #ifdef VMCFG_AOT // bparadie 2009-11-12: In LLVMEmitter exceptfilt will be set to either 0 = catch nothing, -1 = catch anything, // or the pointer of the exception handler. exceptfilt is being passed to abcOP_debugEnter as the 5th parameter // and ends up in callStackNode.m_eip. In other words kCatchAction_SearchForActionScriptExceptionHandler in // AvmCore::willExceptionBeCaught() can return true if callStackNode->eip() != 0. if (info && info->isAotCompiled() && callStackNode->eip() != 0) return true; #endif // native methods don't have exception handlers if (info && info->isNative()) continue; // Note: we only care if the info actually has an exception handler or not... // if WORD_CODE is enabled and this function is jitted, info->word_code_exceptions() will // be null even if we have an exception handler. So always look at info->abc_exceptions() // since it is (currently) always correct. const bool hasExceptionHandler = info && info->abc_exceptions() != NULL; if (hasExceptionHandler && callStackNode->eip() && *callStackNode->eip()) { // Check if this particular frame of the callstack // is going to catch the exception. int32_t ordinal; // unused if (findExceptionHandlerNoRethrow(info, *callStackNode->eip(), exception, &ordinal) != NULL) return true; } } // No ActionScript handler was found, so the exception is going to be // re-thrown; so, 'continue' will get us back to the 'for' loop continue; } default: AvmAssert(false); break; } } return false; } /** * Returns LANG_xx enum */ int AvmCore::determineLanguage() { if (langID < 0) { // @todo system probe to determine language, then return one of our known enums langID = LANG_en; } return langID; } #ifndef VMCFG_DEBUGGER_STUB String* AvmCore::findErrorMessage(int errorID, int* mapTable, /* 2 ints per entry i, i+1 */ const char** errorTable, int numErrors) { // Above that, we must binary search. int lo = 0; int hi = numErrors-1; while (lo <= hi) { int pivot = (lo+hi)>>1; int testID = mapTable[2*pivot]; if (testID == errorID) { lo = pivot; break; } else if (errorID < testID) { hi = pivot-1; } else { lo = pivot+1; } } int index = mapTable[2*lo+1]; int ident = mapTable[2*lo]; if (ident == errorID) { return newStringUTF8(errorTable[index]); } else { return NULL; } } #endif #endif String* AvmCore::getErrorMessage(int errorID) { Stringp buffer = newConstantStringLatin1("Error #"); buffer = concatStrings(buffer, internInt(errorID)); #if defined(DEBUGGER) && !defined(VMCFG_DEBUGGER_STUB) if (_debugger) { // errorConstants is declared char* but is encoded as UTF8 Stringp out = findErrorMessage(errorID, errorMappingTable, errorConstants[determineLanguage()], kNumErrorConstants); if (out) { buffer = concatStrings(buffer, newConstantStringLatin1(": ")); buffer = concatStrings(buffer, out); } else { AvmAssertMsg(0, "errorID not found in the message table; check ErrorConstants.cpp you may need to regenerate it"); } } #endif return buffer; } String* AvmCore::atomToErrorString(Atom a) { String* out = NULL; #ifdef DEBUGGER StringBuffer sb(this); // 256B gc alloc occurs here. sb << asAtom(a); out = sb.toString(); #else (void)a; out = kEmptyString; #endif /* DEBUGGER */ return out; } String* AvmCore::toErrorString(int d) { #ifdef DEBUGGER return MathUtils::convertIntegerToStringBase10(this, d, MathUtils::kTreatAsSigned); #else (void)d; return kEmptyString; #endif /* DEBUGGER */ } String* AvmCore::toErrorString(const char* s) { String* out = NULL; #ifdef DEBUGGER if (s) out = this->newStringUTF8(s); else out = kEmptyString; #else out = kEmptyString; (void)s; #endif /* DEBUGGER */ return out; } String* AvmCore::toErrorString(const wchar* s) { String* out = NULL; #ifdef DEBUGGER if (s) out = this->newStringUTF16(s); else out = kEmptyString; #else out = kEmptyString; (void)s; #endif /* DEBUGGER */ return out; } String* AvmCore::toErrorString(MethodInfo* m) { String* s = NULL; #ifdef DEBUGGER if (m) { StringBuffer sb(this); // 256B gc alloc occurs here. m->print(sb); s = sb.toString(); } else { s = kEmptyString; } #else s = kEmptyString; (void)m; #endif /* DEBUGGER */ return s; } String* AvmCore::toErrorString(const Multiname& n) { return toErrorString(&n); } String* AvmCore::toErrorString(const Multiname* n) { String* s = NULL; #ifdef DEBUGGER if (n) { StringBuffer sb(this); // 256B gc alloc occurs here. sb << Multiname::FormatNameOnly(n); s = sb.toString(); } else { s = kEmptyString; } #else s = kEmptyString; (void)n; #endif /* DEBUGGER */ return s; } String* AvmCore::toErrorString(Namespacep ns) { String* s = NULL; #ifdef DEBUGGER if (ns) { StringBuffer sb(this); // 256B gc alloc occurs here. ns->print(sb); s = sb.toString(); } else { s = kEmptyString; } #else s = kEmptyString; (void)ns; #endif /* DEBUGGER */ return s; } String* AvmCore::toErrorString(const Traits* t) { #ifndef DEBUGGER (void)t; return kEmptyString; #else if (!t) { return newConstantStringLatin1("*"); } StringBuffer sb(this); // 256B gc alloc occurs here. if (t->base == traits.class_itraits) { t = t->itraits; sb << "class "; } Namespacep ns = t->ns(); if (ns != NULL && !ns->getURI()->isEmpty()) sb << ns << "."; Stringp n = t->name(); if (n) sb << n; else sb << "(null)"; Stringp s = sb.toString(); return s; #endif /* DEBUGGER */ } String* AvmCore::formatErrorMessageV( int errorID, Stringp arg1, Stringp arg2, Stringp arg3) { Stringp out = NULL; Stringp errorMessage = getErrorMessage(errorID); if (errorMessage) { #ifdef DEBUGGER StUTF8String errorUTF8(errorMessage); const char *format = errorUTF8.c_str(); // This block is enclosed in {} to force // StringBuffer destructor to unwind. { StringBuffer buffer(this); buffer.formatP( format, arg1, arg2, arg3); out = buffer.toString(); } #else /** * We can skip all the above processing since getErrorMessage() only * returns the error code. Likewise the toErrorString() methods return kEmptyString */ (void)arg1; (void)arg2; (void)arg3; out = errorMessage; #endif /* DEBUGGER*/ } else { AvmAssertMsg(errorMessage != NULL, "contract with getErrorMessage() broken, we should always get a string!"); out = kEmptyString; } return out; } void AvmCore::throwErrorV(ClassClosure *type, int errorID, Stringp arg1, Stringp arg2, Stringp arg3) { AvmAssertMsg(type != NULL, "type should never be NULL - internal bootstrap error if it is."); Stringp out = formatErrorMessageV( errorID, arg1, arg2, arg3); Atom args[3] = { nullObjectAtom, out->atom(), intToAtom(errorID) }; throwAtom(type->construct(2, args)); } /*static*/ Atom AvmCore::booleanAtom(Atom atom) { return boolean(atom) == 0 ? falseAtom : trueAtom; } /*static*/ int AvmCore::boolean(Atom atom) { if (!AvmCore::isNullOrUndefined(atom)) { switch (atomKind(atom)) { case kIntptrType: return atomGetIntptr(atom) != 0; case kDoubleType: { double d = atomToDouble(atom); return !MathUtils::isNaN(d) && d != 0.0; } case kBooleanType: return (atom & ~7) != 0; case kStringType: return !isNull(atom) && atomToString(atom)->length() > 0; #ifdef VMCFG_FLOAT case kSpecialBibopType: { /* we already know it's not "undefined"! */ if( atom == undefinedAtom ) return 0; uint8_t bKind = bibopKind(atom); if(bKind == kBibopFloatType) { float f = atomToFloat(atom); return f != 0.0 && !MathUtils::isNaNf(f) ; } else if(bKind == kBibopFloat4Type) { return 0; } else { AvmAssertMsg(false, "Unhandled bibop kind in ToBoolean()!"); return 0; } } #endif // VMCFG_FLOAT default: // kObjectType, kNamespaceType return !isNull(atom); } } else { return false; } } /* ToPrimitive: Return a default value for the Object. The default value of an object is retrieved by calling the internal [[DefaultValue]] method of the object, passing the optional hint PreferredType. The behaviour of the [[DefaultValue]] method is defined by this specification for all native ECMAScript objects (section 8.6.2.6). */ /*static*/ Atom AvmCore::primitive(Atom atom) { return isObject(atom) ? atomToScriptObject(atom)->defaultValue() : atom; } Atom AvmCore::numberAtom(Atom atom) { return numericAtomImpl(atom); } Atom AvmCore::numericAtom(Atom atom) { return numericAtomImpl(atom); } template Atom AvmCore::numericAtomImpl(Atom atom ) { if (!isNull(atom)) { switch (atomKind(atom)) { case kSpecialBibopType: #ifdef VMCFG_FLOAT if(atom == undefinedAtom) return kNaN; if(!newTypes2Double) return atom; // Float or Float4; stays unchanged in toNumeric if(isFloat(atom)) return doubleToAtom(atomToFloat(atom)); AvmAssert(isFloat4(atom)); #endif // VMCFG_FLOAT return kNaN; case kStringType: return doubleToAtom(atomToString(atom)->toNumber()); default: AvmAssert(false); case kBooleanType: return (atom&~7) | kIntptrType; case kDoubleType: case kIntptrType: return atom; case kNamespaceType: // return ToNumber(namespace->uri) return numberAtom(atomToNamespace(atom)->getURI()->atom()); case kObjectType: return numericAtomImpl( atomToScriptObject(atom)->defaultValue()); } } return zeroIntAtom; } /*static*/ double AvmCore::number(Atom atom) { for (;;) { const int kind = atomKind(atom); // kIntptrType is by far the most common if (kind == kIntptrType) return (double) atomGetIntptr(atom); // kDoubleType is next most common if (kind == kDoubleType) return atomToDouble(atom); if (AvmCore::isNull(atom)) return 0.0; // all other cases are relatively rare switch (kind) { case kStringType: return atomToString(atom)->toNumber(); default: AvmAssert(false); // should never get here, but fall through to NaN if we do. case kSpecialBibopType: #ifdef VMCFG_FLOAT if (atom == undefinedAtom) return MathUtils::kNaN; if (bibopKind(atom) == kBibopFloatType) return atomToFloat(atom); AvmAssert(isFloat4(atom)); #endif return MathUtils::kNaN; case kBooleanType: return atom == trueAtom ? 1.0 : 0.0; case kNamespaceType: atom = atomToNamespace(atom)->getURI()->atom(); break; // continue loop, effectively a tailcall case kObjectType: atom = AvmCore::atomToScriptObject(atom)->defaultValue(); break; // continue loop, effectively a tailcall } } //AvmAssert(0); // can't get here //return 0.0; } Stringp AvmCore::intern(Atom atom) { if (!isNull(atom)) { switch (atomKind(atom)) { case kBooleanType: return booleanStrings[atom>>3]; case kStringType: return internString(atom); case kNamespaceType: return atomToNamespace(atom)->getURI(); case kSpecialBibopType: #ifdef VMCFG_FLOAT if(atom==undefinedAtom) return kundefined; if (bibopKind(atom)==kBibopFloatType) return internFloat(atomToFloat(atom)); if (bibopKind(atom)==kBibopFloat4Type) return internFloat4(atomToFloat4(atom)); AvmAssert(!"Unknown bibopKind"); #endif // VMCFG_FLOAT return kundefined; case kObjectType: return internString(atomToScriptObject(atom)->toString()); case kIntptrType: #ifdef AVMPLUS_64BIT return atomCanBeInt32(atom) ? internInt((int32_t)atomGetIntptr(atom)) : internDouble((double)atomGetIntptr(atom)); #else // atomCanBeInt32() is always true for 32-bit; // use explicit ifdef to avoid compiler warnings. return internInt((int32_t)atomGetIntptr(atom)); #endif case kDoubleType: default: // number return internDouble(atomToDouble(atom)); } } else { return knull; } } Namespacep AvmCore::internNamespace(Namespacep ns) { if (ns->isPrivate()) { // only intern namespaces with interned uri's. this means anonymous // namespaces with null prefixes are always unique and can't be shared. return ns; } int i = findNamespace(ns); if (namespaces[i] == NULL) { // first time we've seen this namespace. intern it nsCount++; namespaces[i] = ns; return ns; } // found the namespace, indexing by URI. return the interned copy. return namespaces[i]; } Namespacep AvmCore::cloneNamespace(Namespacep ns) { // FIXME things get interesting if ns is from a different AvmCore // and the core starts moving string internals (e.g., it's dynamicizing) Stringp prefix = ns->hasPrefix() ? internString(AvmCore::atomToString(ns->getPrefix())) : NULL; Stringp uri = ns->getURI(); if (uri != NULL) { uri = internString(uri->clone(this)); } return Namespace::create(gc, prefix ? prefix->toAtom() : undefinedAtom, uri, ns->getType(), ns->getApiVersion()); } #ifdef AVMPLUS_VERBOSE /* static */ void AvmCore::formatMultiname(PrintWriter& out, uint32_t index, PoolObject* pool) { if (index > 0 && index < pool->cpool_mn_offsets.length()) { Multiname name; pool->parseMultiname(name, index); out << name; } else { out << "invalid multiname index " << (uint32_t)index; } } void AvmCore::formatOpcode(PrintWriter& buffer, const uint8_t *pc, const uint8_t *code_end, AbcOpcode opcode, ptrdiff_t off, PoolObject* pool) { pc++; switch (opcode) { case OP_debugfile: case OP_pushstring: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->constantStringCount) { buffer << " \"" << pool->getString(index) << "\""; } else { buffer << " invalid string index=" << index; } break; } case OP_pushbyte: buffer << opcodeInfo[opcode].name << " " << int(int8_t(*pc)); break; case OP_pushint: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->cpool_int.length()) { buffer << " " << pool->cpool_int[index]; } else { buffer << " invalid index=" << index; } break; } case OP_pushuint: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->cpool_uint.length()) { buffer << " " << (double)pool->cpool_uint[index]; } else { buffer << " invalid index=" << index; } break; } case OP_pushdouble: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->cpool_double.length()) { buffer << " " << pool->cpool_double[index]->value; } else { buffer << " invalid_index=" << index; } break; } #ifdef VMCFG_FLOAT case OP_pushfloat: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->cpool_float.length()) { buffer << " " << pool->cpool_float[index]->value; } else { buffer << " invalid_index=" << index; } break; } case OP_pushfloat4: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->cpool_float4.length()) { const float* pf4 = (const float*) pool->cpool_float4[index]; buffer << " " << pf4[0] << "," << pf4[1] << "," << pf4[2] << "," << pf4[3] ; } else { buffer << " invalid_index=" << index; } break; } #endif // VMCFG_FLOAT case OP_pushnamespace: { buffer << opcodeInfo[opcode].name; uint32_t index = readU32(pc); if (index > 0 && index < pool->cpool_ns.length()) { buffer << " " << pool->cpool_ns[index]->getURI(); } else { buffer << " invalid index=" << index; } break; } case OP_getsuper: case OP_setsuper: case OP_getproperty: case OP_setproperty: case OP_initproperty: case OP_findpropstrict: case OP_findproperty: case OP_finddef: case OP_deleteproperty: case OP_istype: case OP_coerce: case OP_astype: { buffer << opcodeInfo[opcode].name << " "; formatMultiname(buffer, readU32(pc), pool); break; } case OP_callproperty: case OP_callpropvoid: case OP_callproplex: case OP_callsuper: case OP_callsupervoid: { uint32_t index = readU32(pc); int argc = readU32(pc); buffer << opcodeInfo[opcode].name << " "; formatMultiname(buffer, index, pool); buffer << " " << argc; break; } case OP_callstatic: case OP_newfunction: { uint32_t method_id = readU32(pc); buffer << opcodeInfo[opcode].name; if (method_id >= pool->methodCount()) buffer << " invalid"; buffer << " method_id=" << method_id; if (opcode == OP_callstatic) { buffer << " argc=" << (int)readU32(pc); // argc } if (method_id < pool->methodCount()) { Stringp fname = pool->getMethodInfo(method_id)->getMethodName(); if (fname) buffer << " " << fname; else buffer << " null"; } break; } case OP_newclass: { uint32_t id = readU32(pc); if (id < pool->classCount()) { Traits* c = pool->getClassTraits(id); buffer << opcodeInfo[opcode].name << " " << c; } break; } case OP_lookupswitch: { ptrdiff_t target = off + readS24(pc); pc += 3; // maxindex is really a U30, so strip off the two hi bits // (don't bother trying to throw an error, Verifier will handle that) uint32_t maxindex = readU32(pc) & 0x3FFFFFF; buffer << opcodeInfo[opcode].name << " default:" << (int)target << " maxcase:"< code_end) { buffer << " invalid"; break; } target = off + readS24(pc); pc += 3; buffer << " " << (int)target; } break; } case OP_ifnlt: case OP_ifnle: case OP_ifngt: case OP_ifnge: case OP_jump: case OP_iftrue: case OP_iffalse: case OP_ifeq: case OP_ifge: case OP_ifgt: case OP_ifle: case OP_iflt: case OP_ifne: case OP_ifstricteq: case OP_ifstrictne: { int imm24 = 0, imm8 = 0; unsigned int imm30 = 0, imm30b = 0; const uint8_t* p2 = pc-1; readOperands(p2, imm30, imm24, imm30b, imm8); int insWidth = (int)(p2-pc); ptrdiff_t target = off + insWidth + imm24 + 1; buffer << opcodeInfo[opcode].name << " " << (double)target; break; } default: switch (opcodeInfo[opcode].operandCount) { default: buffer << opcodeInfo[opcode].name; break; case 1: { buffer << opcodeInfo[opcode].name << ' ' << (int)readU32(pc); } break; case 2: { int first = readU32(pc); int second = readU32(pc); buffer << opcodeInfo[opcode].name << ' ' << first << ' ' << second; } break; } } } #ifdef VMCFG_WORDCODE void AvmCore::formatBits(PrintWriter& buffer, uint32_t bits) { Atom a = (Atom)(intptr_t)(int32_t)bits; if (isUndefined(a)) buffer << "undefined"; else if (isBoolean(a)) buffer << (boolean(a) ? "true" : "false"); else if (atomIsIntptr(a)) buffer << (double)atomGetIntptr(a); else buffer << "[unknown: " << bits << "]"; } void AvmCore::formatOpcode(PrintWriter& buffer, const uintptr_t *pc, const uintptr_t *code_end, WordOpcode opcode, ptrdiff_t off, PoolObject* pool) { pc++; switch (opcode) { case WOP_debugfile: case WOP_pushstring: { buffer << wopAttrs[opcode].name; uint32_t index = (uint32_t)*pc++; if (index > 0 && index < pool->constantStringCount) buffer << " \"" << pool->getString(index) << "\""; else buffer << " OUT OF RANGE: " << index; break; } case WOP_pushbits: { uint32_t bits = (uint32_t)*pc++; buffer << wopAttrs[opcode].name << " "; formatBits(buffer, bits); break; } case WOP_push_doublebits: { double_overlay d; d.bits32[0] = (uint32_t)*pc++; d.bits32[1] = (uint32_t)*pc++; buffer << wopAttrs[opcode].name << " " << d.value; break; } case WOP_pushdouble: { buffer << wopAttrs[opcode].name; uint32_t index = (uint32_t)*pc++; if (index < pool->cpool_double.length()) buffer << " " << pool->cpool_double[index]->value; else buffer << " OUT OF RANGE: " << index; break; } case WOP_pushnamespace: { buffer << wopAttrs[opcode].name; uint32_t index = (uint32_t)*pc++; if (index > 0 && index < pool->cpool_ns.length()) buffer << " " << pool->cpool_ns[index]->getURI(); else buffer << " OUT OF RANGE: " << index; break; } case WOP_findpropglobal: case WOP_findpropglobalstrict: { buffer << wopAttrs[opcode].name << " "; formatMultiname(buffer, (uint32_t)*pc++, pool); buffer << " " << (uint32_t)*pc++; break; } case WOP_getsuper: case WOP_setsuper: case WOP_getproperty: case WOP_setproperty: case WOP_initproperty: case WOP_findpropstrict: case WOP_findproperty: case WOP_finddef: case WOP_deleteproperty: case WOP_istype: case WOP_coerce: case WOP_astype: { buffer << wopAttrs[opcode].name << " "; formatMultiname(buffer, (uint32_t)*pc++, pool); break; } case WOP_callproperty: case WOP_callpropvoid: case WOP_callproplex: case WOP_callsuper: case WOP_callsupervoid: { uint32_t index = (uint32_t)*pc++; int argc = (int)*pc++; buffer << wopAttrs[opcode].name << " "; formatMultiname(buffer, index, pool); buffer << " " << argc; break; } case WOP_callstatic: case WOP_newfunction: { uint32_t method_id = (uint32_t)*pc++; buffer << wopAttrs[opcode].name; if (method_id < pool->methodCount()) buffer << " invalid"; buffer << " method_id=" << method_id; if (opcode == WOP_callstatic) buffer << " argc=" << (uint32_t)*pc++; // argc if (method_id < pool->methodCount()) buffer << " " << pool->getMethodInfo(method_id)->getMethodName(); break; } case WOP_newclass: { uint32_t id = (uint32_t)*pc++; if (id < pool->classCount()) { Traits* c = pool->getClassTraits(id); buffer << wopAttrs[opcode].name << " " << c; } else { buffer << wopAttrs[opcode].name << " invalid class index " << id; } break; } case WOP_lookupswitch: { ptrdiff_t target = off + *pc++; uint32_t maxindex = (uint32_t)*pc++; buffer << wopAttrs[opcode].name << " default:" << (int)target << " maxcase:"< code_end) { buffer << " invalid"; break; } target = off + *pc++; buffer << " " << (int)target; } break; } case WOP_ifnlt: case WOP_ifnle: case WOP_ifngt: case WOP_ifnge: case WOP_jump: case WOP_iftrue: case WOP_iffalse: case WOP_ifeq: case WOP_ifge: case WOP_ifgt: case WOP_ifle: case WOP_iflt: case WOP_ifne: case WOP_ifstricteq: case WOP_ifstrictne: { int offset = (int)*pc++; buffer << wopAttrs[opcode].name << " " << (uint32_t)(off + 2 + offset); break; } #ifdef VMCFG_WORDCODE_PEEPHOLE case WOP_subtract_lb: case WOP_multiply_lb: case WOP_divide_lb: case WOP_bitand_lb: case WOP_bitor_lb: case WOP_bitxor_lb: { uint32_t r1 = (uint32_t)*pc++; uint32_t b1 = (uint32_t)*pc++; buffer << wopAttrs[opcode].name << " " << r1 << " "; formatBits(buffer, b1); break; } case WOP_iflt_ll: case WOP_ifnlt_ll: case WOP_ifle_ll: case WOP_ifnle_ll: case WOP_ifgt_ll: case WOP_ifngt_ll: case WOP_ifge_ll: case WOP_ifnge_ll: case WOP_ifeq_ll: case WOP_ifne_ll: case WOP_ifstricteq_ll: case WOP_ifstrictne_ll: { uint32_t r1 = (uint32_t)*pc++; uint32_t r2 = (uint32_t)*pc++; int offset = (int)*pc++; buffer << wopAttrs[opcode].name << " " << r1 << " " << r2 << " " << (uint32_t)(off + 4 + offset); break; } case WOP_iflt_lb: case WOP_ifnlt_lb: case WOP_ifle_lb: case WOP_ifnle_lb: case WOP_ifgt_lb: case WOP_ifngt_lb: case WOP_ifge_lb: case WOP_ifnge_lb: case WOP_ifeq_lb: case WOP_ifne_lb: case WOP_ifstricteq_lb: case WOP_ifstrictne_lb: { uint32_t r1 = (uint32_t)*pc++; uint32_t b1 = (uint32_t)*pc++; int offset = (int)*pc++; buffer << wopAttrs[opcode].name << " " << r1 << " "; formatBits(buffer, b1); buffer << " " << (uint32_t)(off + 4 + offset); break; } #endif // VMCFG_WORDCODE_PEEPHOLE default: switch (wopAttrs[opcode].width) { case 0: { buffer << "UNKNOWN: " << (uint32_t)opcode; break; } default: buffer << wopAttrs[opcode].name; break; case 2: { buffer << wopAttrs[opcode].name << ' ' << (int)*pc++; break; } case 3: { int first = (int)*pc++; int second = (int)*pc++; buffer << wopAttrs[opcode].name << ' ' << first << ' ' << second; break; } } } } #endif // VMCFG_WORDCODE #endif // AVMPLUS_VERBOSE ExceptionHandler* AvmCore::findExceptionHandler(MethodInfo *info, intptr_t pc, Exception *exception) { int32_t ordinal; // unused ExceptionHandler* handler = findExceptionHandlerNoRethrow(info, pc, exception, &ordinal); if (handler) return handler; // We don't have a matching exception. throwException(exception); return NULL;// not reached } ExceptionHandler* AvmCore::findExceptionHandlerNoRethrow(MethodInfo *info, intptr_t pc, Exception *exception, int32_t *ordinal) { // If this exception is an EXIT_EXCEPTION, it cannot // be caught by AS code. Exit immediately. if (exception->flags & Exception::EXIT_EXCEPTION) { *ordinal = -1; return NULL; } // Search the exception table for a catch clause // such that pc is between "from" and "to" and // the thrown atom matches the required type. //[ed] we only call this from methods with catch blocks, when exceptions != NULL AvmAssert(info->abc_exceptions() != NULL); #ifdef VMCFG_WORDCODE // This is hacky and will go away. If the target method was not jitted, use // word_code.exceptions, otherwise use info->exceptions. methods may or may // not be JITted based on memory, configuration, or heuristics. ExceptionHandlerTable* exceptions; if (info->isInterpreted()) exceptions = info->word_code_exceptions(); else exceptions = info->abc_exceptions(); AvmAssert(exceptions != NULL); #else ExceptionHandlerTable* exceptions = info->abc_exceptions(); #endif int exception_count = exceptions->exception_count; Atom atom = exception->atom; for (int i = 0; i < exception_count; i++) { ExceptionHandler* handler = &exceptions->exceptions[i]; // verifier makes sure type is valid, resolves to Traits* if (pc >= handler->from && pc < handler->to && istype(atom, handler->traits)) { #ifdef AVMPLUS_VERBOSE if (isVerbose((uint32_t)~0)) // any verbose flag enabled we emit this... { console << "enter " << info << " catch " << handler->traits << '\n'; } #endif // DEBUGGER *ordinal = i; return handler; } } // We don't have a matching exception. *ordinal = -1; return NULL; } Atom AvmCore::increment_number_d(Atom a, int delta) { // Argument must be a number atom. Float and Float4 are not legal. AvmAssert(isNumber(a)); AvmAssert(delta == 1 || delta == -1); if (atomIsIntptr(a)) { // If the value is small enough for kIntptrType representation, // the result of an intptr_t addition is guaranteed not to overflow. intptr_t result = atomGetIntptr(a) + delta; if (atomIsValidIntptrValue(result)) return atomFromIntptrValue(result); else return doubleToAtom(double(result)); } else return doubleToAtom(atomToDouble(a) + delta); } // The result is always in the int32 range, wrapping if needed. Atom AvmCore::increment_i(Atom a, int delta) { switch (atomKind(a)) { case kBooleanType: return intToAtom(int32_t((intptr_t)a >> 3) + int32_t(delta)); case kIntptrType: return intToAtom(int32_t(atomGetIntptr(a)) + int32_t(delta)); case kDoubleType: return intToAtom(int32_t(atomToDouble(a)) + int32_t(delta)); #ifdef VMCFG_FLOAT case kSpecialBibopType: if (isFloat(a)) return intToAtom(int32_t(atomToFloat(a)) + int32_t(delta)); // fall thru for the other cases of bibopType #endif default: return intToAtom(integer(a) + int32_t(delta)); } } // check for the easy cases with bitmasks /*static*/ const int AvmCore::k_atomDoesNotNeedCoerce_Masks[8] = { (1<builtinType; // conceptually, this is atomDoesNotNeedCoerce() inlined, so we don't have to // re-grab bt and kind // cheat and use "kUnusedAtomTag" for all null values (streamlines the test) AvmAssert(atomKind(atom) != kUnusedAtomTag); const int kind = isNull(atom) ? kUnusedAtomTag : atomKind(atom); if ((1<traits()->subtypeof(itraits); } if (kind == kIntptrType) { // ISSUE need special support for number value ranges if (bt == BUILTIN_uint) { return atomCanBeUint32(atom); } if (bt == BUILTIN_int) { return atomCanBeInt32(atom); } } if (kind == kDoubleType) { // ISSUE there must be a better way... if (bt == BUILTIN_int) { const double d = atomToDouble(atom); const int32_t i = int32_t(d); return d == (double)i; } if (bt == BUILTIN_uint) { const double d = atomToDouble(atom); const uint32_t u = (uint32_t)d; return d == (double)u; } } #ifdef VMCFG_FLOAT if(kind == kSpecialBibopType){ if(atom==undefinedAtom) { AvmAssert(bt!=BUILTIN_void); return false; } uint8_t bk = bibopKind(atom); if( bk == kBibopFloatType ) return ( bt == BUILTIN_float || bt==BUILTIN_object ) ; AvmAssert( bk == kBibopFloat4Type ); return ( bt == BUILTIN_float4 || bt==BUILTIN_object ) ; } #endif return false; } Stringp AvmCore::coerce_s(Atom atom) { if (isNullOrUndefined(atom)) return NULL; return string(atom); } Stringp AvmCore::string(Atom atom) { if (!isNull(atom)) { switch (atomKind(atom)) { case kNamespaceType: return atomToNamespace(atom)->getURI(); case kObjectType: return atomToScriptObject(atom)->toString(); case kStringType: return atomToString(atom); case kSpecialBibopType: #ifdef VMCFG_FLOAT if(atom == undefinedAtom) return kundefined; if(isFloat(atom)) return floatToString(atomToFloat(atom)); else { AvmAssert(isFloat4(atom)); return float4ToString(atomToFloat4(atom)); } #else return kundefined; #endif // VMCFG_FLOAT case kBooleanType: return booleanStrings[atom>>3]; case kIntptrType: { return MathUtils::convertIntegerToStringRadix(this, atomGetIntptr(atom), 10, MathUtils::kTreatAsSigned); } case kDoubleType: default: // number return doubleToString(atomToDouble(atom)); } } else { return knull; } } void AvmCore::setConsoleStream(GCOutputStream *stream) { console.setOutputStream(stream); } void AvmCore::setConsoleStream(NonGCOutputStream *stream) { console.setOutputStream(stream); } /*static*/ bool AvmCore::isBuiltinType(Atom atm, BuiltinType bt) { return isObject(atm) && Traits::getBuiltinType(atomToScriptObject(atm)->traits()) == bt; } /*static*/ bool AvmCore::isBuiltinTypeMask(Atom atm, int btmask) { return isObject(atm) && ((1<traits())) & btmask) != 0; } /*static*/ bool AvmCore::isDictionary(Atom atm) { return isObject(atm) && atomToScriptObject(atm)->vtable->traits->isDictionary(); } // Tables are from http://www.w3.org/TR/2004/REC-xml-20040204/#NT-NameChar // E4X 13.1.2.1, pg 63 /* BaseChar = */ static const wchar letterTable[] = { 0x0041, 0x005A, 0x0061, 0x007A, 0x00C0, 0x00D6, 0x00D8, 0x00F6, 0x00F8, 0x00FF, 0x0100, 0x0131, 0x0134, 0x013E, 0x0141, 0x0148, 0x014A, 0x017E, 0x0180, 0x01C3, 0x01CD, 0x01F0, 0x01F4, 0x01F5, 0x01FA, 0x0217, 0x0250, 0x02A8, 0x02BB, 0x02C1, 0x0386, 0x0386, // single 0x0388, 0x038A, 0x038C, 0x038C, // single 0x038E, 0x03A1, 0x03A3, 0x03CE, 0x03D0, 0x03D6, 0x03DA, 0x03DA, // single 0x03DC, 0x03DC, // single 0x03DE, 0x03DE, // single 0x03E0, 0x03E0, // single 0x03E2, 0x03F3, 0x0401, 0x040C, 0x040E, 0x044F, 0x0451, 0x045C, 0x045E, 0x0481, 0x0490, 0x04C4, 0x04C7, 0x04C8, 0x04CB, 0x04CC, 0x04D0, 0x04EB, 0x04EE, 0x04F5, 0x04F8, 0x04F9, 0x0531, 0x0556, 0x0559, 0x0559, // single 0x0561, 0x0586, 0x05D0, 0x05EA, 0x05F0, 0x05F2, 0x0621, 0x063A, 0x0641, 0x064A, 0x0671, 0x06B7, 0x06BA, 0x06BE, 0x06C0, 0x06CE, 0x06D0, 0x06D3, 0x06D5, 0x06D5, // single 0x06E5, 0x06E6, 0x0905, 0x0939, 0x093D, 0x093D, // single 0x0958, 0x0961, 0x0985, 0x098C, 0x098F, 0x0990, 0x0993, 0x09A8, 0x09AA, 0x09B0, 0x09B2, 0x09B2, // single 0x09B6, 0x09B9, 0x09DC, 0x09DD, 0x09DF, 0x09E1, 0x09F0, 0x09F1, 0x0A05, 0x0A0A, 0x0A0F, 0x0A10, 0x0A13, 0x0A28, 0x0A2A, 0x0A30, 0x0A32, 0x0A33, 0x0A35, 0x0A36, 0x0A38, 0x0A39, 0x0A59, 0x0A5C, 0x0A5E, 0x0A5E, // single 0x0A72, 0x0A74, 0x0A85, 0x0A8B, 0x0A8D, 0x0A8D, // single 0x0A8F, 0x0A91, 0x0A93, 0x0AA8, 0x0AAA, 0x0AB0, 0x0AB2, 0x0AB3, 0x0AB5, 0x0AB9, 0x0ABD, 0x0ABD, // single 0x0AE0, 0x0AE0, // single 0x0B05, 0x0B0C, 0x0B0F, 0x0B10, 0x0B13, 0x0B28, 0x0B2A, 0x0B30, 0x0B32, 0x0B33, 0x0B36, 0x0B39, 0x0B3D, 0x0B3D, // single 0x0B5C, 0x0B5D, 0x0B5F, 0x0B61, 0x0B85, 0x0B8A, 0x0B8E, 0x0B90, 0x0B92, 0x0B95, 0x0B99, 0x0B9A, 0x0B9C, 0x0B9C, // single 0x0B9E, 0x0B9F, 0x0BA3, 0x0BA4, 0x0BA8, 0x0BAA, 0x0BAE, 0x0BB5, 0x0BB7, 0x0BB9, 0x0C05, 0x0C0C, 0x0C0E, 0x0C10, 0x0C12, 0x0C28, 0x0C2A, 0x0C33, 0x0C35, 0x0C39, 0x0C60, 0x0C61, 0x0C85, 0x0C8C, 0x0C8E, 0x0C90, 0x0C92, 0x0CA8, 0x0CAA, 0x0CB3, 0x0CB5, 0x0CB9, 0x0CDE, 0x0CDE, // single 0x0CE0, 0x0CE1, 0x0D05, 0x0D0C, 0x0D0E, 0x0D10, 0x0D12, 0x0D28, 0x0D2A, 0x0D39, 0x0D60, 0x0D61, 0x0E01, 0x0E2E, 0x0E30, 0x0E30, //single 0x0E32, 0x0E33, 0x0E40, 0x0E45, 0x0E81, 0x0E82, 0x0E84, 0x0E84, // single 0x0E87, 0x0E88, 0x0E8A, 0x0E8A, // single 0x0E8D, 0x0E8D, // single 0x0E94, 0x0E97, 0x0E99, 0x0E9F, 0x0EA1, 0x0EA3, 0x0EA5, 0x0EA5, // single 0x0EA7, 0x0EA7, // single 0x0EAA, 0x0EAB, 0x0EAD, 0x0EAE, 0x0EB0, 0x0EB0, // single 0x0EB2, 0x0EB3, 0x0EBD, 0x0EBD, // single 0x0EC0, 0x0EC4, 0x0F40, 0x0F47, 0x0F49, 0x0F69, 0x10A0, 0x10C5, 0x10D0, 0x10F6, 0x1100, 0x1100, // single 0x1102, 0x1103, 0x1105, 0x1107, 0x1109, 0x1109, // single 0x110B, 0x110C, 0x110E, 0x1112, 0x113C, 0x113C, // single 0x113E, 0x113E, // single 0x1140, 0x1140, // single 0x114C, 0x114C, // single 0x114E, 0x114E, // single 0x1150, 0x1150, // single 0x1154, 0x1155, 0x1159, 0x1159, // single 0x115F, 0x1161, 0x1163, 0x1163, // single 0x1165, 0x1165, // single 0x1167, 0x1167, // single 0x1169, 0x1169, // single 0x116D, 0x116E, 0x1172, 0x1173, 0x1175, 0x1175, // single 0x119E, 0x119E, // single 0x11A8, 0x11A8, // single 0x11AB, 0x11AB, // single 0x11AE, 0x11AF, 0x11B7, 0x11B8, 0x11BA, 0x11BA, // single 0x11BC, 0x11C2, 0x11EB, 0x11EB, // single 0x11F0, 0x11F0, // single 0x11F9, 0x11F9, // single 0x1E00, 0x1E9B, 0x1EA0, 0x1EF9, 0x1F00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, // single 0x1F5B, 0x1F5B, // single 0x1F5D, 0x1F5D, // single 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, // single 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2126, 0x2126, // single 0x212A, 0x212B, 0x212E, 0x212E, // single 0x2180, 0x2182, 0x3041, 0x3094, 0x30A1, 0x30FA, 0x3105, 0x312C, 0xAC00, 0xD7A3, //[86] Ideographic ::= 0x4E00, 0x9FA5, 0x3007, 0x3007, // single 0x3021, 0x3029 }; /*static*/ bool AvmCore::isLetter(wchar c) { int x = sizeof(letterTable) / (sizeof(wchar)); for (int i = 0; i < x; i += 2) { if (c >= letterTable[i] && c <= letterTable[i+1]) return true; } return false; } //[87] CombiningChar ::= static const wchar combiningCharTable[] = { 0x0300, 0x0345, 0x0360, 0x0361, 0x0483, 0x0486, 0x0591, 0x05A1, 0x05A3, 0x05B9, 0x05BB, 0x05BD, 0x05BF, 0x05BF, // single 0x05C1, 0x05C2, 0x05C4, 0x05C4, // single 0x064B, 0x0652, 0x0670, 0x0670, // single 0x06D6, 0x06DC, 0x06DD, 0x06DF, 0x06E0, 0x06E4, 0x06E7, 0x06E8, 0x06EA, 0x06ED, 0x0901, 0x0903, 0x093C, 0x093C, // single 0x093E, 0x094C, 0x094D, 0x094D, // single 0x0951, 0x0954, 0x0962, 0x0963, 0x0981, 0x0983, 0x09BC, 0x09BC, // single 0x09BE, 0x09BE, // single 0x09BF, 0x09BF, // single 0x09C0, 0x09C4, 0x09C7, 0x09C8, 0x09CB, 0x09CD, 0x09D7, 0x09D7, // single 0x09E2, 0x09E3, 0x0A02, 0x0A02, // single 0x0A3C, 0x0A3C, // single 0x0A3E, 0x0A3E, // single 0x0A3F, 0x0A3F, // single 0x0A40, 0x0A42, 0x0A47, 0x0A48, 0x0A4B, 0x0A4D, 0x0A70, 0x0A71, 0x0A81, 0x0A83, 0x0ABC, 0x0ABC, // single 0x0ABE, 0x0AC5, 0x0AC7, 0x0AC9, 0x0ACB, 0x0ACD, 0x0B01, 0x0B03, 0x0B3C, 0x0B3C, // single 0x0B3E, 0x0B43, 0x0B47, 0x0B48, 0x0B4B, 0x0B4D, 0x0B56, 0x0B57, 0x0B82, 0x0B83, 0x0BBE, 0x0BC2, 0x0BC6, 0x0BC8, 0x0BCA, 0x0BCD, 0x0BD7, 0x0BD7, // single 0x0C01, 0x0C03, 0x0C3E, 0x0C44, 0x0C46, 0x0C48, 0x0C4A, 0x0C4D, 0x0C55, 0x0C56, 0x0C82, 0x0C83, 0x0CBE, 0x0CC4, 0x0CC6, 0x0CC8, 0x0CCA, 0x0CCD, 0x0CD5, 0x0CD6, 0x0D02, 0x0D03, 0x0D3E, 0x0D43, 0x0D46, 0x0D48, 0x0D4A, 0x0D4D, 0x0D57, 0x0D57, // single 0x0E31, 0x0E31, // single 0x0E34, 0x0E3A, 0x0E47, 0x0E4E, 0x0EB1, 0x0EB1, // single 0x0EB4, 0x0EB9, 0x0EBB, 0x0EBC, 0x0EC8, 0x0ECD, 0x0F18, 0x0F19, 0x0F35, 0x0F35, // single 0x0F37, 0x0F37, // single 0x0F39, 0x0F39, // single 0x0F3E, 0x0F3E, // single 0x0F3F, 0x0F3F, // single 0x0F71, 0x0F84, 0x0F86, 0x0F8B, 0x0F90, 0x0F95, 0x0F97, 0x0F97, // single 0x0F99, 0x0FAD, 0x0FB1, 0x0FB7, 0x0FB9, 0x0FB9, // single 0x20D0, 0x20DC, 0x20E1, 0x20E1, // single 0x302A, 0x302F, 0x3099, 0x3099, // single 0x309A, 0x309A // single }; /*static*/ bool AvmCore::isCombiningChar(wchar c) { int x = sizeof(combiningCharTable) / (sizeof(wchar)); for (int i = 0; i < x; i += 2) { if (c >= combiningCharTable[i] && c <= combiningCharTable[i+1]) return true; } return false; } //[88] Digit ::= static const wchar digitTable[] = { 0x0030, 0x0039, 0x0660, 0x0669, 0x06F0, 0x06F9, 0x0966, 0x096F, 0x09E6, 0x09EF, 0x0A66, 0x0A6F, 0x0AE6, 0x0AEF, 0x0B66, 0x0B6F, 0x0BE7, 0x0BEF, 0x0C66, 0x0C6F, 0x0CE6, 0x0CEF, 0x0D66, 0x0D6F, 0x0E50, 0x0E59, 0x0ED0, 0x0ED9, 0x0F20, 0x0F29}; /*static*/ bool AvmCore::isDigit(wchar c) { int x = sizeof(digitTable) / (sizeof(wchar)); for (int i = 0; i < x; i += 2) { if (c >= digitTable[i] && c <= digitTable[i+1]) return true; } return false; } static const wchar extenderTable[] = { 0x00B7, 0x00B7, // single 0x02D0, 0x02D0, // single 0x02D1, 0x02D1, // single 0x0387, 0x0387, // single 0x0640, 0x0640, // single 0x0E46, 0x0E46, // single 0x0EC6, 0x0EC6, // single 0x3005, 0x3005, // single 0x3031, 0x3035, 0x309D, 0x309E, 0x30FC, 0x30FE}; /*static*/ bool AvmCore::isExtender(wchar c) { int x = sizeof(extenderTable) / (sizeof(wchar)); for (int i = 0; i < x; i += 2) { if (c >= extenderTable[i] && c <= extenderTable[i+1]) return true; } return false; } bool AvmCore::isXMLName(Atom arg) { if (isNullOrUndefined(arg)) return false; StringIndexer p(string(arg)); // http://www.w3.org/TR/2004/REC-xml-20040204/#NT-NameChar // Name is (Letter | _ or :) followed by arbitrary number of NameChar if (!p->length()) return false; // According to the Mozilla testcase... // e4x excludes ':' wchar c = p[0]; if (!isLetter(c) && c != '_' /*&& c != ':'*/) return false; for (int i = 1; i < p->length(); i++) { wchar c = p[i]; if (isDigit(c) || isLetter(c) || (c == '.') || (c == '-') || (c == '_') || /*(c != ':') ||*/ isCombiningChar (c) || isExtender(c)) continue; return false; } return true; } Stringp AvmCore::ToXMLString (Atom a) { if (!isNull(a)) { switch (atomKind(a)) { case kStringType: return EscapeElementValue (string(a), true); case kObjectType: case kNamespaceType: if (isXML(a)) { XMLObject *x = atomToXMLObject (a); return x->toXMLString(); } else if (isXMLList(a)) { XMLListObject *x = atomToXMLList (a); return x->toXMLString(); } else { // !!@ to primitive (hint string first) // !!@ namespace case falls into this as well return EscapeElementValue (string(a), true); } break; case kSpecialBibopType: if(a != undefinedAtom) return string(a); return kundefined; case kIntptrType: case kBooleanType: case kDoubleType: default: return string(a); } } else { return knull; } } Stringp AvmCore::EscapeElementValue(const Stringp _s, bool removeLeadingTrailingWhitespace) { StringIndexer s(_s); StringBuffer output(this); int i = 0; int last = s->length() - 1; if (removeLeadingTrailingWhitespace) { // finding trailing whitespace while (last >= 0) { if (!String::isSpace(s[last])) break; last--; } if (last < 0) return kEmptyString; // skip leading whitespace for (i = 0; i <= last; i++) { if (!String::isSpace(s[i])) break; } } while (i <= last) { switch (s[i]) { case '<': output << "<"; break; case '>': output << ">"; break; case '&': output << "&"; break; case 0x0000: output << "�"; // extension of ECMA-357 break; default: output << (s[i]); } i++; } return newStringUTF8(output.c_str(), output.length()); } Stringp AvmCore::EscapeAttributeValue(Atom v) { StringBuffer output(this); StringIndexer s(string(v)); for (int i = 0; i < s->length(); i++) { switch (s[i]) { case '"': output << """; break; case '<': output << "<"; break; case '&': output << "&"; break; case 0x000a: output << " "; break; case 0x000d: output << " "; break; case 0x0009: output << " "; break; case 0x0000: output << "�"; // extension of ECMA-357 break; default: output << (s[i]); } } return newStringUTF8(output.c_str(), output.length()); } /*static*/ XMLObject* AvmCore::atomToXMLObject(Atom atm) { return isXML(atm) ? (XMLObject*)(atomToScriptObject(atm)) : NULL; } /*static*/ E4XNode* AvmCore::atomToXML(Atom atm) { return isXML(atm) ? ((XMLObject*)(atomToScriptObject(atm)))->getNode() : NULL; } /*static*/ XMLListObject* AvmCore::atomToXMLList(Atom atm) { return isXMLList(atm) ? (XMLListObject*)(atomToScriptObject(atm)) : NULL; } /*static*/ QNameObject* AvmCore::atomToQName(Atom atm) { return isQName(atm) ? (QNameObject*)(atomToScriptObject(atm)) : NULL; } Stringp AvmCore::_typeof (Atom arg) { if (!isNull(arg)) { switch (atomKind(arg)) { default: case kObjectType: if (isXMLorXMLList(arg)) { return kxml; } else if (isFunction(arg)) { return kfunction; // No special type code for functions, but we need to // special case to return 'function' here. } // else fall thru and return kobject case kNamespaceType: return kobject; case kBooleanType: return kboolean; case kSpecialBibopType: #ifdef VMCFG_FLOAT if(arg == undefinedAtom) return kundefined; if (isFloat(arg)) return kfloat; if (isFloat4(arg)) return kfloat4; AvmAssertMsg(false, "Unknown bibop type"); #endif // VMCFG_FLOAT return kundefined; case kIntptrType: case kDoubleType: return knumber; case kStringType: return kstring; } } else { // typeof(null) = "object" return kobject; } } const BugCompatibility* AvmCore::createBugCompatibility(BugCompatibility::Version v) { return new (GetGC()) BugCompatibility(v); } void AvmCore::addLivePool(PoolObject* pool) { // Ordering is significant (Bugzilla 574427): GetWeakRef can trigger a collection, // and AvmCore::presweep may update the livePools list, specifically it may delete // the first element of that list. Thus we should not touch that value until after // we've gotten the weak ref. LivePoolNode* node = new LivePoolNode(GetGC()); node->pool = pool->GetWeakRef(); node->next = livePools; livePools = node; } void AvmCore::presweep() { LivePoolNode** prev = &livePools; LivePoolNode* node = livePools; while (node) { PoolObject* pool = (PoolObject*)(void*)(node->pool->peek()); if (pool) { // pool is still alive -- if about to get collected, dynamicize the strings // and remove it from the active list if (!GetGC()->GetMark(pool)) { pool->dynamicizeStrings(); *prev = node->next; delete node; node = *prev; continue; } } else { // pool is dead -- should have already removed it? AvmAssert(0); } prev = &node->next; node = node->next; } // clear out the string table { for (int i=0, n=numStrings; i < n; i++) { if (strings[i] > AVMPLUS_STRING_DELETED && !GetGC()->GetMark(strings[i])) { strings[i] = AVMPLUS_STRING_DELETED; deletedCount++; stringCount--; } } } // do the same for the namespaces { bool rehashFlag = false; for (int i=0, n=numNamespaces; i < n; i++) { if (namespaces[i] != NULL && !GetGC()->GetMark(namespaces[i])) { rehashFlag = true; namespaces[i] = NULL; nsCount--; } } // if any interned strings were freed, rehash the intern table // todo - make this less aggressive if (rehashFlag) rehashNamespacesIfPossible(numNamespaces); } // Clear out the regex compile cache, just to prevent big strings or regexes from // hanging around. m_regexCache.clear(); #ifdef DEBUGGER if (_sampler) _sampler->presweep(); #endif } void AvmCore::postsweep() { #ifdef VMCFG_NANOJIT if (m_flushBindingCachesNextSweep) { for (LivePoolNode* node = livePools; node != NULL; node = node->next) { // Use 'peek' here because we're in postsweep where 'get' could cause trouble; // we're OK because the reference does not escape. PoolObject* pool = (PoolObject*)(void*)(node->pool->peek()); if (pool && pool->codeMgr) pool->codeMgr->flushBindingCaches(); } m_flushBindingCachesNextSweep = false; } #endif #ifdef DEBUGGER if (_sampler) _sampler->postsweep(); #endif } REALLY_INLINE int AvmCore::numStringsCheckLoadBalance() { int m = numStrings; // 80% load factor if (5*(stringCount+deletedCount+1) > 4*m) { if (2*stringCount > m) // 50% rehashStrings(m = m << 1); else rehashStrings(m); } return m; } int AvmCore::findString(Stringp s) { int m = numStringsCheckLoadBalance(); // compute the hash function int hashCode = s->hashCode(); int bitMask = m - 1; // find the slot to use int i = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; Stringp k; if (!deletedCount) { while ((k=strings[i]) != NULL && !k->equals(s)) { i = (i + (n++)) & bitMask; // quadratic probe } } else { int iFirstDeletedSlot = -1; while ((k=strings[i]) != NULL) { if (k == AVMPLUS_STRING_DELETED) { if (iFirstDeletedSlot < 0) { iFirstDeletedSlot = i; } } else if (k->equals(s)) { break; } i = (i + (n++)) & bitMask; // quadratic probe } if ((k == NULL) && (iFirstDeletedSlot >= 0)) i = iFirstDeletedSlot; } return i; } int AvmCore::findStringLatin1(const char* s, int len) { int m = numStringsCheckLoadBalance(); // compute the hash function int hashCode = String::hashCodeLatin1(s, len); int bitMask = m - 1; // find the slot to use int i = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; Stringp k; if (!deletedCount) { while ((k=strings[i]) != NULL && !k->equalsLatin1(s,len)) { i = (i + (n++)) & bitMask; // quadratic probe } } else { int iFirstDeletedSlot = -1; while ((k=strings[i]) != NULL) { if (k == AVMPLUS_STRING_DELETED) { if (iFirstDeletedSlot < 0) { iFirstDeletedSlot = i; } } else if (k->equalsLatin1(s, len)) { break; } i = (i + (n++)) & bitMask; // quadratic probe } if ((k == NULL) && (iFirstDeletedSlot >= 0)) i = iFirstDeletedSlot; } return i; } int AvmCore::findStringUTF16(const wchar* s, int len) { int m = numStringsCheckLoadBalance(); // compute the hash function int hashCode = String::hashCodeUTF16(s, len); int bitMask = m - 1; // find the slot to use int i = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; Stringp k; if (!deletedCount) { while ((k=strings[i]) != NULL && !k->equalsUTF16(s,len)) { i = (i + (n++)) & bitMask; // quadratic probe } } else { int iFirstDeletedSlot = -1; while ((k=strings[i]) != NULL) { if (k == AVMPLUS_STRING_DELETED) { if (iFirstDeletedSlot < 0) { iFirstDeletedSlot = i; } } else if (k->equalsUTF16(s, len)) { break; } i = (i + (n++)) & bitMask; // quadratic probe } if ((k == NULL) && (iFirstDeletedSlot >= 0)) i = iFirstDeletedSlot; } return i; } int AvmCore::findStringWithIndex(uint32_t value) { int m = numStringsCheckLoadBalance(); // compute the hash function int hashCode = String::hashCodeUInt(value); int bitMask = m - 1; // find the slot to use int i = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; Stringp k; uint32_t recv; if (!deletedCount) { while ((k=strings[i]) != NULL) { if (k->parseIndex(recv) && recv == value) break; i = (i + (n++)) & bitMask; // quadratic probe } } else { int iFirstDeletedSlot = -1; while ((k=strings[i]) != NULL) { if (k == AVMPLUS_STRING_DELETED) { if (iFirstDeletedSlot < 0) { iFirstDeletedSlot = i; } } else if (k->parseIndex(recv) && recv == value) { break; } i = (i + (n++)) & bitMask; // quadratic probe } if ((k == NULL) && (iFirstDeletedSlot >= 0)) i = iFirstDeletedSlot; } return i; } /** * namespace hash search. interned namespaces are as unique as their * uri. We assume uri's are already interned, so interning a namespace * is quick because uri's can be compared quickly. */ int AvmCore::findNamespace(Namespacep ns, bool canRehash) { int m = numNamespaces; // 80% load factor if (canRehash && nsCount*5 >= 4*m) { rehashNamespaces(m = m << 1); } // compute the hash function int hashCode = (int)(((uintptr_t)ns->getURI())>>3); // FIXME possibly hash api mask too int bitMask = m - 1; // find the slot to use int i = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; Namespacep k; while ((k=namespaces[i]) != NULL && (k->m_uriAndType != ns->m_uriAndType || k->getApiVersion() != ns->getApiVersion())) { i = (i + (n++)) & bitMask; // quadratic probe } return i; } Namespacep AvmCore::gotNamespace(uintptr_t uriAndType, ApiVersion apiVersion) { int m = numNamespaces; // compute the hash function int hashCode = (int)(((uintptr_t)uriAndType)>>3); // FIXME possibly hash api mask too int bitMask = m - 1; // find the slot to use int i = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; Namespacep k; while ((k=namespaces[i]) != NULL && (k->m_uriAndType != uriAndType || k->getApiVersion() != apiVersion)) { i = (i + (n++)) & bitMask; // quadratic probe } return namespaces[i]; } // note, this assumes Latin-1, not UTF8. Stringp AvmCore::internStringLatin1(const char* s, int len) { if (len < 0) len = String::Length(s); int i = findStringLatin1(s, len); Stringp other; if ((other=strings[i]) <= AVMPLUS_STRING_DELETED) { if (other == AVMPLUS_STRING_DELETED) { deletedCount--; AvmAssert(deletedCount >= 0); } #ifdef DEBUGGER GCRoot::GCMember *oldStrings = strings; #endif other = newStringLatin1(s, len); #ifdef DEBUGGER // re-find if String ctor caused rehash if(strings != oldStrings) i = findStringLatin1(s, len); #endif strings[i] = other; stringCount++; other->setInterned(); } return other; } Stringp AvmCore::internSubstring(Stringp s, int32_t start, int32_t end) { // this is (currently) only called by XMLParser, which only passes us // valid values -- skip the checks in the name of performance. AvmAssert(start < end); AvmAssert(start >= 0 && start <= s->length()); AvmAssert(end >= 0 && end <= s->length()); if (start == 0 && end == s->length()) return internString(s); int32_t const len = end - start; String::Width const w = s->getWidth(); int i; { // enclose this in braces to ensure we don't use Pointers // across an allocation (eg, substring()) String::Pointers ptrs(s); i = (w == String::k8) ? findStringLatin1((const char*)ptrs.p8 + start, len) : findStringUTF16(ptrs.p16 + start, len); } Stringp other; if ((other=strings[i]) <= AVMPLUS_STRING_DELETED) { if (other == AVMPLUS_STRING_DELETED) { deletedCount--; AvmAssert(deletedCount >= 0); } #ifdef DEBUGGER GCRoot::GCMember* oldStrings = strings; #endif other = s->substring(start, end); /* substring() attempts to make a dependent string when possible; normally this is highly desirable, but for interned strings, we must be cautious: we usually call this function from XML parsing, and it would be bad if we forced a giant string of XML to be held in memory because of one tiny dependent string in the intern table. The simplest fix is to call fixDependentString(): if the string is dependent, it's converted to dynamic (unless the length of the dependent string is very close to the size of the master string, in which case there's no memory to be saved by doing the conversion). */ other->fixDependentString(); #ifdef DEBUGGER // re-find if String ctor caused rehash if (strings != oldStrings) { String::Pointers ptrs(s); i = (w == String::k8) ? findStringLatin1((const char*)ptrs.p8 + start, len) : findStringUTF16(ptrs.p16 + start, len); } #endif strings[i] = other; stringCount++; other->setInterned(); } return other; } // note, this assumes Latin-1, not UTF8. Stringp AvmCore::internConstantStringLatin1(const char* s) { return internString(newConstantStringLatin1(s)); } /** * intern the given string atom which has already been allocated * @param atom * @return */ Stringp AvmCore::internString(Stringp o) { if (o->isInterned()) return o; int i = findString(o); Stringp other; if ((other=strings[i]) <= AVMPLUS_STRING_DELETED) { if (other == AVMPLUS_STRING_DELETED) { deletedCount--; AvmAssert(deletedCount >= 0); } stringCount++; // Prevent dependent strings from keeping a huge master in memory o->fixDependentString(); o->setInterned(); strings[i] = o; return o; } else { return other; } } // Identical to internString plus clone minus the isInterned() check - for the receiver // it doesn't matter if the string is interned or not. Stringp AvmCore::internForeignString(Stringp o) { // this can go in parallel int i = findString(o); Stringp other; if ((other=strings[i]) <= AVMPLUS_STRING_DELETED) { if (other == AVMPLUS_STRING_DELETED) { deletedCount--; AvmAssert(deletedCount >= 0); } stringCount++; // Make copy now, an interned string doesn't exist yet o = o->clone(this); // Prevent dependent strings from keeping a huge master in memory o->fixDependentString(); o->setInterned(); strings[i] = o; return o; } else { return other; } } Stringp AvmCore::internString(Atom atom) { AvmAssert(isString(atom)); Stringp s = atomToString(atom); return s->isInterned() ? s : internString(s); } Stringp AvmCore::internInt(int value) { // todo, bug 561092: avoid string buffer conversion by // calculating hashcode from int value directly. // // below: manual inlining and specialization of // MathUtils::convertIntegerToStringBase10. char buffer[MathUtils::kMinSizeForInt32_t_base10_toString]; int32_t len = MathUtils::kMinSizeForInt32_t_base10_toString; intptr_t wideVal = (intptr_t)value; MathUtils::UnsignedTreatment treatAs = MathUtils::kTreatAsSigned; char *p = MathUtils::convertIntegerToStringBuffer(wideVal, buffer, len, 10, treatAs); return internStringLatin1( p, len ); } Stringp AvmCore::internUint32 (uint32_t ui) { if (ui & 0x80000000) return internDouble(ui); else return internInt((int)ui); } bool AvmCore::isInternedUint(uint32_t ui, Stringp *result) { int iSlotForIndex; if (ui & 0x80000000) { // FIXME bug 561092: is there a more direct route to slot here? Stringp s = MathUtils::convertDoubleToString(this, double(ui)); iSlotForIndex = findString(s); } else { iSlotForIndex = findStringWithIndex(ui); } Stringp other = strings[iSlotForIndex]; if (other <= AVMPLUS_STRING_DELETED) { return false; } else { *result = other; return true; } } #ifdef VMCFG_FLOAT Stringp AvmCore::internFloat(float f) { // we call internDouble, because according to the spec, // we must match the behaviour of Number. return internDouble(f); } Stringp AvmCore::internFloat4(const float4_t& f4) { return internString(float4ToString(f4)); } #endif // VMCFG_FLOAT Stringp AvmCore::internDouble(double d) { return internString(MathUtils::convertDoubleToString(this, d)); } #ifdef DEBUGGER Stringp AvmCore::findInternedString(const char *cs, int len8) { // NOTE: this works in strict UTF-8 conversion mode int32_t len16 = UnicodeUtils::Utf8ToUtf16((const uint8_t*)cs, len8, NULL, 0, true); AvmAssertMsg(len16 >= 0, "Malformed UTF-8 sequence"); // use alloca to avoid heap allocations where possible MMgc::GC::AllocaAutoPtr _buffer; wchar *buffer = (wchar*) avmStackAllocArray(this, _buffer, (len16+1), sizeof(wchar)); if(!buffer) { AvmAssertMsg(false, "alloca failed!"); return NULL; } UnicodeUtils::Utf8ToUtf16((const uint8_t*)cs, len8, buffer, len16, true); buffer[len16] = 0; int i = findStringUTF16(buffer, len16); Stringp other; if ((other=strings[i]) > AVMPLUS_STRING_DELETED) { return other; } return NULL; } #endif Stringp AvmCore::internStringUTF8(const char* cs, int len8, bool constant, bool strict) { Stringp s = String::createUTF8(this, (const utf8_t*)cs, len8, String::kAuto, constant, strict); // createUTF8() will return NULL if we pass it invalid UTF8 data and its "strict" arg is true // (which it is by default) -- check and bail if that happens. if (!s) return NULL; int i = findString(s); Stringp other; if ((other=strings[i]) <= AVMPLUS_STRING_DELETED) { if (other == AVMPLUS_STRING_DELETED) { deletedCount--; AvmAssert(deletedCount >= 0); } s->setInterned(); stringCount++; strings[i] = s; return s; } else return other; } Stringp AvmCore::internStringUTF16(const wchar* s, int len) { if (len < 0) len = String::Length(s); int i = findStringUTF16(s, len); Stringp other; if ((other=strings[i]) <= AVMPLUS_STRING_DELETED) { if (other == AVMPLUS_STRING_DELETED) { deletedCount--; AvmAssert(deletedCount >= 0); } #ifdef DEBUGGER GCRoot::GCMember *oldStrings = strings; #endif other = newStringUTF16(s, len); #ifdef DEBUGGER // re-find if String ctor caused rehash if(strings != oldStrings) i = findStringUTF16(s, len); #endif strings[i] = other; stringCount++; other->setInterned(); } return other; } void AvmCore::rehashStrings(int newlen) { // rehash GCRoot::GCMember *oldStrings = strings; strings = mmfx_new_array(GCRoot::GCMember, newlen); int oldStringCount = numStrings; numStrings = newlen; #ifdef _DEBUG // debug sanity checks int oldDeletedCount = deletedCount; int computedDeleteCount = 0; int computedStringCount = 0; #endif deletedCount = 0; // Inlined and optimized our findString routine. We know that there // are no duplicated strings in our intern string table so we don't // need to call Equals. All we need to do is find the first blank // spot available. int m = numStrings; int bitMask = m - 1; for (int i=0; i < oldStringCount; i++) { Stringp o = oldStrings[i]; if (o > AVMPLUS_STRING_DELETED) { // compute the hash function int hashCode = (int) o->hashCode(); // find the slot to use int j = (hashCode&0x7FFFFFFF) & bitMask; int n = 7; while (strings[j] != NULL) { j = (j + (n++)) & bitMask; // quadratic probe } strings[j] = o; #ifdef _DEBUG computedStringCount++; #endif } #ifdef _DEBUG else if (o == AVMPLUS_STRING_DELETED) { computedDeleteCount++; } #endif } #ifdef _DEBUG AvmAssert(computedStringCount == stringCount); AvmAssert(oldDeletedCount == computedDeleteCount); #endif // Clear oldStrings so it can be collected. mmfx_delete_array(oldStrings); } void AvmCore::rehashNamespacesIfPossible(int newlen) { rehashNamespaces(newlen, true); } void AvmCore::rehashNamespaces(int newlen, bool canFail/*=false*/) { // rehash GCRoot::GCMember *old = namespaces; int oldCount = numNamespaces; if (canFail) { AvmAssert(newlen <= oldCount); // failure senseless when growing. // alloc failure when shrinking ==> can recover without OOM Abort. namespaces = mmfx_new_array_opt(GCRoot::GCMember, newlen, kCanFail); if (namespaces == NULL) { namespaces = old; return; } } else { namespaces = mmfx_new_array(GCRoot::GCMember, newlen); } numNamespaces = newlen; for (int i=0; i < oldCount; i++) { Namespacep o = old[i]; if (o != NULL) namespaces[findNamespace(o, /*canRehash = */ false)] = o; } // Clear old namespaces table so it can be collected. mmfx_delete_array(old); } ScriptBufferImpl* AvmCore::newScriptBuffer(size_t size) { return new (GetGC(), size) BasicScriptBufferImpl(size); } VTable* AvmCore::newVTable(Traits* traits, VTable* base, Toplevel* toplevel) { const uint32_t count = traits->getTraitsBindings()->methodCount; size_t extraSize = sizeof(MethodEnv*)*(count > 0 ? count-1 : 0); return new (GetGC(), MMgc::kExact, extraSize) VTable(traits, base, toplevel); } RegExpObject* AvmCore::newRegExp(RegExpClass* regexpClass, Stringp pattern, Stringp options) { return RegExpObject::create(GetGC(), regexpClass->ivtable(), regexpClass->prototypePtr(), pattern, options); } Namespacep AvmCore::newNamespace(Atom prefix, Atom uri, Namespace::NamespaceType type) { // E4X - this is 13.2.3, step 3 - prefix IS specified Atom p; Stringp u; if (isQName(uri) && !isNull(atomToQName(uri)->getURI())) { u = atomToString(atomToQName(uri)->getURI()); } else { u = internString(string(uri)); } if (u->isEmpty()) { if (prefix == undefinedAtom) p = kEmptyString->atom(); else if (!string (prefix)->length()) p = kEmptyString->atom(); else { // !!@ throw correct type error //typeErrorClass()->throwError(kConvertUndefinedToObjectError); return NULL; } } else if (prefix == undefinedAtom) { p = undefinedAtom; } else if (prefix != kEmptyString->atom() && !isXMLName (prefix)) { p = undefinedAtom; } else { p = internString(string(prefix))->atom(); } // called from AS so need to get the api version off the call stack ApiVersion const apiVersion = this->getApiVersionFromCallStack(); AvmAssert(isValidApiVersion(apiVersion)); Namespacep ns = Namespace::create(GetGC(), p, u, type, apiVersion); return ns; } Namespacep AvmCore::newNamespace(Atom uri, Namespace::NamespaceType type) { // prefix and uri must be interned! // E4X - this is 13.2.2, step 3 - "prefix not specified" Atom p; Stringp u; if (isNamespace (uri)) { Namespacep ns = atomToNamespace(uri); p = ns->getPrefix(); u = ns->getURI(); } else if (isObject(uri) && isQName(uri) && !isNull(atomToQName(uri)->getURI())) { p = undefinedAtom; u = atomToString(atomToQName(uri)->getURI()); } else { u = string(uri); p = u->isEmpty() ? kEmptyString->atom() : undefinedAtom; u = internString(u); } // called from AS so need to get the api version off the call stack ApiVersion const apiVersion = this->getApiVersionFromCallStack(); AvmAssert(isValidApiVersion(apiVersion)); Namespacep ns = Namespace::create(GetGC(), p, u, type, apiVersion); return ns; } Namespacep AvmCore::newNamespace(Stringp uri, Namespace::NamespaceType type, ApiVersion apiVersion) { apiVersion = getValidApiVersion(apiVersion); Atom prefix = uri->isEmpty() ? kEmptyString->atom() : undefinedAtom; Namespacep ns = Namespace::create(GetGC(), prefix, uri, type, apiVersion); return ns; } Namespacep AvmCore::newAnyNamespace(Stringp uri, Namespace::NamespaceType type) { // kApiVersion_VM_INTERNAL matches all versions, so that's what we want, // not kApiVersion_VM_ALLVERSIONS, amusingly enough. (But: only for NS_Public // namespaces, since all other forms are always nonversioned and require // ALLVERSIONS at all times.) ApiVersion const apiVersion = (type == Namespace::NS_Public) ? kApiVersion_VM_INTERNAL : kApiVersion_VM_ALLVERSIONS; Atom prefix = uri->isEmpty() ? kEmptyString->atom() : undefinedAtom; Namespacep ns = Namespace::create(GetGC(), prefix, uri, type, apiVersion); return ns; } Namespacep AvmCore::newPublicNamespace(Stringp uri) { ApiVersion const apiVersion = this->getApiVersionFromCallStack(); AvmAssert(isValidApiVersion(apiVersion)); return newNamespace(uri, Namespace::NS_Public, apiVersion); } Atom AvmCore::uintToAtom(uint32_t n) { return (atomIsValidIntptrValue_u(n) ? // always true on 64-bit atomFromIntptrValue_u(n) : allocDouble(n)); } Atom AvmCore::intToAtom(int32_t n) { return (atomIsValidIntptrValue(n) ? // always true on 64-bit atomFromIntptrValue(n) : allocDouble(n)); } REALLY_INLINE bool atomIsValidIntptrValueAndEqualTo(const intptr_t ival, const double orig) { // ival's intptr_t type is critical; implicitly casts from // int32_t in most invocations of this function here. return ((((ival << atomSignExtendShift) >> atomSignExtendShift) == orig) && !((ival == 0) && MathUtils::isNegZero(orig))); } #if defined(AVMPLUS_IA32) || defined(AVMPLUS_AMD64) // ignore warning that inline asm disables global optimization in this function #ifdef _MSC_VER #pragma warning(disable: 4740) #endif Atom AvmCore::doubleToAtom_sse2(double n) { // handle integer values w/out allocation // this logic rounds in the wrong direction for E3, but // we never use a rounded value, only cleanly converted values. #if defined(WIN32) || defined(__ICC) #ifdef AVMPLUS_AMD64 int32_t const intval = _mm_cvttsd_si32(_mm_set_sd(n)); if (atomIsValidIntptrValueAndEqualTo(intval, n)) return atomFromIntptrValue(intval); return allocDouble(n); #else // x86 int id3; __asm { movsd xmm0,n cvttsd2si ecx,xmm0 shl ecx,3 // intval<<3 mov eax,ecx sar ecx,3 // intval>>3 cvtsi2sd xmm1,ecx ucomisd xmm0,xmm1 jne d2a_alloc // < or > jp d2a_alloc // unordered mov id3,eax } if (id3 != 0 || !MathUtils::isNegZero(n)) { return id3 | kIntptrType; } else { __asm d2a_alloc: return allocDouble(n); } #endif #elif defined(_MAC) // MacTel always has SSE2 available int32_t const intval = _mm_cvttsd_si32(_mm_set_sd(n)); if (atomIsValidIntptrValueAndEqualTo(intval, n)) return atomFromIntptrValue(intval); return allocDouble(n); #elif defined(SOLARIS) return AvmCore::doubleToAtom(n); // This needs to be optimized for solaris. #elif defined(AVMPLUS_UNIX) #ifdef AVMPLUS_AMD64 int32_t const intval = _mm_cvttsd_si32(_mm_set_sd(n)); if (atomIsValidIntptrValueAndEqualTo(intval, n)) return atomFromIntptrValue(intval); return allocDouble(n); #else // not AVMPLUS_AMD64 int id3; asm("movups %1, %%xmm0;" "cvttsd2si %%xmm0, %%ecx;" "shl $0x3, %%ecx;" "mov %%ecx, %%eax;" "sar $0x3, %%ecx;" "cvtsi2sd %%ecx, %%xmm1;" "ucomisd %%xmm1, %%xmm0;" "jne d2a_alloc;" "jp d2a_alloc;" "movl %%eax, %0" : "=r" (id3) : "m" (n)); if (id3 != 0 || !MathUtils::isNegZero(n)) { return id3 | kIntptrType; } asm("d2a_alloc:"); return allocDouble(n); #endif // AVMPLUS_AMD64 #endif // defined(AVMPLUS_UNIX) } #endif // x86 or x64 #ifndef AVMPLUS_SSE2_ALWAYS Atom AvmCore::doubleToAtom(double n) { // There is no need for special logic for NaN or +/-Inf since we don't // ever test for those values in coreplayer. As far as we're concerned // they are regular numeric values. // handle integer values w/out allocation #if defined(WIN32) && !defined(_ARM_) #ifdef AVMPLUS_AMD64 int intval = _mm_cvttsd_si32(_mm_set_sd(n)); #else // this logic rounds in the wrong direction for E3, but // we never use a rounded value, only cleanly converted values. int intval; _asm { fld [n]; fistp [intval]; } #endif #elif defined(_MAC) && (defined (AVMPLUS_IA32) || defined(AVMPLUS_AMD64)) int intval = _mm_cvttsd_si32(_mm_set_sd(n)); #else int intval = int32_t(n); #endif // make sure n is integer value that fits in 29 bits if (atomIsValidIntptrValueAndEqualTo(intval, n)) return atomFromIntptrValue(intval); return allocDouble(n); } #endif // not AVMPLUS_SSE2_ALWAYS Stringp AvmCore::newConstantStringLatin1(const char* s) { return String::createLatin1(this, s, String::Length(s), String::k8, true); } Stringp AvmCore::newStringLatin1(const char* s, int len) { return String::createLatin1(this, s, len); } Stringp AvmCore::newStringUTF8(const char* s, int len, bool strict) { return String::createUTF8(this, (const utf8_t*)s, len, String::kDefaultWidth, false, strict); } Stringp AvmCore::newStringUTF16(const wchar* s, int len, bool strict) { return String::createUTF16(this, s, len, String::kDefaultWidth, false, strict); } // "swap16" is apparently reserved/defined in some build environments... inline uint16_t avmSwap16(const uint16_t c) { const uint16_t hi = (c >> 8); const uint16_t lo = (c & 0xff); return (lo << 8) | hi; } Stringp AvmCore::newStringEndianUTF16(bool littleEndian, const wchar* s, int len, bool strict) { #ifdef VMCFG_LITTLE_ENDIAN const bool nativeIsLE = true; #else const bool nativeIsLE = false; #endif if (littleEndian == nativeIsLE) { return newStringUTF16(s, len, strict); } else { if (len < 0 && s != NULL) len = String::Length(s); if (s == NULL || len == 0) return this->kEmptyString; MMgc::GC::AllocaAutoPtr _swapped; wchar* swapped = (wchar*)avmStackAllocArray(this, _swapped, len, sizeof(wchar)); for (int32_t i = 0; i < len; i++) { swapped[i] = avmSwap16(s[i]); } return newStringUTF16(swapped, len, strict); } } Stringp AvmCore::concatStrings(Stringp s1, Stringp s2) { if (!s1) s1 = knull; if (!s2) s2 = knull; return String::concatStrings(s1, s2); } Stringp AvmCore::intToString(int value) { return MathUtils::convertIntegerToStringBase10(this, value, MathUtils::kTreatAsSigned); } Stringp AvmCore::uintToString(uint32_t value) { return MathUtils::convertIntegerToStringBase10(this, value, MathUtils::kTreatAsUnsigned); } Stringp AvmCore::doubleToString(double d) { return MathUtils::convertDoubleToString(this, d, MathUtils::DTOSTR_NORMAL,15); } #ifdef VMCFG_FLOAT Stringp AvmCore::floatToString(float f) { return doubleToString(f); } Stringp AvmCore::float4ToString(const float4_t& f) { float x = f4_x(f), y = f4_y(f), z = f4_z(f), w = f4_w(f); return floatToString(x) ->appendLatin1(",") ->append(floatToString(y)) ->appendLatin1(",") ->append(floatToString(z)) ->appendLatin1(",") ->append(floatToString(w)); } #endif StackTrace* AvmCore::newStackTrace() { if (callStack) { int depth = callStack->depth(); StackTrace* stackTrace = StackTrace::create(GetGC(), depth); CallStackNode *curr = callStack; StackTrace::Element *element = stackTrace->elements; while (curr) { element->set(*curr); element++; curr = curr->next(); } return stackTrace; } else if (currentBugCompatibility()->bugzilla619148) // Stack traces in release builds { int depth = 0; MethodFrame* methodFrame = currentMethodFrame; while (methodFrame != NULL) { if (methodFrame->env() != NULL) depth++; methodFrame = methodFrame->next; } StackTrace* stackTrace = StackTrace::create(GetGC(), depth); StackTrace::Element *element = stackTrace->elements; methodFrame = currentMethodFrame; while (methodFrame != NULL && depth > 0) { if (methodFrame->env() != NULL) { element->set(methodFrame->env()->method); element++; depth--; } methodFrame = methodFrame->next; } return stackTrace; } return NULL; } #ifdef DEBUGGER #ifdef _DEBUG void AvmCore::dumpStackTrace() { StringBuffer buffer(this); buffer << "Stack Trace:\n" << newStackTrace()->format(this) << '\n'; AvmDebugMsg(false, (const char*)buffer.c_str()); } #endif #endif /* DEBUGGER */ /*static*/ int32_t AvmCore::integer(Atom atom) { const int kind = atomKind(atom); if (kind == kIntptrType) { AvmAssert(int32_t(atomGetIntptr(atom)) == (int32_t)integer_d(number(atom))); return int32_t(atomGetIntptr(atom)); } else if (kind == kBooleanType) { return int32_t(atom>>3); } else { // TODO optimize the code below. return (int32_t)integer_d(number(atom)); } } #ifdef VMCFG_FLOAT /*static*/ float AvmCore::singlePrecisionFloat(Atom atom) { const int kind = atomKind(atom); if (kind == kSpecialBibopType ) { if( atom == AtomConstants::undefinedAtom ) return (float) MathUtils::kNaN; if(bibopKind(atom) == kBibopFloatType) return atomToFloat(atom); AvmAssert(bibopKind(atom) == kBibopFloat4Type); return (float) MathUtils::kNaN; } else { // TODO optimize the code below if needed return (float)number(atom); } } /*static*/ void AvmCore::float4(float4_t* retval, Atom atom) { if (isFloat4(atom)) { *retval = atomToFloat4(atom); return; } float f = singlePrecisionFloat(atom); float4_t f4 = {f,f,f,f}; *retval = f4; } #endif // VMCFG_FLOAT #ifndef AVMPLUS_SSE2_ALWAYS /*static*/ int AvmCore::integer_d(double d) { // Try a simple case first to see if we have a in-range float value #if defined(AVMPLUS_IA32) // x86 int32_t(double) returns 0x80000000 if d is not in a valid integer range int intval = int32_t(d); if (intval != (int)0x80000000) return intval; #elif defined(AVMPLUS_MIPS) // mips int32_t(double) returns 0x7fffffff if d is not in a valid integer range int intval = int32_t(d); if (intval != 0x7fffffff) return intval; #elif defined(AVMPLUS_SPARC) || defined(AVMPLUS_ARM) // an int cast which should give: // +/-0.0:0 +/-nan:0 +/-ind:0 -inf:0x80000000 +inf:0x7fffffff den:0 >=0x7fffffff:x7fffffff <=0x80000000:0x80000000 int intval = int32_t(d); if (intval != 0x7fffffff && intval != (int)0x80000000) return intval; #endif return doubleToInt32(d); } #endif // not AVMPLUS_SSE2_ALWAYS #if defined(AVMPLUS_IA32) || defined(AVMPLUS_AMD64) int AvmCore::integer_d_sse2(double d) { int intval; #ifdef WIN32 #ifdef AVMPLUS_AMD64 intval = _mm_cvttsd_si32(_mm_set_sd(d)); if (intval != (int)0x80000000) return intval; #else _asm { cvttsd2si eax,d mov intval,eax } if (intval != 0x80000000) return intval; #endif #elif defined(_MAC) && (defined(AVMPLUS_IA32) || defined(AVMPLUS_AMD64)) intval = _mm_cvttsd_si32(_mm_set_sd(d)); if (intval != (int)0x80000000) return intval; #elif defined(SOLARIS) #elif defined(AVMPLUS_UNIX) asm("movups %1, %%xmm0;" "cvttsd2si %%xmm0, %%eax;" "movl %%eax, %0" : "=r" (intval) : "m" (d) : "%eax"); if (intval != (int) 0x80000000) return intval; #endif return doubleToInt32(d); } #endif // AVMPLUS_IA32 or AVMPLUS_AMD64 #if !defined(AVMPLUS_IA32) && !defined(AVMPLUS_AMD64) int AvmCore::doubleToInt32(double d) { // From the ES3 spec, 9.5 // 2. If Result(1) is NaN, +0, -0, +Inf, or -Inf, return +0. // 3. Compute sign(Result(1)) * floor(abs(Result(1))). // 4. Compute Result(3) modulo 2^32; that is, a finite integer value k of Number // type with positive sign and less than 2^32 in magnitude such the mathematical // difference of Result(3) and k is mathematically an integer multiple of 2^32. // 5. If Result(4) is greater than or equal to 2^31, return Result(4)- 2^32, // otherwise return Result(4). // step 2 if (MathUtils::isNaN(d) || MathUtils::isInfinite(d) || d == 0) { return 0; } // step 3 (round towards 0) double ad = d < 0.0 ? MathUtils::floor(-d) : MathUtils::floor(d); // step 4 if (ad > 4294967295.0) ad = MathUtils::mod(ad,4294967296.0); // ad %= 0x10000000 // step 5 if (ad >= (double)2147483648.0) { // This case is a large negative number that overflows back to a positive // number. This code has been tweaked to work on both Mac and Windows. Mac // is particularly sensitive to edge numbers such as 0x80000000 when converting // doubles to ints). if (d < 0.0) { int intVal = int32_t (ad - 2147483648.0); return 0x80000000 - intVal; } // This case is a large positive number overflowing to negative. else { int intVal = int32_t (ad - 2147483648.0); return 0x80000000 + intVal; } } else { return int32_t(d < 0.0 ? -ad : ad); } } #else // From the ES3 spec, 9.5 // 2. If Result(1) is NaN, +0, -0, +Inf, or -Inf, return +0. // 3. Compute sign(Result(1)) * floor(abs(Result(1))). // 4. Compute Result(3) modulo 2^32; that is, a finite integer value k of Number // type with positive sign and less than 2^32 in magnitude such the mathematical // difference of Result(3) and k is mathematically an integer multiple of 2^32. // 5. If Result(4) is greater than or equal to 2^31, return Result(4)- 2^32, // otherwise return Result(4). #if defined(AVMPLUS_AMD64) #define DBLTOINT32_INT64 1 #else #define DBLTOINT32_INT64 0 #endif typedef union { double d; uint64_t i; #if defined(AVMPLUS_IA32) || defined(AVMPLUS_AMD64) struct { uint32_t il, ih; } i32; #else #error("this routine does not work in PowerPC processors"); struct { uint32_t ih, il; } i32; #endif } double_int; #if DBLTOINT32_INT64 int AvmCore::doubleToInt32(double d) { double_int du, duh, two32; uint64_t sign_d; int64_t MASK; uint32_t DI_H, u_tmp, expon, shift_amount; // Algorithm Outline // Step 1. If d is NaN, +/-Inf or |d|>=2^84 or |d|<1, then return 0 // All of this is implemented based on an exponent comparison. // Step 2. If |d|<2^31, then return (int)d // The cast to integer (conversion in RZ mode) returns the correct result. // Step 3. If |d|>=2^32, d:=fmod(d, 2^32) is taken -- but without a call // Step 4. If |d|>=2^31, then the fractional bits are cleared before // applying the correction by 2^32: d - sign(d)*2^32 // Step 5. Return (int)d du.d = d; DI_H = du.i32.ih; u_tmp = (DI_H & 0x7ff00000) - 0x3ff00000; if(u_tmp >= (0x45300000-0x3ff00000)) { // d is Nan, +/-Inf or +/-0, or |d|>=2^(32+52) or |d|<1, in which case result=0 return 0; } if(u_tmp < 0x01f00000) { // |d|<2^31 return int32_t(d); } if(u_tmp > 0x01f00000) { // |d|>=2^32 expon = u_tmp >> 20; shift_amount = expon - 21; duh.i = du.i; MASK = 0x8000000000000000ll; MASK = MASK >> shift_amount; duh.i &= (uint64_t)MASK; du.d -= duh.d; } DI_H = du.i32.ih; // eliminate fractional bits u_tmp = (DI_H & 0x7ff00000); if(u_tmp >= 0x41e00000) { // |d|>=2^31 expon = u_tmp >> 20; shift_amount = expon - (0x3ff - 11); MASK = 0x8000000000000000ll; MASK = MASK >> shift_amount; du.i &= (uint64_t)MASK; sign_d = du.i & 0x8000000000000000ull; two32.i = 0x41f0000000000000ull ^ sign_d; du.d -= two32.d; } return int32_t(du.d); } #else // DBLTOINT32_INT64 int AvmCore::doubleToInt32(double d) { double_int du, duh, two32; uint32_t DI_H, u_tmp, expon, shift_amount; int32_t mask32; // Algorithm Outline // Step 1. If d is NaN, +/-Inf or |d|>=2^84 or |d|<1, then return 0 // All of this is implemented based on an exponent comparison. // Step 2. If |d|<2^31, then return (int)d // The cast to integer (conversion in RZ mode) returns the correct result. // Step 3. If |d|>=2^32, d:=fmod(d, 2^32) is taken -- but without a call // Step 4. If |d|>=2^31, then the fractional bits are cleared before // applying the correction by 2^32: d - sign(d)*2^32 // Step 5. Return (int)d du.d = d; DI_H = du.i32.ih; u_tmp = (DI_H & 0x7ff00000) - 0x3ff00000; if(u_tmp >= (0x45300000-0x3ff00000)) { // d is Nan, +/-Inf or +/-0, or |d|>=2^(32+52) or |d|<1, in which case result=0 return 0; } if(u_tmp < 0x01f00000) { // |d|<2^31 return int32_t(d); } if(u_tmp > 0x01f00000) { // |d|>=2^32 expon = u_tmp >> 20; shift_amount = expon - 21; duh.i = du.i; mask32 = 0x80000000; if(shift_amount<32) { mask32 >>= shift_amount; duh.i32.ih = du.i32.ih & mask32; duh.i32.il = 0; } else { mask32 >>= (shift_amount-32); duh.i32.ih = du.i32.ih; duh.i32.il = du.i32.il & mask32; } du.d -= duh.d; } DI_H = du.i32.ih; // eliminate fractional bits u_tmp = (DI_H & 0x7ff00000); if(u_tmp >= 0x41e00000) { // |d|>=2^31 expon = u_tmp >> 20; shift_amount = expon - (0x3ff - 11); mask32 = 0x80000000; if(shift_amount<32) { mask32 >>= shift_amount; du.i32.ih &= mask32; du.i32.il = 0; } else { mask32 >>= (shift_amount-32); du.i32.il &= mask32; } two32.i32.ih = 0x41f00000 ^ (du.i32.ih & 0x80000000); two32.i32.il = 0; du.d -= two32.d; } return int32_t(du.d); } #endif // DBLTOINT32_INT64 #endif // !(defined(AVMPLUS_IA32) || defined(AVMPLUS_AMD64)) ApiVersion AvmCore::getApiVersionFromCallStack() { for (MethodFrame* f = currentMethodFrame; f != NULL; f = f->next) { CodeContext* cc = f->cc(); if (cc) { // There is no user code on the stack, but we entered actionscript on behalf of some toplevel, // so use its version. There might be user code below an EnterCodeContext, but it would be // irrelevant because it represents an entirely different entry into the VM. ApiVersion apiVersion = cc->domainEnv()->toplevel()->abcEnv()->pool()->getApiVersion(); AvmAssert(isValidApiVersion(apiVersion)); return apiVersion; } else { MethodEnv* env = f->env(); if (env && !env->method->pool()->isBuiltin) { ApiVersion apiVersion = env->method->pool()->getApiVersion(); AvmAssert(isValidApiVersion(apiVersion)); return apiVersion; } } } ApiVersion const apiVersion = this->getDefaultAPI(); AvmAssert(isValidApiVersion(apiVersion)); return apiVersion; } bool AvmCore::isNamespaceVisibleToApiVersionFromCallStack(Namespace* ns) { return ns->getApiVersion() <= getApiVersionFromCallStack(); } void AvmCore::setDxns(MethodFrame* f, String* internedUri) { Namespace* ns = newPublicNamespace(internedUri); f->setDxns(ns); } void AvmCore::setDxnsLate(MethodFrame* f, Atom uri) { String* internedUri = intern(uri); Namespace* ns = newPublicNamespace(internedUri); f->setDxns(ns); } Namespace* AvmCore::dxns() const { // if currentMethodFrame is null, then no AS3 code is executing -- we are being // called directly from builtin code. We really want to return a non-NULL namespace // in this case, so that native code that calls (say) XML::construct directly // can succeed (otherwise, getDefaultNamespace() would throw exceptions). if (!currentMethodFrame) return publicNamespace; // NULL is ok to return here -- Toplevel::getDefaultNamespace() will throw return MethodFrame::findDxns(currentMethodFrame); } // static Namespace* MethodFrame::findDxns(const MethodFrame* start) { for (const MethodFrame* f = start; f != NULL; f = f->next) { // explicit dxns set here? if so, it's the winner if (f->envOrCodeContext & DXNS_NOT_NULL) return f->dxns; // if not -- is this a frame with an env? if so, return its default ns, even if null. MethodEnv* env = f->env(); if (env) return env->scope()->getDefaultNamespace(); } return NULL; } // apparently SunPro compiler doesn't like combining REALLY_INLINE with static functions. /*static*/ REALLY_INLINE void decr_atom(Atom const a) { // contrary to what you might think from the name, "kUnusedAtomTag" is in fact occasionally used // in player code, so be sure *not* to include it. int const RC_TYPE_MASK = (1 << kObjectType) | (1 << kStringType) | (1 << kNamespaceType); int const aKind = (1 << atomKind(a)); if (aKind & RC_TYPE_MASK) { MMgc::RCObject* rcptr = (MMgc::RCObject*)atomPtr(a); if (rcptr) rcptr->DecrementRef(); } } // apparently SunPro compiler doesn't like combining REALLY_INLINE with static functions. /*static*/ REALLY_INLINE void incr_atom(MMgc::GC *gc, const void* container, Atom const a) { int const RC_TYPE_MASK = (1 << kObjectType) | (1 << kStringType) | (1 << kNamespaceType); int const GC_TYPE_MASK = RC_TYPE_MASK | (1 << kDoubleType) FLOAT_ONLY(| (1<< kSpecialBibopType)) ; // assume existing contents of address are potentially uninitialized, // so don't bother even making an assertion. int const aKind = (1 << atomKind(a)); if (aKind & GC_TYPE_MASK FLOAT_ONLY(&& a!=AtomConstants::undefinedAtom) ) { if (aKind & RC_TYPE_MASK) { MMgc::RCObject* rcptr = (MMgc::RCObject*)atomPtr(a); if (rcptr) rcptr->IncrementRef(); // fall through to InlineWriteBarrierTrap() } #ifdef DEBUG else { AvmAssert(!gc->IsRCObjectSafe(atomPtr(a))); } #endif if (gc->BarrierActive()) gc->InlineWriteBarrierTrap(container); } #ifdef DEBUG else { // Its illegal for kUnusedAtomTag, kSpecialType and // kBooleanType to carry a GC pointer. AvmAssert(atomKind(a) == kIntptrType || !gc->IsPointerToGCPage(atomPtr(a))); } #endif } /*static*/ void AvmCore::decrementAtomRegion(Atom* arr, int length) { Atom* end = arr + length; while (arr < end) { decr_atom(*arr); *arr++ = 0; } } /*static*/ void AvmCore::decrementAtomRegion_null(Atom* arr, int length) { Atom* end = arr + length; while (arr < end) { decr_atom(*arr); *arr++ = nullObjectAtom; } } /*static*/ void AvmCore::atomWriteBarrier(MMgc::GC *gc, const void *container, Atom *address, Atom atomNew) { decr_atom(*address); incr_atom(gc, container, atomNew); *address = atomNew; } /*static*/ void AvmCore::atomWriteBarrier_ctor(MMgc::GC *gc, const void *container, Atom *address, Atom atomNew) { incr_atom(gc, container, atomNew); *address = atomNew; } /*static*/ void AvmCore::atomWriteBarrier_dtor(Atom *address) { decr_atom(*address); *address = 0; } #ifdef _DEBUG Atom AvmCore::genericObjectToAtom(const void* obj) { // We get a null obj here through ElementE4XNode::_insert if (obj != NULL) { GC* gc = GC::GetGC(obj); AvmAssert(!gc->IsRCObjectSafe(obj)); } // return a non-RC atom, makes atomWriteBarrier do the right thing return (Atom)obj|kDoubleType; } #endif void AvmCore::oom(MemoryStatus) { // on kReserve ditch native pages and switch from JIT to interpreter // on kEmpty ditch WORDCODE and switch to abc interpreter } CodeContext* AvmCore::codeContext() const { for (MethodFrame* f = currentMethodFrame; f != NULL; f = f->next) { // has someone overridden the CodeContext at this level (via EnterCodeContext)? CodeContext* cc = f->cc(); if (cc) return cc; MethodEnv* env = f->env(); if (env && !env->method->pool()->isBuiltin) return env->codeContext(); } return NULL; } const BugCompatibility* AvmCore::currentBugCompatibility() const { CodeContext* cc = this->codeContext(); if (cc != NULL) return cc->bugCompatibility(); // If CodeContext is NULL, then we're not executing any user code, // so just use the BugCompatibility of the builtin pool... // which should always be builtinBugCompatibility. return builtinBugCompatibility; } void AvmCore::setStackLimit(uintptr_t p) { stack_limit = p; if (interrupted == NotInterrupted) minstack = p; } #ifdef VMCFG_TELEMETRY_SAMPLER // Tells the sampler to take a stack sample. This is called only when // the sampler is enabled. void AvmCore::takeSample() { if (sampler) sampler->takeSample(); } // A static wrapper for takeSample(), used by the jit. /* static */ void AvmCore::takeSampleWrapper(AvmCore *theCore) { if (theCore->sampler) theCore->sampler->takeSample(); } void AvmCore::enableSampler(telemetry::ISampler* pSampler) { sampler = pSampler; samplerEnabled = pSampler != 0; sampleTicks = 0; } void AvmCore::requestSample() { sampleTicks = 1; } void AvmCore::clearSampleRequest() { sampleTicks = 0; } bool AvmCore::isCallStackEmpty() { for (MethodFrame* frame = currentMethodFrame; frame != NULL; frame = frame->next) { MethodEnv* env = frame->env(); if (env && env->method) { return false; } } return true; } unsigned int AvmCore::recordCallStack(telemetry::FunctionHandle* outFrameBuffer, unsigned int maxDepth) { unsigned int nFramesWritten = 0; for (MethodFrame* curFrame = currentMethodFrame; curFrame != NULL; curFrame = curFrame->next) { MethodEnv* env = curFrame->env(); if (env && env->method) { if (nFramesWritten == maxDepth) { // more stack frames than maxDepth. // return 1 greater than the max length so we know there is more.. return ++nFramesWritten; } else { outFrameBuffer[nFramesWritten++] = env->method; } } } return nFramesWritten; } avmplus::Stringp AvmCore::functionHandleToString(telemetry::FunctionHandle handle) { return ((MethodInfo*)handle)->getMethodName(); } void AvmCore::GetStackTrace(StringBuffer &buffer) { for (MethodFrame* curFrame = currentMethodFrame; curFrame != NULL; curFrame = curFrame->next) { MethodEnv* env = curFrame->env(); if (env && env->method) { avmplus::Stringp pStrFunc = functionHandleToString(env->method); avmplus::StUTF8String methodNameUtf8(pStrFunc); buffer << methodNameUtf8.c_str() << '\n'; } } } #endif /* static */ void AvmCore::handleStackOverflowMethodEnv(MethodEnv* env) { handleStackOverflowToplevel(env->toplevel()); } /* static */ void AvmCore::handleStackOverflowToplevel(Toplevel* toplevel) { // this could be a real stack overflow, or an interrupt that // used the stack overflow handler as a way to take control of AS3. AvmCore *core = toplevel->core(); if (core->interrupted) { AvmAssert(core->interrupted != SafepointPoll); handleInterruptToplevel(toplevel, true); // never returns } // invoke host's stack overflow handler core->stackOverflow(toplevel); } #ifdef VMCFG_STACK_METRICS void AvmCore::initStackMetrics() { minStack = ~uintptr_t(0); maxStack = 0; } uintptr_t AvmCore::getMinStack() { return minStack; } uintptr_t AvmCore::getMaxStack() { return maxStack; } void AvmCore::recordStackPointer() { char* dummy; uintptr_t sp = (uintptr_t)&dummy; if (sp < minStack) minStack = sp; if (sp > maxStack) maxStack = sp; } // A static wrapper for recordStackPointer(), used by the jit. /* static */ void AvmCore::recordStackPointerWrapper(AvmCore *theCore) { theCore->recordStackPointer(); } #endif #ifdef VMCFG_EPOC_EMULATOR // Current (June 2010) Symbian emulator can not handle PCREContext because the constructor // calls OS methods, disable for emulator build. void AvmCore::checkPCREStackOverflow() { } void AvmCore::setPCREContext(Toplevel* /*env*/) { } void AvmCore::releasePCREContext() { } #else static GCThreadLocal PCREContext; /* static */ void AvmCore::checkPCREStackOverflow() { // Stopgap measure 2009-10-29: The PCREContext can be NULL when host code (*cough* Flash Player *cough*) // calls directly into the PCRE engine. We need to fix that, but for the moment we allow it to happen: // it's no worse than before. if (PCREContext != NULL) (PCREContext->core())->stackCheck(PCREContext); } /* static */ void AvmCore::setPCREContext(Toplevel* env) { PCREContext = env; } /* static */ void AvmCore::releasePCREContext() { PCREContext.destroy(); } #endif // VMCFG_EPOC_EMULATOR void AvmCore::clearInterrupt() { interrupted = NotInterrupted; } void AvmCore::raiseInterrupt(InterruptReason reason) { AvmAssert(reason != NotInterrupted); AvmAssert(reason != SafepointPoll); // Don't use this for safepoints // If this core is already suppose to be in a safepoint poll state, then pend the reason for // examination after the safepoint. if (interrupted == SafepointPoll) { pending_interrupt = reason; } else { interrupted = reason; } } /* static */ void AvmCore::handleInterruptMethodEnv(MethodEnv *env) { handleInterruptToplevel(env->toplevel(), !env->method->isNonInterruptible()); } /* static */ void AvmCore::handleInterruptToplevel(Toplevel *toplevel, bool canUnwindStack) { AvmCore *core = toplevel->core(); InterruptReason reason = core->interrupted; core->interrupted = NotInterrupted; if (reason == SafepointPoll) { SAFEPOINT_POLL(); if (core->pending_interrupt != NotInterrupted && canUnwindStack) { reason = core->pending_interrupt; core->pending_interrupt = NotInterrupted; core->interrupt(toplevel, reason); // Doesn't return. AvmAssert(false); } else { // No pending interrupt, or there is one, but we can't unwind. // We'll unwind next time we get interrupted and can unwind. return; // This branch does return. } } else if (canUnwindStack) { // There might be a pending terminating interrupt but we don't care. core->pending_interrupt = NotInterrupted; core->interrupt(toplevel, reason); // interrupt() must not return from here! AvmAssert(false); } else { // We'll act on the interrupt next time we're in safepoint. core->pending_interrupt = reason; // Interrupted a noninterruptible method and it's not a safepoint poll point. // Ignore and return to caller, interrupt is pending. } } void AvmCore::addVersionedURIs(char const* const* uris) { while (*uris != NULL) { String* uri = internConstantStringLatin1(*uris++); #ifdef _DEBUG if (m_unversionedURIs->get_ht()->get(uri->atom()) != undefinedAtom) { VMPI_log(StUTF8String(uri).c_str()); AvmAssert(!"a URI that is used in unversioned form must not later be used in versioned form"); } #endif m_versionedURIs->get_ht()->add(uri->atom(), trueAtom); } } Namespacep AvmCore::findPublicNamespace() { ApiVersion apiVersion = this->getApiVersionFromCallStack(); AvmAssert(isValidApiVersion(apiVersion)); return publicNamespaces->nsAt(apiVersion); } // NOTE: this is a temporary fix until OOM handling hooks in to Avm exceptions generally; // see https://bugzilla.mozilla.org/show_bug.cgi?id=611078 #ifdef DEBUGGER AvmCoreAutoEnter::AvmCoreAutoEnter(AvmCore* core) : m_ef(GCHeap::GetGCHeap()->GetEnterFrame()) , m_savedCore(core) , m_savedCallStack(core ? core->callStack : NULL) { if (m_ef) m_ef->AddAbortUnwindObject(this); } AvmCoreAutoEnter::~AvmCoreAutoEnter() { if (m_ef) m_ef->RemoveAbortUnwindObject(this); } void AvmCoreAutoEnter::Unwind() { if (m_savedCore) m_savedCore->callStack = m_savedCallStack; } #endif // global helpers bool AvmCore::getBaseURIAndApiVersion(Stringp& uri, ApiVersion& apiVersion) { if (!uri->isEmpty()) { int const idx = uri->length() - 1; int const mark = uri->charAt(idx); // if mark is not recognized as a valid version marker, then ignore it if (mark >= AvmCore::MIN_API_MARK && mark < AvmCore::MIN_API_MARK + kApiVersion_count) { apiVersion = ApiVersion(mark - AvmCore::MIN_API_MARK); uri = (idx > 0) ? internSubstring(uri, 0, idx) : Stringp(kEmptyString); #ifdef VMCFG_VERIFYALL // You can use -Dverifyonly to simply verify a SWF, but not execute any code; // as part of this you have to also specify avmglue.abc, eg // // avm -Dverifyonly avmglue.abc something.swf // // The problem is that avmglue.abc contains version-marked URIs, // but avmshell doesn't know which of those are "supposed" to be versioned // (e.g., flash.debugger). For -Dverifyonly, however, it doesn't really // matter much, so rather than jump thru hoops to figure out the proper // URIs for this, just skip the assert. if (!config.verifyonly) { AvmAssert(isVersionedURI(uri)); } #else AvmAssert(isVersionedURI(uri)); #endif return true; } // this is not the case: we can legitimately see unmarked URIs for "versioned uris". // AvmAssert(!isVersionedURI(uri)); } return false; } Namespacep AvmCore::getVersionedNamespace(Namespacep ns, ApiVersion apiVersion) { if (ns->getType() != Namespace::NS_Public || !isVersionedURI(ns->getURI())) return ns; Namespacep ns2 = gotNamespace(ns->m_uriAndType, apiVersion); if (ns2 == NULL) { ns2 = internNamespace(newNamespace(ns->getURI(), ns->getType(), apiVersion)); } return ns2; } bool AvmCore::isVersionedURI(Stringp uri) { AvmAssert(uri != NULL); AvmAssert(uri->isInterned()); bool const r = m_versionedURIs->get_ht()->get(uri->atom()) != undefinedAtom; #ifdef _DEBUG if (!r) m_unversionedURIs->get_ht()->add(uri->atom(), trueAtom); #endif return r; } /*static*/ bool AvmCore::parseApiVersion(const char* p, ApiVersion& apiVersion, ApiVersionSeries& apiVersionSeries) { for (int i = 0; i < kApiVersion_count; ++i) { if (VMPI_strcmp(p, kApiVersionNames[i]) == 0) { apiVersion = ApiVersion(i); // This is not terribly efficient, but doesn't need to be. uint32_t m = kApiVersionSeriesMembership[i]; apiVersionSeries = ApiVersionSeries(0); while ((m & 1) == 0) { apiVersionSeries = ApiVersionSeries(int(apiVersionSeries) + 1); m >>= 1; } return true; } } return false; } BugCompatibility::BugCompatibility(BugCompatibility::Version v) { #ifdef DEBUG // Prevent the name table from getting out of sync with the enum. for ( int i=0 ; i < VersionCount ; i++) AvmAssert(kNames[i] != 0); #endif // We rely on the fact that we are allocated pre-zeroed by MMgc, // thus ensuring that the default state of all compatibility bits is zero. // Thus we only need to set bits based on versions, we don't have to clear anything. // Primordial version did not fix any bugs (it only introduced them) // if (v >= kSWF9) { nothing to do } // kSWF10 is also a no-op, since the one bug that used to live here actually needs // to be controlled by a global (!) flag for backwards compatibility in this case. // see class BugCompatibility for more information. // if (v >= kSWF10) { nothing to do here, either } if (v >= kSWF11) { bugzilla444630 = 1; // Entities are not escaped when appending String content to XML bugzilla456852a = 1; // Different runtime error for invalid constructor call when -Ojit set bugzilla456852b = 1; // Different runtime error for negative vector index when -Ojit set bugzilla504525 = 1; // Vector.concat processes arguments in reverse order bugzilla513018 = 1; // parseFloat accepts illegal number syntax bugzilla524122 = 1; // Incorrect optimization for integers in numeric sort bugzilla526662 = 1; // XMLParser stops at NUL char bugzilla539094 = 1; // OSR (On-Stack replacement of interpreted methods by JIT-ted methods) bugzilla551587 = 1; // MathClass:_min() does not correctly handle -0 bugzilla558863 = 1; // in operator on bytearray throws exception for non-natural number bugzilla585791 = 1; // String.localeCompare with a null String object returns 0 bugzilla598683 = 1; // Bad XML with unterminated node with namespace not throwing correctly bugzilla609416 = 1; // encodeURIComponent and decodeURIComponent give wrong output when input contains surrogate pairs } // if (v >= kSWF12) { nothing to do here } if (v >= kSWF13) /* Serrano */ { bugzilla532454 = 1; // Sort incorrectly converts result of user-supplied comparison function to integer bugzilla574600 = 1; // Vector.<>.AS3::map returns no value bugzilla654807 = 1; // sealed subclasses of Array act sort-of-dynamic } if (v >= kSWF14) /* Anza, tentative! */ { bugzilla678952 = 1; // Operations on Vector. do not make use of or provide information about C } if (v >= kSWF15) /* Brannan */ { bugzilla513039 = 1; // Number.toFixed(0) returns incorrect numbers, rounding issues bugzilla661330 = 1; // Array.length behavior doesn't follow ECMA262 near 2^32-1 } if (v >= kSWF16) /* Cyril */ { } if (v >= kSWF17) /* Dolores */ { } if (v >= kSWF18) /* Ellis */ { bugzilla619148 = 1; // Stack traces in release builds } if (v >= kSWF19) /* Folsom */ { } if (v >= kSWF20) /* Geary */ { } if (v >= kSWF21) /* Harrison */ { watson3548918 = 1; // For appendChild, if the newChild is already in the tree, it is first removed. watson3573621 = 1; // HTTPStatusEvent is not sent when the request has to have a crossdomain policy file checked. } if (v >= kSWF22) /* Irving */ { } } /*static*/ uint32_t const BugCompatibility::kNames[BugCompatibility::VersionCount] = { 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 }; }