зеркало из https://github.com/mozilla/gecko-dev.git
5609 строки
243 KiB
C++
5609 строки
243 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is the JavaScript 2 Prototype.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
|
#ifdef _WIN32
|
|
#include "msvc_pragma.h"
|
|
#endif
|
|
|
|
|
|
#include <algorithm>
|
|
#include <assert.h>
|
|
#include <map>
|
|
#include <list>
|
|
#include <stack>
|
|
|
|
#include "world.h"
|
|
#include "utilities.h"
|
|
#include "js2value.h"
|
|
#include "jslong.h"
|
|
#include "numerics.h"
|
|
#include "reader.h"
|
|
#include "parser.h"
|
|
#include "js2engine.h"
|
|
#include "regexp.h"
|
|
#include "bytecodecontainer.h"
|
|
#include "js2metadata.h"
|
|
|
|
|
|
namespace JavaScript {
|
|
namespace MetaData {
|
|
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Statements and statement lists
|
|
*
|
|
************************************************************************************/
|
|
|
|
|
|
/*
|
|
* Validate the linked list of statement nodes beginning at 'p'
|
|
*/
|
|
void JS2Metadata::ValidateStmtList(StmtNode *p) {
|
|
while (p) {
|
|
ValidateStmt(&cxt, env, Singular, p);
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
void JS2Metadata::ValidateStmtList(Context *cxt, Environment *env, Plurality pl, StmtNode *p) {
|
|
while (p) {
|
|
ValidateStmt(cxt, env, pl, p);
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
FunctionInstance *JS2Metadata::createFunctionInstance(Environment *env, bool prototype, bool unchecked, NativeCode *code, uint32 length, DynamicVariable **lengthProperty)
|
|
{
|
|
ParameterFrame *compileFrame = new (this) ParameterFrame(JS2VAL_VOID, prototype);
|
|
DEFINE_ROOTKEEPER(this, rk1, compileFrame);
|
|
FunctionInstance *result = new (this) FunctionInstance(this, functionClass->prototype, functionClass);
|
|
DEFINE_ROOTKEEPER(this, rk2, result);
|
|
if (code == NULL)
|
|
result->fWrap = new FunctionWrapper(this, unchecked, compileFrame, env);
|
|
else
|
|
result->fWrap = new FunctionWrapper(this, unchecked, compileFrame, code, env);
|
|
result->fWrap->length = length;
|
|
DynamicVariable *dv = createDynamicProperty(result, engine->length_StringAtom, INT_TO_JS2VAL(length), ReadAccess, true, false);
|
|
if (lengthProperty)
|
|
*lengthProperty = dv;
|
|
return result;
|
|
}
|
|
|
|
|
|
FunctionInstance *JS2Metadata::validateStaticFunction(Context *cxt, Environment *env, FunctionDefinition *fnDef, bool prototype, bool unchecked, bool isConstructor, size_t pos)
|
|
{
|
|
DynamicVariable *lengthVar = NULL;
|
|
FunctionInstance *result = createFunctionInstance(env, prototype, unchecked, NULL, 0, &lengthVar);
|
|
DEFINE_ROOTKEEPER(this, rk1, result);
|
|
fnDef->fn = result;
|
|
|
|
Frame *curTopFrame = env->getTopFrame();
|
|
CompilationData *oldData = startCompilationUnit(result->fWrap->bCon, bCon->mSource, bCon->mSourceLocation);
|
|
try {
|
|
env->addFrame(result->fWrap->compileFrame);
|
|
VariableBinding *pb = fnDef->parameters;
|
|
uint32 pCount = 0;
|
|
if (pb) {
|
|
while (pb) {
|
|
pCount++;
|
|
pb = pb->next;
|
|
}
|
|
pb = fnDef->parameters;
|
|
while (pb) {
|
|
if (pb->type)
|
|
ValidateTypeExpression(cxt, env, pb->type);
|
|
if (unchecked) {
|
|
pb->member = NULL;
|
|
defineHoistedVar(env, *pb->name, JS2VAL_UNDEFINED, true, pos);
|
|
}
|
|
else {
|
|
FrameVariable *v = new FrameVariable(result->fWrap->compileFrame->allocateSlot(), FrameVariable::Parameter);
|
|
pb->member = v;
|
|
defineLocalMember(env, *pb->name, NULL, Attribute::NoOverride, false, ReadWriteAccess, v, pb->pos, true);
|
|
}
|
|
pb = pb->next;
|
|
}
|
|
}
|
|
if (fnDef->resultType)
|
|
ValidateTypeExpression(cxt, env, fnDef->resultType);
|
|
result->fWrap->length = pCount;
|
|
lengthVar->value = INT_TO_JS2VAL(pCount);
|
|
if (cxt->E3compatibility)
|
|
createDynamicProperty(result, world.identifiers["arguments"], JS2VAL_NULL, ReadAccess, true, false);
|
|
result->fWrap->compileFrame->isConstructor = isConstructor;
|
|
ValidateStmt(cxt, env, Plural, fnDef->body);
|
|
env->removeTopFrame();
|
|
}
|
|
catch (Exception x) {
|
|
restoreCompilationUnit(oldData);
|
|
env->setTopFrame(curTopFrame);
|
|
throw x;
|
|
}
|
|
restoreCompilationUnit(oldData);
|
|
return result;
|
|
}
|
|
|
|
void JS2Metadata::validateStatic(Context *cxt, Environment *env, FunctionDefinition *fnDef, CompoundAttribute *a, bool unchecked, bool hoisted, size_t pos)
|
|
{
|
|
FunctionInstance *fnInst = NULL;
|
|
DEFINE_ROOTKEEPER(this, rk1, fnInst);
|
|
switch (fnDef->prefix) {
|
|
case FunctionName::normal:
|
|
fnInst = validateStaticFunction(cxt, env, fnDef, a->prototype, unchecked, false, pos);
|
|
if (hoisted)
|
|
defineHoistedVar(env, *fnDef->name, OBJECT_TO_JS2VAL(fnInst), false, pos);
|
|
else {
|
|
Variable *v = new Variable(functionClass, OBJECT_TO_JS2VAL(fnInst), true);
|
|
defineLocalMember(env, *fnDef->name, &a->namespaces, a->overrideMod, a->xplicit, ReadWriteAccess, v, pos, true);
|
|
}
|
|
break;
|
|
case FunctionName::Get:
|
|
{
|
|
if (a->prototype)
|
|
reportError(Exception::attributeError, "A getter cannot have the prototype attribute", pos);
|
|
ASSERT(!(unchecked || hoisted));
|
|
// XXX shouldn't be using validateStaticFunction
|
|
fnInst = validateStaticFunction(cxt, env, fnDef, false, false, false, pos);
|
|
Getter *g = new Getter(fnInst->fWrap->resultType, fnInst);
|
|
defineLocalMember(env, *fnDef->name, &a->namespaces, a->overrideMod, a->xplicit, ReadAccess, g, pos, true);
|
|
}
|
|
break;
|
|
case FunctionName::Set:
|
|
{
|
|
if (a->prototype)
|
|
reportError(Exception::attributeError, "A setter cannot have the prototype attribute", pos);
|
|
ASSERT(!(unchecked || hoisted));
|
|
// XXX shouldn't be using validateStaticFunction
|
|
fnInst = validateStaticFunction(cxt, env, fnDef, false, false, false, pos);
|
|
Setter *s = new Setter(fnInst->fWrap->resultType, fnInst);
|
|
defineLocalMember(env, *fnDef->name, &a->namespaces, a->overrideMod, a->xplicit, WriteAccess, s, pos, true);
|
|
}
|
|
break;
|
|
}
|
|
StringFormatter sFmt;
|
|
PrettyPrinter pp(sFmt, 80);
|
|
fnDef->print(pp, NULL, true);
|
|
pp.end();
|
|
fnInst->sourceText = engine->allocStringPtr(&sFmt.getString());
|
|
}
|
|
|
|
void JS2Metadata::validateConstructor(Context *cxt, Environment *env, FunctionDefinition *fnDef, JS2Class *c, CompoundAttribute *a, size_t pos)
|
|
{
|
|
if (a->prototype)
|
|
reportError(Exception::attributeError, "A class constructor cannot have the prototype attribute", pos);
|
|
if (fnDef->prefix != FunctionName::normal)
|
|
reportError(Exception::syntaxError, "A class constructor cannot be a getter or a setter", pos);
|
|
// XXX shouldn't be using validateStaticFunction
|
|
c->init = validateStaticFunction(cxt, env, fnDef, false, false, true, pos);
|
|
}
|
|
|
|
void JS2Metadata::validateInstance(Context *cxt, Environment *env, FunctionDefinition *fnDef, JS2Class *c, CompoundAttribute *a, bool final, size_t pos)
|
|
{
|
|
if (a->prototype)
|
|
reportError(Exception::attributeError, "An instance method cannot have the prototype attribute", pos);
|
|
// XXX shouldn't be using validateStaticFunction
|
|
FunctionInstance *fnInst = NULL;
|
|
DEFINE_ROOTKEEPER(this, rk1, fnInst);
|
|
fnInst = validateStaticFunction(cxt, env, fnDef, false, false, false, pos);
|
|
Multiname *mn = new (this) Multiname(*fnDef->name, a->namespaces);
|
|
InstanceMember *m;
|
|
switch (fnDef->prefix) {
|
|
case FunctionName::normal:
|
|
m = new InstanceMethod(mn, fnInst, final, true);
|
|
break;
|
|
case FunctionName::Set:
|
|
m = new InstanceSetter(mn, fnInst, objectClass, final, true);
|
|
break;
|
|
case FunctionName::Get:
|
|
m = new InstanceGetter(mn, fnInst, objectClass, final, true);
|
|
break;
|
|
}
|
|
defineInstanceMember(c, cxt, *fnDef->name, a->namespaces, a->overrideMod, a->xplicit, m, pos);
|
|
}
|
|
|
|
/*
|
|
* Validate an individual statement 'p', including it's children
|
|
*/
|
|
void JS2Metadata::ValidateStmt(Context *cxt, Environment *env, Plurality pl, StmtNode *p)
|
|
{
|
|
CompoundAttribute *a = NULL;
|
|
DEFINE_ROOTKEEPER(this, rk, a);
|
|
Frame *curTopFrame = env->getTopFrame();
|
|
|
|
try {
|
|
switch (p->getKind()) {
|
|
case StmtNode::block:
|
|
case StmtNode::group:
|
|
{
|
|
BlockStmtNode *b = checked_cast<BlockStmtNode *>(p);
|
|
b->compileFrame = new (this) BlockFrame();
|
|
b->compileFrame->isFunctionFrame = (env->getTopFrame()->kind == ParameterFrameKind);
|
|
bCon->saveFrame(b->compileFrame); // stash this frame so it doesn't get gc'd before eval pass.
|
|
env->addFrame(b->compileFrame);
|
|
targetList.push_back(p);
|
|
ValidateStmtList(cxt, env, pl, b->statements);
|
|
env->removeTopFrame();
|
|
targetList.pop_back();
|
|
}
|
|
break;
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(p);
|
|
l->labelID = bCon->getLabel();
|
|
// Make sure there is no existing target with the same name
|
|
for (TargetListIterator si = targetList.begin(), end = targetList.end(); (si != end); si++) {
|
|
switch ((*si)->getKind()) {
|
|
case StmtNode::label:
|
|
if (checked_cast<LabelStmtNode *>(*si)->name == l->name)
|
|
reportError(Exception::syntaxError, "Duplicate statement label", p->pos);
|
|
break;
|
|
}
|
|
}
|
|
targetList.push_back(p);
|
|
ValidateStmt(cxt, env, pl, l->stmt);
|
|
targetList.pop_back();
|
|
}
|
|
break;
|
|
case StmtNode::If:
|
|
{
|
|
UnaryStmtNode *i = checked_cast<UnaryStmtNode *>(p);
|
|
ValidateExpression(cxt, env, i->expr);
|
|
ValidateStmt(cxt, env, pl, i->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::IfElse:
|
|
{
|
|
BinaryStmtNode *i = checked_cast<BinaryStmtNode *>(p);
|
|
ValidateExpression(cxt, env, i->expr);
|
|
ValidateStmt(cxt, env, pl, i->stmt);
|
|
ValidateStmt(cxt, env, pl, i->stmt2);
|
|
}
|
|
break;
|
|
case StmtNode::ForIn:
|
|
// XXX need to validate that first expression is a single binding ?
|
|
case StmtNode::For:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(p);
|
|
f->breakLabelID = bCon->getLabel();
|
|
f->continueLabelID = bCon->getLabel();
|
|
if (f->initializer)
|
|
ValidateStmt(cxt, env, pl, f->initializer);
|
|
if (f->expr2)
|
|
ValidateExpression(cxt, env, f->expr2);
|
|
if (f->expr3)
|
|
ValidateExpression(cxt, env, f->expr3);
|
|
f->breakLabelID = bCon->getLabel();
|
|
f->continueLabelID = bCon->getLabel();
|
|
targetList.push_back(p);
|
|
ValidateStmt(cxt, env, pl, f->stmt);
|
|
targetList.pop_back();
|
|
}
|
|
break;
|
|
case StmtNode::Switch:
|
|
{
|
|
SwitchStmtNode *sw = checked_cast<SwitchStmtNode *>(p);
|
|
sw->breakLabelID = bCon->getLabel();
|
|
ValidateExpression(cxt, env, sw->expr);
|
|
targetList.push_back(p);
|
|
StmtNode *s = sw->statements;
|
|
while (s) {
|
|
if (s->getKind() == StmtNode::Case) {
|
|
ExprStmtNode *c = checked_cast<ExprStmtNode *>(s);
|
|
c->labelID = bCon->getLabel();
|
|
if (c->expr)
|
|
ValidateExpression(cxt, env, c->expr);
|
|
}
|
|
else
|
|
ValidateStmt(cxt, env, pl, s);
|
|
s = s->next;
|
|
}
|
|
targetList.pop_back();
|
|
}
|
|
break;
|
|
case StmtNode::While:
|
|
case StmtNode::DoWhile:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(p);
|
|
w->breakLabelID = bCon->getLabel();
|
|
w->continueLabelID = bCon->getLabel();
|
|
targetList.push_back(p);
|
|
ValidateExpression(cxt, env, w->expr);
|
|
ValidateStmt(cxt, env, pl, w->stmt);
|
|
targetList.pop_back();
|
|
}
|
|
break;
|
|
case StmtNode::Break:
|
|
{
|
|
GoStmtNode *g = checked_cast<GoStmtNode *>(p);
|
|
g->tgtID = NotALabel;
|
|
if (g->name) {
|
|
// need to find the closest 'breakable' statement covered by the named label
|
|
bool foundit = false;
|
|
for (TargetListReverseIterator si = targetList.rbegin(), end = targetList.rend();
|
|
((g->tgtID == NotALabel) && (si != end) && !foundit); si++)
|
|
{
|
|
switch ((*si)->getKind()) {
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(*si);
|
|
if (l->name == *g->name) {
|
|
g->tgtID = l->labelID;
|
|
foundit = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// un-labelled, just find the closest breakable statement
|
|
for (TargetListReverseIterator si = targetList.rbegin(), end = targetList.rend();
|
|
((g->tgtID == NotALabel) && (si != end)); si++) {
|
|
switch ((*si)->getKind()) {
|
|
case StmtNode::While:
|
|
case StmtNode::DoWhile:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(*si);
|
|
g->tgtID = w->breakLabelID;
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
case StmtNode::ForIn:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(*si);
|
|
g->tgtID = f->breakLabelID;
|
|
}
|
|
break;
|
|
case StmtNode::Switch:
|
|
{
|
|
SwitchStmtNode *s = checked_cast<SwitchStmtNode *>(*si);
|
|
g->tgtID = s->breakLabelID;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (g->tgtID == NotALabel)
|
|
reportError(Exception::syntaxError, "No break target available", p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::Continue:
|
|
{
|
|
GoStmtNode *g = checked_cast<GoStmtNode *>(p);
|
|
g->tgtID = NotALabel;
|
|
if (g->name) {
|
|
// need to find the closest 'continuable' statement covered by the named label
|
|
LabelID tgt = NotALabel;
|
|
bool foundit = false;
|
|
for (TargetListReverseIterator si = targetList.rbegin(), end = targetList.rend();
|
|
((g->tgtID == NotALabel) && (si != end) && !foundit); si++)
|
|
{
|
|
switch ((*si)->getKind()) {
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(*si);
|
|
if (l->name == *g->name) {
|
|
g->tgtID = tgt;
|
|
foundit = true;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::While:
|
|
case StmtNode::DoWhile:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(*si);
|
|
tgt = w->continueLabelID;
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
case StmtNode::ForIn:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(*si);
|
|
tgt = f->continueLabelID;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// un-labelled, just find the closest breakable statement
|
|
for (TargetListReverseIterator si = targetList.rbegin(), end = targetList.rend();
|
|
((g->tgtID == NotALabel) && (si != end)); si++) {
|
|
// only some non-label statements will do
|
|
StmtNode *s = *si;
|
|
switch (s->getKind()) {
|
|
case StmtNode::While:
|
|
case StmtNode::DoWhile:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(*si);
|
|
g->tgtID = w->continueLabelID;
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
case StmtNode::ForIn:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(*si);
|
|
g->tgtID = f->continueLabelID;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (g->tgtID == NotALabel)
|
|
reportError(Exception::syntaxError, "No continue target available", p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::Throw:
|
|
{
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(p);
|
|
ValidateExpression(cxt, env, e->expr);
|
|
}
|
|
break;
|
|
case StmtNode::Try:
|
|
{
|
|
TryStmtNode *t = checked_cast<TryStmtNode *>(p);
|
|
ValidateStmt(cxt, env, pl, t->stmt);
|
|
if (t->finally)
|
|
ValidateStmt(cxt, env, pl, t->finally);
|
|
CatchClause *c = t->catches;
|
|
while (c) {
|
|
ValidateStmt(cxt, env, pl, c->stmt);
|
|
if (c->type)
|
|
ValidateExpression(cxt, env, c->type);
|
|
c = c->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Return:
|
|
{
|
|
ParameterFrame *pFrame = env->getEnclosingParameterFrame(NULL);
|
|
// If there isn't a parameter frame, or the parameter frame is
|
|
// only a runtime frame (for eval), then it's an orphan return
|
|
if (!pFrame || pFrame->pluralFrame)
|
|
reportError(Exception::syntaxError, "Return statement not in function", p->pos);
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(p);
|
|
if (e->expr) {
|
|
ValidateExpression(cxt, env, e->expr);
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Function:
|
|
{
|
|
Attribute *attr = NULL;
|
|
FunctionStmtNode *f = checked_cast<FunctionStmtNode *>(p);
|
|
if (f->attributes) {
|
|
ValidateAttributeExpression(cxt, env, f->attributes);
|
|
attr = EvalAttributeExpression(env, CompilePhase, f->attributes);
|
|
}
|
|
a = Attribute::toCompoundAttribute(this, attr);
|
|
if (a->dynamic)
|
|
reportError(Exception::attributeError, "A function cannot have the dynamic attribute", p->pos);
|
|
Frame *topFrame = env->getTopFrame();
|
|
if (topFrame->kind == ClassKind) {
|
|
switch (a->memberMod) {
|
|
case Attribute::Static:
|
|
validateStatic(cxt, env, &f->function, a, false, false, p->pos);
|
|
break;
|
|
case Attribute::NoModifier:
|
|
if (*f->function.name == (checked_cast<JS2Class *>(topFrame))->name)
|
|
validateConstructor(cxt, env, &f->function, checked_cast<JS2Class *>(topFrame), a, p->pos);
|
|
else
|
|
validateInstance(cxt, env, &f->function, checked_cast<JS2Class *>(topFrame), a, false, p->pos);
|
|
break;
|
|
case Attribute::Virtual:
|
|
validateInstance(cxt, env, &f->function, checked_cast<JS2Class *>(topFrame), a, false, p->pos);
|
|
break;
|
|
case Attribute::Final:
|
|
validateInstance(cxt, env, &f->function, checked_cast<JS2Class *>(topFrame), a, true, p->pos);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
if (a->memberMod != Attribute::NoModifier)
|
|
reportError(Exception::attributeError, "Non-class-member functions cannot have a static, virtual or final attribute", p->pos);
|
|
// discover Plain[function] by looking for typed parameters or result
|
|
VariableBinding *vb = f->function.parameters;
|
|
bool untyped = (f->function.resultType == NULL);
|
|
if (untyped) {
|
|
while (vb) {
|
|
if (vb->type) {
|
|
untyped = false;
|
|
break;
|
|
}
|
|
vb = vb->next;
|
|
}
|
|
}
|
|
bool unchecked = !cxt->strict && (f->function.prefix == FunctionName::normal) && untyped;
|
|
bool hoisted = unchecked
|
|
&& (f->attributes == NULL)
|
|
&& ((topFrame->kind == PackageKind)
|
|
|| (topFrame->kind == BlockFrameKind)
|
|
|| (topFrame->kind == ParameterFrameKind));
|
|
validateStatic(cxt, env, &f->function, a, unchecked, hoisted, p->pos);
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Var:
|
|
case StmtNode::Const:
|
|
{
|
|
bool immutable = (p->getKind() == StmtNode::Const);
|
|
Attribute *attr = NULL;
|
|
VariableStmtNode *vs = checked_cast<VariableStmtNode *>(p);
|
|
|
|
if (vs->attributes) {
|
|
ValidateAttributeExpression(cxt, env, vs->attributes);
|
|
attr = EvalAttributeExpression(env, CompilePhase, vs->attributes);
|
|
}
|
|
|
|
VariableBinding *vb = vs->bindings;
|
|
Frame *regionalFrame = *(env->getRegionalFrame());
|
|
while (vb) {
|
|
ASSERT(vb->name);
|
|
const StringAtom &name = *vb->name;
|
|
if (vb->type)
|
|
ValidateTypeExpression(cxt, env, vb->type);
|
|
vb->member = NULL;
|
|
if (vb->initializer)
|
|
ValidateExpression(cxt, env, vb->initializer);
|
|
|
|
if (!cxt->strict && ((regionalFrame->kind == PackageKind)
|
|
|| (regionalFrame->kind == ParameterFrameKind))
|
|
&& !immutable
|
|
&& (vs->attributes == NULL)
|
|
&& (vb->type == NULL)) {
|
|
defineHoistedVar(env, name, JS2VAL_UNDEFINED, true, p->pos);
|
|
}
|
|
else {
|
|
a = Attribute::toCompoundAttribute(this, attr);
|
|
if (a->dynamic || a->prototype)
|
|
reportError(Exception::definitionError, "Illegal attribute", p->pos);
|
|
Attribute::MemberModifier memberMod = a->memberMod;
|
|
if ((env->getTopFrame()->kind == ClassKind)
|
|
&& (memberMod == Attribute::NoModifier))
|
|
memberMod = Attribute::Final;
|
|
switch (memberMod) {
|
|
case Attribute::NoModifier:
|
|
case Attribute::Static:
|
|
{
|
|
// Set type to FUTURE_TYPE - it will be resolved during 'Setup'. The value is either FUTURE_VALUE
|
|
// for 'const' - in which case the expression is compile time evaluated (or attempted) or set
|
|
// to INACCESSIBLE until run time initialization occurs.
|
|
Variable *v = new Variable(FUTURE_TYPE, immutable ? JS2VAL_FUTUREVALUE : JS2VAL_INACCESSIBLE, immutable);
|
|
vb->member = v;
|
|
v->vb = vb;
|
|
vb->mn = defineLocalMember(env, name, &a->namespaces, a->overrideMod, a->xplicit, ReadWriteAccess, v, p->pos, true);
|
|
bCon->saveMultiname(vb->mn);
|
|
}
|
|
break;
|
|
case Attribute::Virtual:
|
|
case Attribute::Final:
|
|
{
|
|
Multiname *mn = new (this) Multiname(name, a->namespaces);
|
|
JS2Class *c = checked_cast<JS2Class *>(env->getTopFrame());
|
|
InstanceMember *m = new InstanceVariable(mn, FUTURE_TYPE, immutable, (memberMod == Attribute::Final), true, c->slotCount++);
|
|
vb->member = m;
|
|
vb->overridden = defineInstanceMember(c, cxt, name, a->namespaces, a->overrideMod, a->xplicit, m, p->pos);
|
|
}
|
|
break;
|
|
default:
|
|
reportError(Exception::definitionError, "Illegal attribute", p->pos);
|
|
break;
|
|
}
|
|
}
|
|
vb = vb->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::expression:
|
|
{
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(p);
|
|
ValidateExpression(cxt, env, e->expr);
|
|
}
|
|
break;
|
|
case StmtNode::Namespace:
|
|
{
|
|
NamespaceStmtNode *ns = checked_cast<NamespaceStmtNode *>(p);
|
|
Attribute *attr = NULL;
|
|
if (ns->attributes) {
|
|
ValidateAttributeExpression(cxt, env, ns->attributes);
|
|
attr = EvalAttributeExpression(env, CompilePhase, ns->attributes);
|
|
}
|
|
a = Attribute::toCompoundAttribute(this, attr);
|
|
if (a->dynamic || a->prototype)
|
|
reportError(Exception::definitionError, "Illegal attribute", p->pos);
|
|
if ( ! ((a->memberMod == Attribute::NoModifier) || ((a->memberMod == Attribute::Static) && (env->getTopFrame()->kind == ClassKind))) )
|
|
reportError(Exception::definitionError, "Illegal attribute", p->pos);
|
|
Variable *v = new Variable(namespaceClass, OBJECT_TO_JS2VAL(new (this) Namespace(ns->name)), true);
|
|
defineLocalMember(env, ns->name, &a->namespaces, a->overrideMod, a->xplicit, ReadWriteAccess, v, p->pos, true);
|
|
}
|
|
break;
|
|
case StmtNode::Use:
|
|
{
|
|
UseStmtNode *u = checked_cast<UseStmtNode *>(p);
|
|
ExprList *eList = u->namespaces;
|
|
while (eList) {
|
|
js2val av = EvalExpression(env, CompilePhase, eList->expr);
|
|
if (JS2VAL_IS_NULL(av) || !JS2VAL_IS_OBJECT(av))
|
|
reportError(Exception::badValueError, "Namespace expected in use directive", p->pos);
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(av);
|
|
if ((obj->kind != AttributeObjectKind) || (checked_cast<Attribute *>(obj)->attrKind != Attribute::NamespaceAttr))
|
|
reportError(Exception::badValueError, "Namespace expected in use directive", p->pos);
|
|
cxt->openNamespaces.push_back(checked_cast<Namespace *>(obj));
|
|
eList = eList->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::Class:
|
|
{
|
|
ClassStmtNode *classStmt = checked_cast<ClassStmtNode *>(p);
|
|
JS2Class *superClass = objectClass;
|
|
if (classStmt->superclass) {
|
|
ValidateExpression(cxt, env, classStmt->superclass);
|
|
js2val av = EvalExpression(env, CompilePhase, classStmt->superclass);
|
|
if (JS2VAL_IS_NULL(av) || !JS2VAL_IS_OBJECT(av))
|
|
reportError(Exception::badValueError, "Class expected in inheritance", p->pos);
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(av);
|
|
if (obj->kind != ClassKind)
|
|
reportError(Exception::badValueError, "Class expected in inheritance", p->pos);
|
|
superClass = checked_cast<JS2Class *>(obj);
|
|
}
|
|
Attribute *attr = NULL;
|
|
if (classStmt->attributes) {
|
|
ValidateAttributeExpression(cxt, env, classStmt->attributes);
|
|
attr = EvalAttributeExpression(env, CompilePhase, classStmt->attributes);
|
|
}
|
|
a = Attribute::toCompoundAttribute(this, attr);
|
|
if (!superClass->complete || superClass->final)
|
|
reportError(Exception::definitionError, "Illegal inheritance", p->pos);
|
|
js2val protoVal = JS2VAL_NULL;
|
|
bool final = false;
|
|
switch (a->memberMod) {
|
|
case Attribute::NoModifier:
|
|
final = false;
|
|
break;
|
|
case Attribute::Static:
|
|
if (env->getTopFrame()->kind != ClassKind)
|
|
reportError(Exception::definitionError, "Illegal use of static modifier", p->pos);
|
|
final = false;
|
|
break;
|
|
case Attribute::Final:
|
|
final = true;
|
|
break;
|
|
default:
|
|
reportError(Exception::definitionError, "Illegal modifier for class definition", p->pos);
|
|
break;
|
|
}
|
|
JS2Class *c = new (this) JS2Class(superClass, protoVal, new (this) Namespace(engine->private_StringAtom), (a->dynamic || superClass->dynamic), final, classStmt->name);
|
|
classStmt->c = c;
|
|
Variable *v = new Variable(classClass, OBJECT_TO_JS2VAL(c), true);
|
|
defineLocalMember(env, classStmt->name, &a->namespaces, a->overrideMod, a->xplicit, ReadWriteAccess, v, p->pos, true);
|
|
if (classStmt->body) {
|
|
env->addFrame(c);
|
|
ValidateStmtList(cxt, env, pl, classStmt->body->statements);
|
|
ASSERT(env->getTopFrame() == c);
|
|
env->removeTopFrame();
|
|
}
|
|
if (c->init == NULL)
|
|
c->init = superClass->init;
|
|
c->complete = true;
|
|
}
|
|
break;
|
|
case StmtNode::With:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(p);
|
|
ValidateExpression(cxt, env, w->expr);
|
|
if (w->stmt->getKind() != StmtNode::block) {
|
|
w->compileFrame = new (this) BlockFrame();
|
|
env->addFrame(w->compileFrame);
|
|
ValidateStmt(cxt, env, pl, w->stmt);
|
|
env->removeTopFrame();
|
|
}
|
|
else
|
|
ValidateStmt(cxt, env, pl, w->stmt);
|
|
}
|
|
break;
|
|
case StmtNode::empty:
|
|
break;
|
|
case StmtNode::Package:
|
|
{
|
|
PackageStmtNode *ps = checked_cast<PackageStmtNode *>(p);
|
|
String packageName = getPackageName(ps->packageIdList);
|
|
Package *package = new (this) Package(packageName, new (this) Namespace(world.identifiers["internal"]));
|
|
|
|
Variable *v = new Variable(packageClass, OBJECT_TO_JS2VAL(package), true);
|
|
defineLocalMember(env, world.identifiers[packageName], NULL, Attribute::NoOverride, false, ReadAccess, v, 0, true);
|
|
|
|
package->status = Package::InTransit;
|
|
packages.push_back(package);
|
|
env->addFrame(package);
|
|
ValidateStmt(cxt, env, pl, ps->body);
|
|
env->removeTopFrame();
|
|
package->status = Package::InHand;
|
|
}
|
|
break;
|
|
case StmtNode::Import:
|
|
{
|
|
ImportStmtNode *i = checked_cast<ImportStmtNode *>(p);
|
|
String packageName;
|
|
if (i->packageIdList)
|
|
packageName = getPackageName(i->packageIdList);
|
|
else
|
|
packageName = *i->packageString;
|
|
|
|
if (!checkForPackage(packageName, i->pos))
|
|
loadPackage(packageName, packageName + ".js");
|
|
|
|
Multiname mn(world.identifiers[packageName], publicNamespace);
|
|
js2val packageValue;
|
|
env->lexicalRead(this, &mn, CompilePhase, &packageValue, NULL);
|
|
if (JS2VAL_IS_VOID(packageValue) || JS2VAL_IS_NULL(packageValue) || !JS2VAL_IS_OBJECT(packageValue)
|
|
|| (JS2VAL_TO_OBJECT(packageValue)->kind != PackageKind))
|
|
reportError(Exception::badValueError, "Package expected in Import directive", i->pos);
|
|
|
|
#if 0
|
|
Package *package = checked_cast<Package *>(JS2VAL_TO_OBJECT(packageValue));
|
|
if (i->varName) {
|
|
Variable *v = new Variable(packageClass, packageValue, true);
|
|
defineLocalMember(env, *i->varName, NULL, Attribute::NoOverride, false, ReadAccess, v, 0, true);
|
|
}
|
|
|
|
// defineVariable(m_cx, *i->varName, NULL, Package_Type, JSValue::newPackage(package));
|
|
|
|
// scan all local bindings in 'package' and handle the alias-ing issue...
|
|
for (PropertyIterator it = package->mProperties.begin(), end = package->mProperties.end();
|
|
(it != end); it++)
|
|
{
|
|
ASSERT(PROPERTY_KIND(it) == ValuePointer);
|
|
bool makeAlias = true;
|
|
if (i->includeExclude) {
|
|
makeAlias = i->exclude;
|
|
IdentifierList *idList = i->includeExclude;
|
|
while (idList) {
|
|
if (idList->name.compare(PROPERTY_NAME(it)) == 0) {
|
|
makeAlias = !makeAlias;
|
|
break;
|
|
}
|
|
idList = idList->next;
|
|
}
|
|
}
|
|
if (makeAlias)
|
|
defineAlias(m_cx, PROPERTY_NAME(it), PROPERTY_NAMESPACELIST(it), PROPERTY_ATTR(it), PROPERTY_TYPE(it), PROPERTY_VALUEPOINTER(it));
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
default:
|
|
NOT_REACHED("Not Yet Implemented");
|
|
} // switch (p->getKind())
|
|
}
|
|
catch (Exception x) {
|
|
env->setTopFrame(curTopFrame);
|
|
throw x;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Build a name for the package from the identifier list
|
|
*/
|
|
String JS2Metadata::getPackageName(IdentifierList *packageIdList)
|
|
{
|
|
String packagePath;
|
|
IdentifierList *idList = packageIdList;
|
|
while (idList) {
|
|
packagePath += idList->name;
|
|
idList = idList->next;
|
|
if (idList)
|
|
packagePath += '/'; // XXX how to get path separator for OS?
|
|
}
|
|
return packagePath;
|
|
}
|
|
|
|
/*
|
|
See if the specified package is already loaded - return true
|
|
Throw an exception if the package is being loaded already
|
|
*/
|
|
bool JS2Metadata::checkForPackage(const String &packageName, size_t pos)
|
|
{
|
|
// XXX linear search
|
|
for (PackageList::iterator pi = packages.begin(), end = packages.end(); (pi != end); pi++) {
|
|
if ((*pi)->name.compare(packageName) == 0) {
|
|
if ((*pi)->status == Package::InTransit)
|
|
reportError(Exception::referenceError, "Package circularity", pos);
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Load the specified package from the file
|
|
*/
|
|
void JS2Metadata::loadPackage(const String & /*packageName*/, const String &filename)
|
|
{
|
|
// XXX need some rules for search path
|
|
// XXX need to extract just the target package from the file
|
|
readEvalFile(filename);
|
|
}
|
|
|
|
JS2Class *JS2Metadata::getVariableType(Variable *v, Phase phase, size_t pos)
|
|
{
|
|
JS2Class *type = v->type;
|
|
if (type == NULL) { // Inaccessible, Note that this can only happen when phase = compile
|
|
// because the compilation phase ensures that all types are valid,
|
|
// so invalid types will not occur during the run phase.
|
|
ASSERT(phase == CompilePhase);
|
|
reportError(Exception::compileExpressionError, "No type assigned", pos);
|
|
}
|
|
else {
|
|
if (v->type == FUTURE_TYPE) {
|
|
// Note that phase = compile because all futures are resolved by the end of the compilation phase.
|
|
ASSERT(phase == CompilePhase);
|
|
if (v->vb->type) {
|
|
v->type = NULL;
|
|
v->type = EvalTypeExpression(env, CompilePhase, v->vb->type);
|
|
}
|
|
else
|
|
v->type = objectClass;
|
|
}
|
|
}
|
|
return v->type;
|
|
}
|
|
|
|
/*
|
|
* Process an individual statement 'p', including it's children
|
|
* - this generates bytecode for each statement, but doesn't actually
|
|
* execute it.
|
|
*/
|
|
void JS2Metadata::SetupStmt(Environment *env, Phase phase, StmtNode *p)
|
|
{
|
|
JS2Class *exprType;
|
|
switch (p->getKind()) {
|
|
case StmtNode::block:
|
|
case StmtNode::group:
|
|
{
|
|
targetList.push_back(p);
|
|
bool pushed = false;
|
|
BlockStmtNode *b = checked_cast<BlockStmtNode *>(p);
|
|
if (b->compileFrame->isFunctionFrame || b->compileFrame->localBindings.size()) {
|
|
bCon->emitOp(ePushFrame, p->pos);
|
|
bCon->addFrame(b->compileFrame);
|
|
pushed = true;
|
|
}
|
|
env->addFrame(b->compileFrame);
|
|
StmtNode *bp = b->statements;
|
|
while (bp) {
|
|
SetupStmt(env, phase, bp);
|
|
bp = bp->next;
|
|
}
|
|
if (pushed)
|
|
bCon->emitOp(ePopFrame, p->pos);
|
|
env->removeTopFrame();
|
|
targetList.pop_back();
|
|
}
|
|
break;
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(p);
|
|
targetList.push_back(p);
|
|
SetupStmt(env, phase, l->stmt);
|
|
targetList.pop_back();
|
|
// labelled statements target are break targets
|
|
bCon->setLabel(l->labelID);
|
|
}
|
|
break;
|
|
case StmtNode::If:
|
|
{
|
|
BytecodeContainer::LabelID skipOverStmt = bCon->getLabel();
|
|
UnaryStmtNode *i = checked_cast<UnaryStmtNode *>(p);
|
|
Reference *r = SetupExprNode(env, phase, i->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranchFalse, skipOverStmt, p->pos);
|
|
SetupStmt(env, phase, i->stmt);
|
|
bCon->setLabel(skipOverStmt);
|
|
}
|
|
break;
|
|
case StmtNode::IfElse:
|
|
{
|
|
BytecodeContainer::LabelID falseStmt = bCon->getLabel();
|
|
BytecodeContainer::LabelID skipOverFalseStmt = bCon->getLabel();
|
|
BinaryStmtNode *i = checked_cast<BinaryStmtNode *>(p);
|
|
Reference *r = SetupExprNode(env, phase, i->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranchFalse, falseStmt, p->pos);
|
|
SetupStmt(env, phase, i->stmt);
|
|
bCon->emitBranch(eBranch, skipOverFalseStmt, p->pos);
|
|
bCon->setLabel(falseStmt);
|
|
SetupStmt(env, phase, i->stmt2);
|
|
bCon->setLabel(skipOverFalseStmt);
|
|
}
|
|
break;
|
|
case StmtNode::Break:
|
|
// XXX for break - if there's a finally that applies to this block, it should
|
|
// be invoked at this point - need to track the appropriate label and emit
|
|
// eCallFinally here.
|
|
case StmtNode::Continue:
|
|
{
|
|
GoStmtNode *g = checked_cast<GoStmtNode *>(p);
|
|
bCon->emitBranch(eBreak, g->tgtID, p->pos);
|
|
uint32 blockCount = 0;
|
|
bool foundit = false;
|
|
for (TargetListReverseIterator si = targetList.rbegin(), end = targetList.rend();
|
|
((si != end) && !foundit); si++)
|
|
{
|
|
switch ((*si)->getKind()) {
|
|
case StmtNode::label:
|
|
{
|
|
LabelStmtNode *l = checked_cast<LabelStmtNode *>(*si);
|
|
if (g->tgtID == l->labelID)
|
|
foundit = true;
|
|
}
|
|
break;
|
|
case StmtNode::While:
|
|
case StmtNode::DoWhile:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(*si);
|
|
if ((g->tgtID == w->breakLabelID) || (g->tgtID == w->continueLabelID))
|
|
foundit = true;
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
case StmtNode::ForIn:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(*si);
|
|
if ((g->tgtID == f->breakLabelID) || (g->tgtID == f->continueLabelID))
|
|
foundit = true;
|
|
}
|
|
break;
|
|
case StmtNode::Switch:
|
|
{
|
|
SwitchStmtNode *s = checked_cast<SwitchStmtNode *>(*si);
|
|
if (g->tgtID == s->breakLabelID)
|
|
foundit = true;
|
|
}
|
|
break;
|
|
case StmtNode::block:
|
|
{
|
|
BlockStmtNode *b = checked_cast<BlockStmtNode *>(*si);
|
|
if (b->compileFrame->isFunctionFrame || b->compileFrame->localBindings.size())
|
|
blockCount++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
ASSERT(foundit);
|
|
bCon->addShort(blockCount);
|
|
}
|
|
break;
|
|
case StmtNode::ForIn:
|
|
/*
|
|
iterator = get_first_property_of(object) [eFirst]
|
|
//
|
|
// pushes iterator object on stack, returns true/false
|
|
//
|
|
if (false) --> break;
|
|
top:
|
|
v <-- iterator.value // v is the thing specified by for ('v' in object) ...
|
|
<statement body>
|
|
continue:
|
|
//
|
|
// expect iterator object on top of stack
|
|
// increment it. Returns true/false
|
|
//
|
|
iterator = get_next_property_of(object, iterator) [eNext]
|
|
if (true) --> top;
|
|
break:
|
|
// want stack cleared of iterator at this point
|
|
*/
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(p);
|
|
BytecodeContainer::LabelID loopTop = bCon->getLabel();
|
|
|
|
Reference *r = SetupExprNode(env, phase, f->expr2, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eFirst, p->pos);
|
|
bCon->emitBranch(eBranchFalse, f->breakLabelID, p->pos);
|
|
|
|
bCon->setLabel(loopTop);
|
|
|
|
bCon->emitOp(eForValue, p->pos);
|
|
|
|
Reference *v = NULL;
|
|
if (f->initializer->getKind() == StmtNode::Var) {
|
|
VariableStmtNode *vs = checked_cast<VariableStmtNode *>(f->initializer);
|
|
VariableBinding *vb = vs->bindings;
|
|
v = new (*referenceArena) LexicalReference(new (this) Multiname(*vb->name), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(v);
|
|
}
|
|
else {
|
|
if (f->initializer->getKind() == StmtNode::expression) {
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(f->initializer);
|
|
v = SetupExprNode(env, phase, e->expr, &exprType);
|
|
if (v == NULL)
|
|
reportError(Exception::semanticError, "for..in needs an lValue", p->pos);
|
|
}
|
|
else
|
|
NOT_REACHED("what else??");
|
|
}
|
|
switch (v->hasStackEffect()) {
|
|
case 1:
|
|
bCon->emitOp(eSwap, p->pos);
|
|
break;
|
|
case 2:
|
|
bCon->emitOp(eSwap2, p->pos);
|
|
break;
|
|
}
|
|
v->emitWriteBytecode(bCon, p->pos);
|
|
bCon->emitOp(ePop, p->pos); // clear iterator value from stack
|
|
targetList.push_back(p);
|
|
SetupStmt(env, phase, f->stmt);
|
|
targetList.pop_back();
|
|
bCon->setLabel(f->continueLabelID);
|
|
bCon->emitOp(eNext, p->pos);
|
|
bCon->emitBranch(eBranchTrue, loopTop, p->pos);
|
|
bCon->setLabel(f->breakLabelID);
|
|
bCon->emitOp(ePop, p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::For:
|
|
{
|
|
ForStmtNode *f = checked_cast<ForStmtNode *>(p);
|
|
BytecodeContainer::LabelID loopTop = bCon->getLabel();
|
|
BytecodeContainer::LabelID testLocation = bCon->getLabel();
|
|
|
|
if (f->initializer)
|
|
SetupStmt(env, phase, f->initializer);
|
|
if (f->expr2)
|
|
bCon->emitBranch(eBranch, testLocation, p->pos);
|
|
bCon->setLabel(loopTop);
|
|
targetList.push_back(p);
|
|
SetupStmt(env, phase, f->stmt);
|
|
targetList.pop_back();
|
|
bCon->setLabel(f->continueLabelID);
|
|
if (f->expr3) {
|
|
Reference *r = SetupExprNode(env, phase, f->expr3, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
}
|
|
bCon->setLabel(testLocation);
|
|
if (f->expr2) {
|
|
Reference *r = SetupExprNode(env, phase, f->expr2, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranchTrue, loopTop, p->pos);
|
|
}
|
|
else
|
|
bCon->emitBranch(eBranch, loopTop, p->pos);
|
|
bCon->setLabel(f->breakLabelID);
|
|
}
|
|
break;
|
|
case StmtNode::Switch:
|
|
/*
|
|
<swexpr>
|
|
eSlotWrite <switchTemp>
|
|
|
|
// test sequence in source order except
|
|
// the default is moved to end.
|
|
|
|
eSlotRead <switchTemp>
|
|
<case1expr>
|
|
Equal
|
|
BranchTrue --> case1StmtLabel
|
|
eLexicalRead <switchTemp>
|
|
<case2expr>
|
|
Equal
|
|
BranchTrue --> case2StmtLabel
|
|
Branch --> default, if there is one, or break label
|
|
|
|
case1StmtLabel:
|
|
<stmt>
|
|
case2StmtLabel:
|
|
<stmt>
|
|
defaultLabel:
|
|
<stmt>
|
|
case3StmtLabel:
|
|
<stmt>
|
|
..etc.. // all in source order
|
|
|
|
breakLabel:
|
|
*/
|
|
{
|
|
SwitchStmtNode *sw = checked_cast<SwitchStmtNode *>(p);
|
|
FrameListIterator fi = env->getRegionalFrame();
|
|
NonWithFrame *regionalFrame = checked_cast<NonWithFrame *>(*fi);
|
|
if (regionalFrame->kind == ParameterFrameKind)
|
|
regionalFrame = checked_cast<NonWithFrame *>(*--fi);
|
|
FrameVariable *frV = makeFrameVariable(regionalFrame);
|
|
ASSERT(frV->kind != FrameVariable::Parameter);
|
|
Reference *switchTemp;
|
|
if (frV->kind == FrameVariable::Package)
|
|
switchTemp = new (*referenceArena) PackageSlotReference(frV->frameSlot);
|
|
else
|
|
switchTemp = new (*referenceArena) FrameSlotReference(frV->frameSlot);
|
|
BytecodeContainer::LabelID defaultLabel = NotALabel;
|
|
|
|
Reference *r = SetupExprNode(env, phase, sw->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
switchTemp->emitWriteBytecode(bCon, p->pos);
|
|
bCon->emitOp(ePopv, p->pos);
|
|
|
|
|
|
// First time through, generate the conditional waterfall
|
|
StmtNode *s = sw->statements;
|
|
while (s) {
|
|
if (s->getKind() == StmtNode::Case) {
|
|
ExprStmtNode *c = checked_cast<ExprStmtNode *>(s);
|
|
if (c->expr) {
|
|
switchTemp->emitReadBytecode(bCon, p->pos);
|
|
Reference *r = SetupExprNode(env, phase, c->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, c->pos);
|
|
bCon->emitOp(eIdentical, c->pos);
|
|
bCon->emitBranch(eBranchTrue, c->labelID, c->pos);
|
|
}
|
|
else
|
|
defaultLabel = c->labelID;
|
|
}
|
|
s = s->next;
|
|
}
|
|
if (defaultLabel == NotALabel)
|
|
bCon->emitBranch(eBranch, sw->breakLabelID, p->pos);
|
|
else
|
|
bCon->emitBranch(eBranch, defaultLabel, p->pos);
|
|
// Now emit the contents
|
|
targetList.push_back(p);
|
|
s = sw->statements;
|
|
while (s) {
|
|
if (s->getKind() == StmtNode::Case) {
|
|
ExprStmtNode *c = checked_cast<ExprStmtNode *>(s);
|
|
bCon->setLabel(c->labelID);
|
|
}
|
|
else
|
|
SetupStmt(env, phase, s);
|
|
s = s->next;
|
|
}
|
|
targetList.pop_back();
|
|
|
|
bCon->setLabel(sw->breakLabelID);
|
|
delete frV;
|
|
}
|
|
break;
|
|
case StmtNode::While:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(p);
|
|
BytecodeContainer::LabelID loopTop = bCon->getLabel();
|
|
bCon->emitBranch(eBranch, w->continueLabelID, p->pos);
|
|
bCon->setLabel(loopTop);
|
|
targetList.push_back(p);
|
|
SetupStmt(env, phase, w->stmt);
|
|
targetList.pop_back();
|
|
bCon->setLabel(w->continueLabelID);
|
|
Reference *r = SetupExprNode(env, phase, w->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranchTrue, loopTop, p->pos);
|
|
bCon->setLabel(w->breakLabelID);
|
|
}
|
|
break;
|
|
case StmtNode::DoWhile:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(p);
|
|
BytecodeContainer::LabelID loopTop = bCon->getLabel();
|
|
bCon->setLabel(loopTop);
|
|
targetList.push_back(p);
|
|
SetupStmt(env, phase, w->stmt);
|
|
targetList.pop_back();
|
|
bCon->setLabel(w->continueLabelID);
|
|
Reference *r = SetupExprNode(env, phase, w->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranchTrue, loopTop, p->pos);
|
|
bCon->setLabel(w->breakLabelID);
|
|
}
|
|
break;
|
|
case StmtNode::Throw:
|
|
{
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(p);
|
|
Reference *r = SetupExprNode(env, phase, e->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eThrow, p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::Try:
|
|
// Your logic is insane and happenstance, like that of a troll
|
|
/*
|
|
try { // [catchLabel,finallyInvoker] handler labels are pushed on handler stack [eTry]
|
|
<tryblock>
|
|
} // catch handler label is popped off handler stack [eHandler]
|
|
jsr finally
|
|
jump-->finished
|
|
|
|
finally: // finally handler label popped off here.
|
|
{ // A throw from in here goes to the next handler on the
|
|
// handler stack
|
|
}
|
|
rts
|
|
|
|
finallyInvoker: // invoked when an exception is caught in the try block
|
|
push exception // it arranges to call the finally block and then re-throw
|
|
jsr finally // the exception - reaching the catch block
|
|
throw exception
|
|
catchLabel:
|
|
|
|
the incoming exception is on the top of the stack at this point
|
|
|
|
catch (exception) { // catch handler label popped off [eHandler]
|
|
// any throw from in here must jump to the next handler
|
|
// (i.e. not the this same catch handler!)
|
|
|
|
Of the many catch clauses specified, only the one whose exception variable type
|
|
matches the type of the incoming exception is executed...
|
|
|
|
dup
|
|
push type of exception-variable
|
|
is
|
|
jumpfalse-->next catch
|
|
|
|
setlocalvar exception-variable
|
|
pop
|
|
<catch body>
|
|
|
|
|
|
}
|
|
// 'normal' fall thru from catch
|
|
jsr finally
|
|
jump finished
|
|
|
|
finished:
|
|
*/
|
|
{
|
|
TryStmtNode *t = checked_cast<TryStmtNode *>(p);
|
|
BytecodeContainer::LabelID catchClauseLabel;
|
|
BytecodeContainer::LabelID finallyInvokerLabel;
|
|
BytecodeContainer::LabelID t_finallyLabel;
|
|
bCon->emitOp(eTry, p->pos);
|
|
if (t->finally) {
|
|
finallyInvokerLabel = bCon->getLabel();
|
|
bCon->addFixup(finallyInvokerLabel);
|
|
t_finallyLabel = bCon->getLabel();
|
|
}
|
|
else {
|
|
finallyInvokerLabel = NotALabel;
|
|
bCon->addOffset(NotALabel);
|
|
t_finallyLabel = NotALabel;
|
|
}
|
|
if (t->catches) {
|
|
catchClauseLabel = bCon->getLabel();
|
|
bCon->addFixup(catchClauseLabel);
|
|
}
|
|
else {
|
|
catchClauseLabel = NotALabel;
|
|
bCon->addOffset(NotALabel);
|
|
}
|
|
BytecodeContainer::LabelID finishedLabel = bCon->getLabel();
|
|
SetupStmt(env, phase, t->stmt);
|
|
|
|
if (t->finally) {
|
|
bCon->emitBranch(eCallFinally, t_finallyLabel, p->pos);
|
|
bCon->emitBranch(eBranch, finishedLabel, p->pos);
|
|
|
|
bCon->setLabel(t_finallyLabel);
|
|
bCon->emitOp(eHandler, p->pos);
|
|
SetupStmt(env, phase, t->finally);
|
|
bCon->emitOp(eReturnFinally, p->pos);
|
|
|
|
bCon->setLabel(finallyInvokerLabel);
|
|
// the exception object is on the top of the stack already
|
|
bCon->emitBranch(eCallFinally, t_finallyLabel, p->pos);
|
|
ASSERT(bCon->mStackTop == 0);
|
|
bCon->mStackTop = 1;
|
|
bCon->emitOp(eThrow, p->pos);
|
|
}
|
|
else {
|
|
bCon->emitBranch(eBranch, finishedLabel, p->pos);
|
|
}
|
|
|
|
if (t->catches) {
|
|
bCon->setLabel(catchClauseLabel);
|
|
bCon->emitOp(eHandler, p->pos);
|
|
CatchClause *c = t->catches;
|
|
// the exception object will be the only thing on the stack
|
|
// ASSERT(bCon->mStackTop == 0);
|
|
bCon->mStackTop = 1;
|
|
if (bCon->mStackMax < 1) bCon->mStackMax = 1;
|
|
BytecodeContainer::LabelID nextCatch = NotALabel;
|
|
while (c) {
|
|
if (c->next && c->type) {
|
|
nextCatch = bCon->getLabel();
|
|
bCon->emitOp(eDup, p->pos);
|
|
Reference *r = SetupExprNode(env, phase, c->type, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eIs, p->pos);
|
|
bCon->emitBranch(eBranchFalse, nextCatch, p->pos);
|
|
}
|
|
// write the exception object (on stack top) into the named
|
|
// local variable
|
|
Reference *r = new (*referenceArena) LexicalReference(new (this) Multiname(c->name), false, bCon);
|
|
referenceArena->registerDestructor(r);
|
|
r->emitWriteBytecode(bCon, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
SetupStmt(env, phase, c->stmt);
|
|
if (t->finally) {
|
|
bCon->emitBranch(eCallFinally, t_finallyLabel, p->pos);
|
|
}
|
|
c = c->next;
|
|
if (c) {
|
|
bCon->emitBranch(eBranch, finishedLabel, p->pos);
|
|
bCon->mStackTop = 1;
|
|
if (nextCatch != NotALabel)
|
|
bCon->setLabel(nextCatch);
|
|
}
|
|
}
|
|
}
|
|
bCon->setLabel(finishedLabel);
|
|
}
|
|
break;
|
|
case StmtNode::Return:
|
|
{
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(p);
|
|
if (e->expr) {
|
|
Reference *r = SetupExprNode(env, phase, e->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eReturn, p->pos);
|
|
}
|
|
else
|
|
bCon->emitOp(eReturnVoid, p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::Function:
|
|
{
|
|
FunctionStmtNode *f = checked_cast<FunctionStmtNode *>(p);
|
|
CompilationData *oldData = NULL;
|
|
FunctionInstance *fnInst = f->function.fn;
|
|
try {
|
|
oldData = startCompilationUnit(fnInst->fWrap->bCon, bCon->mSource, bCon->mSourceLocation);
|
|
env->addFrame(fnInst->fWrap->compileFrame);
|
|
bCon->fName = *f->function.name;
|
|
VariableBinding *pb = f->function.parameters;
|
|
while (pb) {
|
|
if (pb->member) {
|
|
FrameVariable *v = checked_cast<FrameVariable *>(pb->member);
|
|
if (pb->type)
|
|
v->type = EvalTypeExpression(env, CompilePhase, pb->type);
|
|
else
|
|
v->type = objectClass;
|
|
}
|
|
pb = pb->next;
|
|
}
|
|
if (f->function.resultType)
|
|
fnInst->fWrap->resultType = EvalTypeExpression(env, CompilePhase, f->function.resultType);
|
|
else
|
|
fnInst->fWrap->resultType = objectClass;
|
|
|
|
SetupStmt(env, phase, f->function.body);
|
|
// XXX need to make sure that all paths lead to an exit of some kind
|
|
bCon->emitOp(eReturnVoid, p->pos);
|
|
env->removeTopFrame();
|
|
restoreCompilationUnit(oldData);
|
|
}
|
|
catch (Exception &x) {
|
|
if (oldData)
|
|
restoreCompilationUnit(oldData);
|
|
throw x;
|
|
}
|
|
bCon->emitOp(eClosure, p->pos);
|
|
bCon->addObject(fnInst);
|
|
}
|
|
break;
|
|
case StmtNode::Var:
|
|
case StmtNode::Const:
|
|
{
|
|
// Note that the code here is the Setup code plus the emit of the Eval bytecode
|
|
VariableStmtNode *vs = checked_cast<VariableStmtNode *>(p);
|
|
VariableBinding *vb = vs->bindings;
|
|
while (vb) {
|
|
if (vb->member) { // static or instance variable
|
|
if (vb->member->memberKind == Member::VariableMember) {
|
|
Variable *v = checked_cast<Variable *>(vb->member);
|
|
JS2Class *type = getVariableType(v, CompilePhase, p->pos);
|
|
if (JS2VAL_IS_FUTURE(v->value)) { // it's a const, execute the initializer
|
|
v->value = JS2VAL_INACCESSIBLE;
|
|
if (vb->initializer) {
|
|
try {
|
|
js2val newValue = EvalExpression(env, CompilePhase, vb->initializer);
|
|
v->value = type->ImplicitCoerce(this, newValue);
|
|
}
|
|
catch (Exception x) {
|
|
// If a compileExpressionError occurred, then the initialiser is
|
|
// not a compile-time constant expression. In this case, ignore the
|
|
// error and leave the value of the variable INACCESSIBLE until it
|
|
// is defined at run time.
|
|
if (x.kind != Exception::compileExpressionError)
|
|
throw x;
|
|
Reference *r = SetupExprNode(env, phase, vb->initializer, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
LexicalReference *lVal = new (*referenceArena) LexicalReference(new (this) Multiname(vb->mn), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(lVal);
|
|
lVal->emitWriteBytecode(bCon, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
}
|
|
}
|
|
else
|
|
// Would only have come here if the variable was immutable - i.e. a 'const' definition
|
|
// XXX why isn't this handled at validation-time?
|
|
reportError(Exception::compileExpressionError, "Missing compile time expression", p->pos);
|
|
}
|
|
else {
|
|
// Not immutable
|
|
ASSERT(JS2VAL_IS_INACCESSIBLE(v->value));
|
|
if (vb->initializer) {
|
|
Reference *r = SetupExprNode(env, phase, vb->initializer, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eCoerce, p->pos);
|
|
bCon->addType(v->type);
|
|
LexicalReference *lVal = new (*referenceArena) LexicalReference(new (this) Multiname(vb->mn), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(lVal);
|
|
lVal->emitInitBytecode(bCon, p->pos);
|
|
}
|
|
else {
|
|
v->type->emitDefaultValue(bCon, p->pos);
|
|
LexicalReference *lVal = new (*referenceArena) LexicalReference(new (this) Multiname(vb->mn), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(lVal);
|
|
lVal->emitInitBytecode(bCon, p->pos);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
InstanceVariable *v = checked_cast<InstanceVariable *>(vb->member);
|
|
JS2Class *t;
|
|
if (vb->type)
|
|
t = EvalTypeExpression(env, CompilePhase, vb->type);
|
|
else {
|
|
if (vb->overridden) {
|
|
switch (vb->overridden->memberKind) {
|
|
case Member::InstanceVariableMember:
|
|
t = checked_cast<InstanceVariable *>(vb->overridden)->type;
|
|
break;
|
|
case Member::InstanceGetterMember:
|
|
t = checked_cast<InstanceGetter *>(vb->overridden)->type;
|
|
break;
|
|
case Member::InstanceSetterMember:
|
|
t = checked_cast<InstanceSetter *>(vb->overridden)->type;
|
|
break;
|
|
case Member::InstanceMethodMember:
|
|
//t = checked_cast<InstanceMethod *>(vb->overridden)->type;
|
|
t = objectClass;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
t = objectClass;
|
|
}
|
|
v->type = t;
|
|
if (vb->initializer) {
|
|
js2val newValue = EvalExpression(env, CompilePhase, vb->initializer);
|
|
v->defaultValue = t->ImplicitCoerce(this, newValue);
|
|
}
|
|
else
|
|
v->defaultValue = t->defaultValue;
|
|
}
|
|
}
|
|
else { // HoistedVariable
|
|
if (vb->initializer) {
|
|
Reference *r = SetupExprNode(env, phase, vb->initializer, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
LexicalReference *lVal = new (*referenceArena) LexicalReference(new (this) Multiname(*vb->name), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(lVal);
|
|
lVal->variableMultiname->addNamespace(publicNamespace);
|
|
lVal->emitInitBytecode(bCon, p->pos);
|
|
}
|
|
}
|
|
vb = vb->next;
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::expression:
|
|
{
|
|
ExprStmtNode *e = checked_cast<ExprStmtNode *>(p);
|
|
Reference *r = SetupExprNode(env, phase, e->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
// superStmt expressions don't produce any result value
|
|
if (e->expr->getKind() != ExprNode::superStmt)
|
|
bCon->emitOp(ePopv, p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::Namespace:
|
|
{
|
|
}
|
|
break;
|
|
case StmtNode::Use:
|
|
{
|
|
}
|
|
break;
|
|
case StmtNode::Class:
|
|
{
|
|
ClassStmtNode *classStmt = checked_cast<ClassStmtNode *>(p);
|
|
JS2Class *c = classStmt->c;
|
|
if (classStmt->body) {
|
|
env->addFrame(c);
|
|
bCon->emitOp(ePushFrame, p->pos);
|
|
bCon->addFrame(c);
|
|
StmtNode *bp = classStmt->body->statements;
|
|
while (bp) {
|
|
SetupStmt(env, phase, bp);
|
|
bp = bp->next;
|
|
}
|
|
ASSERT(env->getTopFrame() == c);
|
|
env->removeTopFrame();
|
|
bCon->emitOp(ePopFrame, p->pos);
|
|
}
|
|
}
|
|
break;
|
|
case StmtNode::With:
|
|
{
|
|
UnaryStmtNode *w = checked_cast<UnaryStmtNode *>(p);
|
|
Reference *r = SetupExprNode(env, phase, w->expr, &exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eWithin, p->pos);
|
|
if (w->stmt->getKind() != StmtNode::block) {
|
|
env->addFrame(w->compileFrame);
|
|
bCon->emitOp(ePushFrame, p->pos);
|
|
bCon->addFrame(w->compileFrame);
|
|
SetupStmt(env, phase, w->stmt);
|
|
bCon->emitOp(ePopFrame, p->pos);
|
|
env->removeTopFrame();
|
|
}
|
|
else
|
|
SetupStmt(env, phase, w->stmt);
|
|
bCon->emitOp(eWithout, p->pos);
|
|
}
|
|
break;
|
|
case StmtNode::empty:
|
|
break;
|
|
case StmtNode::Import:
|
|
break;
|
|
case StmtNode::Package:
|
|
break;
|
|
default:
|
|
NOT_REACHED("Not Yet Implemented");
|
|
} // switch (p->getKind())
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Attributes
|
|
*
|
|
************************************************************************************/
|
|
|
|
//
|
|
// Validate the Attribute expression at p
|
|
// An attribute expression can only be a list of 'juxtaposed' attribute elements
|
|
//
|
|
// Note : "AttributeExpression" here is a different beast than in the spec. - here it
|
|
// describes the entire attribute part of a directive, not just the qualified identifier
|
|
// and other references encountered in an attribute.
|
|
//
|
|
void JS2Metadata::ValidateAttributeExpression(Context *cxt, Environment *env, ExprNode *p)
|
|
{
|
|
switch (p->getKind()) {
|
|
case ExprNode::boolean:
|
|
break;
|
|
case ExprNode::juxtapose:
|
|
{
|
|
BinaryExprNode *j = checked_cast<BinaryExprNode *>(p);
|
|
ValidateAttributeExpression(cxt, env, j->op1);
|
|
ValidateAttributeExpression(cxt, env, j->op2);
|
|
}
|
|
break;
|
|
case ExprNode::identifier:
|
|
{
|
|
const StringAtom &name = checked_cast<IdentifierExprNode *>(p)->name;
|
|
switch (name.tokenKind) {
|
|
case Token::Public:
|
|
return;
|
|
case Token::Abstract:
|
|
return;
|
|
case Token::Final:
|
|
return;
|
|
case Token::Private:
|
|
{
|
|
JS2Class *c = env->getEnclosingClass();
|
|
if (!c)
|
|
reportError(Exception::syntaxError, "Private can only be used inside a class definition", p->pos);
|
|
}
|
|
return;
|
|
case Token::Static:
|
|
return;
|
|
}
|
|
// fall thru to handle as generic expression element...
|
|
}
|
|
default:
|
|
{
|
|
ValidateExpression(cxt, env, p);
|
|
}
|
|
break;
|
|
|
|
} // switch (p->getKind())
|
|
}
|
|
|
|
// Evaluate the Attribute expression rooted at p.
|
|
// An attribute expression can only be a list of 'juxtaposed' attribute elements
|
|
Attribute *JS2Metadata::EvalAttributeExpression(Environment *env, Phase phase, ExprNode *p)
|
|
{
|
|
switch (p->getKind()) {
|
|
case ExprNode::boolean:
|
|
if (checked_cast<BooleanExprNode *>(p)->value)
|
|
return new (this) TrueAttribute();
|
|
else
|
|
return new (this) FalseAttribute();
|
|
case ExprNode::juxtapose:
|
|
{
|
|
BinaryExprNode *j = checked_cast<BinaryExprNode *>(p);
|
|
Attribute *a = EvalAttributeExpression(env, phase, j->op1);
|
|
if (a && (a->attrKind == Attribute::FalseAttr))
|
|
return a;
|
|
Attribute *b = EvalAttributeExpression(env, phase, j->op2);
|
|
try {
|
|
return Attribute::combineAttributes(this, a, b);
|
|
}
|
|
catch (char *err) {
|
|
reportError(Exception::badValueError, err, p->pos);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ExprNode::identifier:
|
|
{
|
|
const StringAtom &name = checked_cast<IdentifierExprNode *>(p)->name;
|
|
CompoundAttribute *ca = NULL;
|
|
switch (name.tokenKind) {
|
|
case Token::Public:
|
|
return publicNamespace;
|
|
case Token::Final:
|
|
ca = new (this) CompoundAttribute();
|
|
ca->memberMod = Attribute::Final;
|
|
return ca;
|
|
case Token::Private:
|
|
{
|
|
JS2Class *c = env->getEnclosingClass();
|
|
return c->privateNamespace;
|
|
}
|
|
case Token::Static:
|
|
ca = new (this) CompoundAttribute();
|
|
ca->memberMod = Attribute::Static;
|
|
return ca;
|
|
case Token::identifier:
|
|
if (name == world.identifiers["override"]) {
|
|
ca = new (this) CompoundAttribute();
|
|
ca->overrideMod = Attribute::DoOverride;
|
|
return ca;
|
|
}
|
|
else
|
|
if (name == world.identifiers["enumerable"]) {
|
|
ca = new (this) CompoundAttribute();
|
|
ca->enumerable = true;
|
|
return ca;
|
|
}
|
|
else
|
|
if (name == world.identifiers["virtual"]) {
|
|
ca = new (this) CompoundAttribute();
|
|
ca->memberMod = Attribute::Virtual;
|
|
return ca;
|
|
}
|
|
else
|
|
if (name == world.identifiers["dynamic"]) {
|
|
ca = new (this) CompoundAttribute();
|
|
ca->dynamic = true;
|
|
return ca;
|
|
}
|
|
}
|
|
}
|
|
// fall thru to execute a readReference on the identifier...
|
|
default:
|
|
{
|
|
// anything else (just references of one kind or another) must
|
|
// be compile-time constant values that resolve to namespaces
|
|
js2val av = EvalExpression(env, CompilePhase, p);
|
|
if (JS2VAL_IS_NULL(av) || !JS2VAL_IS_OBJECT(av))
|
|
reportError(Exception::badValueError, "Namespace expected in attribute", p->pos);
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(av);
|
|
if ((obj->kind != AttributeObjectKind) || (checked_cast<Attribute *>(obj)->attrKind != Attribute::NamespaceAttr))
|
|
reportError(Exception::badValueError, "Namespace expected in attribute", p->pos);
|
|
return checked_cast<Attribute *>(obj);
|
|
}
|
|
break;
|
|
|
|
} // switch (p->getKind())
|
|
return NULL;
|
|
}
|
|
|
|
// Combine attributes a & b, reporting errors for incompatibilities
|
|
// a is not false
|
|
Attribute *Attribute::combineAttributes(JS2Metadata *meta, Attribute *a, Attribute *b)
|
|
{
|
|
if (b && (b->attrKind == FalseAttr)) {
|
|
if (a) delete a;
|
|
return b;
|
|
}
|
|
if (!a || (a->attrKind == TrueAttr)) {
|
|
if (a) delete a;
|
|
return b;
|
|
}
|
|
if (!b || (b->attrKind == TrueAttr)) {
|
|
if (b) delete b;
|
|
return a;
|
|
}
|
|
if (a->attrKind == NamespaceAttr) {
|
|
if (a == b) {
|
|
delete b;
|
|
return a;
|
|
}
|
|
Namespace *na = checked_cast<Namespace *>(a);
|
|
if (b->attrKind == NamespaceAttr) {
|
|
Namespace *nb = checked_cast<Namespace *>(b);
|
|
CompoundAttribute *c = new (meta) CompoundAttribute();
|
|
c->addNamespace(na);
|
|
c->addNamespace(nb);
|
|
delete a;
|
|
delete b;
|
|
return (Attribute *)c;
|
|
}
|
|
else {
|
|
ASSERT(b->attrKind == CompoundAttr);
|
|
CompoundAttribute *cb = checked_cast<CompoundAttribute *>(b);
|
|
cb->addNamespace(na);
|
|
delete a;
|
|
return b;
|
|
}
|
|
}
|
|
else {
|
|
// Both a and b are compound attributes. Ensure that they have no conflicting contents.
|
|
ASSERT((a->attrKind == CompoundAttr) && (b->attrKind == CompoundAttr));
|
|
CompoundAttribute *ca = checked_cast<CompoundAttribute *>(a);
|
|
CompoundAttribute *cb = checked_cast<CompoundAttribute *>(b);
|
|
if ((ca->memberMod != NoModifier) && (cb->memberMod != NoModifier) && (ca->memberMod != cb->memberMod))
|
|
throw("Illegal combination of member modifier attributes");
|
|
if ((ca->overrideMod != NoOverride) && (cb->overrideMod != NoOverride) && (ca->overrideMod != cb->overrideMod))
|
|
throw("Illegal combination of override attributes");
|
|
for (NamespaceListIterator i = cb->namespaces.begin(), end = cb->namespaces.end(); (i != end); i++)
|
|
ca->addNamespace(*i);
|
|
ca->xplicit |= cb->xplicit;
|
|
ca->dynamic |= cb->dynamic;
|
|
if (ca->memberMod == NoModifier)
|
|
ca->memberMod = cb->memberMod;
|
|
if (ca->overrideMod == NoOverride)
|
|
ca->overrideMod = cb->overrideMod;
|
|
ca->prototype |= cb->prototype;
|
|
ca->unused |= cb->unused;
|
|
delete b;
|
|
return a;
|
|
}
|
|
}
|
|
|
|
// add the namespace to our list, but only if it's not there already
|
|
void CompoundAttribute::addNamespace(Namespace *n)
|
|
{
|
|
for (NamespaceListIterator i = namespaces.begin(), end = namespaces.end(); (i != end); i++)
|
|
if (*i == n)
|
|
return;
|
|
namespaces.push_back(n);
|
|
}
|
|
|
|
CompoundAttribute::CompoundAttribute() : Attribute(CompoundAttr),
|
|
xplicit(false), enumerable(false), dynamic(false), memberMod(NoModifier),
|
|
overrideMod(NoOverride), prototype(false), unused(false)
|
|
{
|
|
}
|
|
|
|
// Convert an attribute to a compoundAttribute. If the attribute
|
|
// is NULL, return a default compoundAttribute
|
|
CompoundAttribute *Attribute::toCompoundAttribute(JS2Metadata *meta, Attribute *a)
|
|
{
|
|
if (a)
|
|
return a->toCompoundAttribute();
|
|
else
|
|
return new (meta) CompoundAttribute();
|
|
}
|
|
|
|
// Convert a simple namespace to a compoundAttribute with that namespace
|
|
CompoundAttribute *Namespace::toCompoundAttribute(JS2Metadata *meta)
|
|
{
|
|
CompoundAttribute *t = new (meta) CompoundAttribute();
|
|
t->addNamespace(this);
|
|
return t;
|
|
}
|
|
|
|
// Convert a 'true' attribute to a default compoundAttribute
|
|
CompoundAttribute *TrueAttribute::toCompoundAttribute(JS2Metadata *meta)
|
|
{
|
|
return new (meta) CompoundAttribute();
|
|
}
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void CompoundAttribute::markChildren()
|
|
{
|
|
for (NamespaceListIterator i = namespaces.begin(), end = namespaces.end(); (i != end); i++) {
|
|
GCMARKOBJECT(*i)
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Expressions
|
|
*
|
|
************************************************************************************/
|
|
|
|
// Validate the entire expression rooted at p
|
|
void JS2Metadata::ValidateExpression(Context *cxt, Environment *env, ExprNode *p)
|
|
{
|
|
switch (p->getKind()) {
|
|
case ExprNode::Null:
|
|
case ExprNode::number:
|
|
case ExprNode::regExp:
|
|
case ExprNode::numUnit:
|
|
case ExprNode::string:
|
|
case ExprNode::boolean:
|
|
break;
|
|
case ExprNode::This:
|
|
{
|
|
js2val thisVal;
|
|
ParameterFrame *pFrame = env->getEnclosingParameterFrame(&thisVal);
|
|
if ((pFrame == NULL) || (thisVal == JS2VAL_VOID))
|
|
if (!cxt->E3compatibility)
|
|
reportError(Exception::syntaxError, "No 'this' available", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::superExpr:
|
|
{
|
|
SuperExprNode *s = checked_cast<SuperExprNode *>(p);
|
|
JS2Class *c = env->getEnclosingClass();
|
|
if (s->op) {
|
|
if (c == NULL)
|
|
reportError(Exception::syntaxError, "No 'super' available", p->pos);
|
|
ValidateExpression(cxt, env, s->op);
|
|
}
|
|
else {
|
|
ParameterFrame *pFrame = env->getEnclosingParameterFrame(NULL);
|
|
if ((c == NULL) || (pFrame == NULL) || !(pFrame->isConstructor || pFrame->isInstance))
|
|
reportError(Exception::syntaxError, "No 'super' available", p->pos);
|
|
if (c->super == NULL)
|
|
reportError(Exception::definitionError, "No 'super' for this class", p->pos);
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::objectLiteral:
|
|
{
|
|
PairListExprNode *plen = checked_cast<PairListExprNode *>(p);
|
|
ExprPairList *e = plen->pairs;
|
|
while (e) {
|
|
ASSERT(e->field && e->value);
|
|
ValidateExpression(cxt, env, e->value);
|
|
e = e->next;
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::arrayLiteral:
|
|
{
|
|
PairListExprNode *plen = checked_cast<PairListExprNode *>(p);
|
|
ExprPairList *e = plen->pairs;
|
|
while (e) {
|
|
if (e->value)
|
|
ValidateExpression(cxt, env, e->value);
|
|
e = e->next;
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::index:
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
ValidateExpression(cxt, env, i->op);
|
|
ExprPairList *ep = i->pairs;
|
|
uint16 positionalCount = 0;
|
|
// XXX errors below should only occur at runtime - insert code to throw exception
|
|
// or let the bytecodes handle (and throw on) multiple & named arguments?
|
|
while (ep) {
|
|
if (ep->field)
|
|
reportError(Exception::argumentMismatchError, "Indexing doesn't support named arguments", p->pos);
|
|
else {
|
|
if (positionalCount)
|
|
reportError(Exception::argumentMismatchError, "Indexing doesn't support more than 1 argument", p->pos);
|
|
positionalCount++;
|
|
ValidateExpression(cxt, env, ep->value);
|
|
}
|
|
ep = ep->next;
|
|
}
|
|
if (!positionalCount)
|
|
reportError(Exception::argumentMismatchError, "Indexing requires at least 1 argument", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::dot:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
ValidateExpression(cxt, env, b->op1);
|
|
ValidateExpression(cxt, env, b->op2);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::lessThan:
|
|
case ExprNode::lessThanOrEqual:
|
|
case ExprNode::greaterThan:
|
|
case ExprNode::greaterThanOrEqual:
|
|
case ExprNode::equal:
|
|
case ExprNode::notEqual:
|
|
case ExprNode::assignment:
|
|
case ExprNode::add:
|
|
case ExprNode::subtract:
|
|
case ExprNode::multiply:
|
|
case ExprNode::divide:
|
|
case ExprNode::modulo:
|
|
case ExprNode::addEquals:
|
|
case ExprNode::subtractEquals:
|
|
case ExprNode::multiplyEquals:
|
|
case ExprNode::divideEquals:
|
|
case ExprNode::moduloEquals:
|
|
case ExprNode::logicalAnd:
|
|
case ExprNode::logicalXor:
|
|
case ExprNode::logicalOr:
|
|
case ExprNode::leftShift:
|
|
case ExprNode::rightShift:
|
|
case ExprNode::logicalRightShift:
|
|
case ExprNode::bitwiseAnd:
|
|
case ExprNode::bitwiseXor:
|
|
case ExprNode::bitwiseOr:
|
|
case ExprNode::leftShiftEquals:
|
|
case ExprNode::rightShiftEquals:
|
|
case ExprNode::logicalRightShiftEquals:
|
|
case ExprNode::bitwiseAndEquals:
|
|
case ExprNode::bitwiseXorEquals:
|
|
case ExprNode::bitwiseOrEquals:
|
|
case ExprNode::logicalAndEquals:
|
|
case ExprNode::logicalXorEquals:
|
|
case ExprNode::logicalOrEquals:
|
|
case ExprNode::comma:
|
|
case ExprNode::Instanceof:
|
|
case ExprNode::identical:
|
|
case ExprNode::notIdentical:
|
|
case ExprNode::In:
|
|
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
ValidateExpression(cxt, env, b->op1);
|
|
ValidateExpression(cxt, env, b->op2);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::Delete:
|
|
case ExprNode::minus:
|
|
case ExprNode::plus:
|
|
case ExprNode::complement:
|
|
case ExprNode::postIncrement:
|
|
case ExprNode::postDecrement:
|
|
case ExprNode::preIncrement:
|
|
case ExprNode::preDecrement:
|
|
case ExprNode::parentheses:
|
|
case ExprNode::Typeof:
|
|
case ExprNode::logicalNot:
|
|
case ExprNode::Void:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
ValidateExpression(cxt, env, u->op);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::conditional:
|
|
{
|
|
TernaryExprNode *c = checked_cast<TernaryExprNode *>(p);
|
|
ValidateExpression(cxt, env, c->op1);
|
|
ValidateExpression(cxt, env, c->op2);
|
|
ValidateExpression(cxt, env, c->op3);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::qualify:
|
|
case ExprNode::identifier:
|
|
{
|
|
// IdentifierExprNode *i = checked_cast<IdentifierExprNode *>(p);
|
|
}
|
|
break;
|
|
case ExprNode::call:
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
ValidateExpression(cxt, env, i->op);
|
|
ExprPairList *args = i->pairs;
|
|
while (args) {
|
|
ValidateExpression(cxt, env, args->value);
|
|
args = args->next;
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::New:
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
ValidateExpression(cxt, env, i->op);
|
|
ExprPairList *args = i->pairs;
|
|
while (args) {
|
|
ValidateExpression(cxt, env, args->value);
|
|
args = args->next;
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::functionLiteral:
|
|
{
|
|
FunctionExprNode *f = checked_cast<FunctionExprNode *>(p);
|
|
validateStaticFunction(cxt, env, &f->function, true, true, false, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::superStmt:
|
|
{
|
|
ParameterFrame *pFrame = env->getEnclosingParameterFrame(NULL);
|
|
if ((pFrame == NULL) || !pFrame->isConstructor)
|
|
reportError(Exception::syntaxError, "A super statement is meaningful only inside a constructor", p->pos);
|
|
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
ExprPairList *args = i->pairs;
|
|
while (args) {
|
|
ValidateExpression(cxt, env, args->value);
|
|
args = args->next;
|
|
}
|
|
pFrame->callsSuperConstructor = true;
|
|
}
|
|
break;
|
|
default:
|
|
NOT_REACHED("Not Yet Implemented");
|
|
} // switch (p->getKind())
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Process the expression (i.e. generate bytecode, but don't execute) rooted at p.
|
|
*/
|
|
Reference *JS2Metadata::SetupExprNode(Environment *env, Phase phase, ExprNode *p, JS2Class **exprType)
|
|
{
|
|
Reference *returnRef = NULL;
|
|
*exprType = NULL;
|
|
JS2Op op;
|
|
|
|
switch (p->getKind()) {
|
|
|
|
case ExprNode::parentheses:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
returnRef = SetupExprNode(env, phase, u->op, exprType);
|
|
}
|
|
break;
|
|
case ExprNode::assignment:
|
|
{
|
|
if (phase == CompilePhase) reportError(Exception::compileExpressionError, "Inappropriate compile time expression", p->pos);
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
JS2Class *l_exprType = *exprType;
|
|
if (lVal) {
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
lVal->emitWriteBytecode(bCon, p->pos);
|
|
*exprType = l_exprType;
|
|
}
|
|
else
|
|
reportError(Exception::semanticError, "Assignment needs an lValue", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::leftShiftEquals:
|
|
op = eLeftShift;
|
|
goto doAssignBinary;
|
|
case ExprNode::rightShiftEquals:
|
|
op = eRightShift;
|
|
goto doAssignBinary;
|
|
case ExprNode::logicalRightShiftEquals:
|
|
op = eLogicalRightShift;
|
|
goto doAssignBinary;
|
|
case ExprNode::bitwiseAndEquals:
|
|
op = eBitwiseAnd;
|
|
goto doAssignBinary;
|
|
case ExprNode::bitwiseXorEquals:
|
|
op = eBitwiseXor;
|
|
goto doAssignBinary;
|
|
case ExprNode::logicalXorEquals:
|
|
op = eLogicalXor;
|
|
goto doAssignBinary;
|
|
case ExprNode::bitwiseOrEquals:
|
|
op = eBitwiseOr;
|
|
goto doAssignBinary;
|
|
case ExprNode::addEquals:
|
|
op = eAdd;
|
|
goto doAssignBinary;
|
|
case ExprNode::subtractEquals:
|
|
op = eSubtract;
|
|
goto doAssignBinary;
|
|
case ExprNode::multiplyEquals:
|
|
op = eMultiply;
|
|
goto doAssignBinary;
|
|
case ExprNode::divideEquals:
|
|
op = eDivide;
|
|
goto doAssignBinary;
|
|
case ExprNode::moduloEquals:
|
|
op = eModulo;
|
|
goto doAssignBinary;
|
|
doAssignBinary:
|
|
{
|
|
if (phase == CompilePhase) reportError(Exception::compileExpressionError, "Inappropriate compile time expression", p->pos);
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
JS2Class *l_exprType = *exprType;
|
|
if (lVal) {
|
|
lVal->emitReadForWriteBackBytecode(bCon, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
*exprType = l_exprType;
|
|
}
|
|
else
|
|
reportError(Exception::semanticError, "Assignment needs an lValue", p->pos);
|
|
bCon->emitOp(op, p->pos);
|
|
lVal->emitWriteBackBytecode(bCon, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::lessThan:
|
|
op = eLess;
|
|
goto boolBinary;
|
|
case ExprNode::lessThanOrEqual:
|
|
op = eLessEqual;
|
|
goto boolBinary;
|
|
case ExprNode::greaterThan:
|
|
op = eGreater;
|
|
goto boolBinary;
|
|
case ExprNode::greaterThanOrEqual:
|
|
op = eGreaterEqual;
|
|
goto boolBinary;
|
|
case ExprNode::equal:
|
|
op = eEqual;
|
|
goto boolBinary;
|
|
case ExprNode::notEqual:
|
|
op = eNotEqual;
|
|
goto boolBinary;
|
|
case ExprNode::identical:
|
|
op = eIdentical;
|
|
goto boolBinary;
|
|
case ExprNode::notIdentical:
|
|
op = eNotIdentical;
|
|
goto boolBinary;
|
|
boolBinary:
|
|
*exprType = booleanClass;
|
|
goto doBinary;
|
|
|
|
case ExprNode::leftShift:
|
|
op = eLeftShift;
|
|
goto doBinary;
|
|
case ExprNode::rightShift:
|
|
op = eRightShift;
|
|
goto doBinary;
|
|
case ExprNode::logicalRightShift:
|
|
op = eLogicalRightShift;
|
|
goto doBinary;
|
|
case ExprNode::bitwiseAnd:
|
|
op = eBitwiseAnd;
|
|
goto doBinary;
|
|
case ExprNode::bitwiseXor:
|
|
op = eBitwiseXor;
|
|
goto doBinary;
|
|
case ExprNode::bitwiseOr:
|
|
op = eBitwiseOr;
|
|
goto doBinary;
|
|
|
|
case ExprNode::add:
|
|
op = eAdd;
|
|
goto doBinary;
|
|
case ExprNode::subtract:
|
|
op = eSubtract;
|
|
goto doBinary;
|
|
case ExprNode::multiply:
|
|
op = eMultiply;
|
|
goto doBinary;
|
|
case ExprNode::divide:
|
|
op = eDivide;
|
|
goto doBinary;
|
|
case ExprNode::modulo:
|
|
op = eModulo;
|
|
goto doBinary;
|
|
doBinary:
|
|
{
|
|
JS2Class *l_exprType, *r_exprType;
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, &l_exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, &r_exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(op, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::Void:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
SetupExprNode(env, phase, u->op, exprType);
|
|
bCon->emitOp(eVoid, p->pos);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::logicalNot:
|
|
op = eLogicalNot;
|
|
goto doUnary;
|
|
case ExprNode::minus:
|
|
op = eMinus;
|
|
goto doUnary;
|
|
case ExprNode::plus:
|
|
op = ePlus;
|
|
goto doUnary;
|
|
case ExprNode::complement:
|
|
op = eComplement;
|
|
goto doUnary;
|
|
doUnary:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *rVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(op, p->pos);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::logicalAndEquals:
|
|
{
|
|
if (phase == CompilePhase) reportError(Exception::compileExpressionError, "Inappropriate compile time expression", p->pos);
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
BytecodeContainer::LabelID skipOverSecondHalf = bCon->getLabel();
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (lVal)
|
|
lVal->emitReadForWriteBackBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "Assignment needs an lValue", p->pos);
|
|
bCon->emitOp(eDup, p->pos);
|
|
bCon->emitBranch(eBranchFalse, skipOverSecondHalf, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->setLabel(skipOverSecondHalf);
|
|
lVal->emitWriteBackBytecode(bCon, p->pos);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::logicalOrEquals:
|
|
{
|
|
if (phase == CompilePhase) reportError(Exception::compileExpressionError, "Inappropriate compile time expression", p->pos);
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
BytecodeContainer::LabelID skipOverSecondHalf = bCon->getLabel();
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (lVal)
|
|
lVal->emitReadForWriteBackBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "Assignment needs an lValue", p->pos);
|
|
bCon->emitOp(eDup, p->pos);
|
|
bCon->emitBranch(eBranchTrue, skipOverSecondHalf, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->setLabel(skipOverSecondHalf);
|
|
lVal->emitWriteBackBytecode(bCon, p->pos);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::logicalAnd:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
BytecodeContainer::LabelID skipOverSecondHalf = bCon->getLabel();
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eDup, p->pos);
|
|
bCon->emitBranch(eBranchFalse, skipOverSecondHalf, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->setLabel(skipOverSecondHalf);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::logicalXor:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eLogicalXor, p->pos);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::logicalOr:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
BytecodeContainer::LabelID skipOverSecondHalf = bCon->getLabel();
|
|
Reference *lVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eDup, p->pos);
|
|
bCon->emitBranch(eBranchTrue, skipOverSecondHalf, p->pos);
|
|
bCon->emitOp(ePop, p->pos);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->setLabel(skipOverSecondHalf);
|
|
}
|
|
break;
|
|
|
|
case ExprNode::This:
|
|
{
|
|
bCon->emitOp(eThis, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::superExpr:
|
|
{
|
|
SuperExprNode *s = checked_cast<SuperExprNode *>(p);
|
|
if (s->op) {
|
|
Reference *lVal = SetupExprNode(env, phase, s->op, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eSuperExpr, p->pos);
|
|
}
|
|
else
|
|
bCon->emitOp(eSuper, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::Null:
|
|
{
|
|
bCon->emitOp(eNull, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::numUnit:
|
|
{
|
|
NumUnitExprNode *n = checked_cast<NumUnitExprNode *>(p);
|
|
if (n->str.compare(String(widenCString("UL"))) == 0)
|
|
bCon->addUInt64((uint64)(n->num), p->pos);
|
|
else
|
|
if (n->str.compare(String(widenCString("L"))) == 0)
|
|
bCon->addInt64((uint64)(n->num), p->pos);
|
|
else
|
|
reportError(Exception::badValueError, "Unrecognized unit", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::number:
|
|
{
|
|
int32 i;
|
|
float64 x = checked_cast<NumberExprNode *>(p)->value;
|
|
if (JSDOUBLE_IS_INT(x, i) && INT_FITS_IN_JS2VAL(i))
|
|
bCon->addInteger(i, p->pos);
|
|
else
|
|
bCon->addFloat64(x, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::regExp:
|
|
{
|
|
RegExpExprNode *v = checked_cast<RegExpExprNode *>(p);
|
|
js2val args[2];
|
|
const String *reStr = engine->allocStringPtr(&v->re);
|
|
DEFINE_ROOTKEEPER(this, rk1, reStr);
|
|
const String *flagStr = engine->allocStringPtr(&v->flags);
|
|
DEFINE_ROOTKEEPER(this, rk2, flagStr);
|
|
args[0] = STRING_TO_JS2VAL(reStr);
|
|
args[1] = STRING_TO_JS2VAL(flagStr);
|
|
// XXX error handling during this parse? The RegExp_Constructor is
|
|
// going to call errorPos() on the current bCon.
|
|
js2val reValue = RegExp_Constructor(this, JS2VAL_NULL, args, 2);
|
|
RegExpInstance *reInst = checked_cast<RegExpInstance *>(JS2VAL_TO_OBJECT(reValue));
|
|
bCon->addRegExp(reInst, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::string:
|
|
{
|
|
bCon->addString(checked_cast<StringExprNode *>(p)->str, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::conditional:
|
|
{
|
|
BytecodeContainer::LabelID falseConditionExpression = bCon->getLabel();
|
|
BytecodeContainer::LabelID labelAtBottom = bCon->getLabel();
|
|
|
|
TernaryExprNode *c = checked_cast<TernaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, c->op1, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranchFalse, falseConditionExpression, p->pos);
|
|
|
|
lVal = SetupExprNode(env, phase, c->op2, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitBranch(eBranch, labelAtBottom, p->pos);
|
|
|
|
bCon->setLabel(falseConditionExpression);
|
|
//adjustStack(-1); // the true case will leave a stack entry pending
|
|
// but we can discard it since only one path will be taken.
|
|
lVal = SetupExprNode(env, phase, c->op3, exprType);
|
|
if (lVal) lVal->emitReadBytecode(bCon, p->pos);
|
|
|
|
bCon->setLabel(labelAtBottom);
|
|
}
|
|
break;
|
|
case ExprNode::qualify:
|
|
{
|
|
QualifyExprNode *qe = checked_cast<QualifyExprNode *>(p);
|
|
const StringAtom &name = qe->name;
|
|
|
|
js2val av = EvalExpression(env, CompilePhase, qe->qualifier);
|
|
if (JS2VAL_IS_NULL(av) || !JS2VAL_IS_OBJECT(av))
|
|
reportError(Exception::badValueError, "Namespace expected in qualifier", p->pos);
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(av);
|
|
if ((obj->kind != AttributeObjectKind) || (checked_cast<Attribute *>(obj)->attrKind != Attribute::NamespaceAttr))
|
|
reportError(Exception::badValueError, "Namespace expected in qualifier", p->pos);
|
|
Namespace *ns = checked_cast<Namespace *>(obj);
|
|
|
|
returnRef = new (*referenceArena) LexicalReference(new (this) Multiname(name, ns), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(returnRef);
|
|
}
|
|
break;
|
|
case ExprNode::identifier:
|
|
{
|
|
IdentifierExprNode *i = checked_cast<IdentifierExprNode *>(p);
|
|
if ((i->name == widenCString("eval")) || (i->name == widenCString("arguments"))) {
|
|
// find the parameterFrame for this function and make sure
|
|
// that the arguments property will get built
|
|
FrameListIterator fi = env->getBegin(), end = env->getEnd();
|
|
while (fi != end) {
|
|
Frame *fr = *fi;
|
|
if ((fr->kind != WithFrameKind) && (fr->kind != BlockFrameKind)) {
|
|
NonWithFrame *nwf = checked_cast<NonWithFrame *>(fr);
|
|
if (nwf->kind == ParameterFrameKind) {
|
|
ParameterFrame *pf = checked_cast<ParameterFrame *>(nwf);
|
|
pf->buildArguments = true;
|
|
break;
|
|
}
|
|
else // ran into a class or package, we're not in a function
|
|
break;
|
|
}
|
|
fi++;
|
|
}
|
|
}
|
|
returnRef = new (*referenceArena) LexicalReference(new (this) Multiname(i->name), cxt.strict, bCon);
|
|
referenceArena->registerDestructor(returnRef);
|
|
((LexicalReference *)returnRef)->variableMultiname->addNamespace(cxt);
|
|
// Try to find this identifier at compile time, we have to stop if we reach
|
|
// a frame that supports dynamic properties - the identifier could be
|
|
// created at runtime without us finding it here.
|
|
// We're looking to find both the type of the reference (to store into exprType)
|
|
// and to see if we can change the reference to a FrameSlot or Slot (for member
|
|
// functions)
|
|
Multiname *multiname = ((LexicalReference *)returnRef)->variableMultiname;
|
|
FrameListIterator fi = env->getBegin(), end = env->getEnd();
|
|
bool keepLooking = true;
|
|
while (fi != end && keepLooking) {
|
|
Frame *fr = *fi;
|
|
if (fr->kind == WithFrameKind)
|
|
// XXX unless it's provably not a dynamic object that been with'd??
|
|
break;
|
|
NonWithFrame *pf = checked_cast<NonWithFrame *>(fr);
|
|
switch (pf->kind) {
|
|
default:
|
|
keepLooking = false;
|
|
break;
|
|
case ParameterFrameKind:
|
|
{
|
|
bool isEnumerable;
|
|
LocalMember *m = findLocalMember(pf, multiname, ReadAccess, isEnumerable);
|
|
if (m) {
|
|
switch (checked_cast<LocalMember *>(m)->memberKind) {
|
|
case LocalMember::VariableMember:
|
|
*exprType = checked_cast<Variable *>(m)->type;
|
|
break;
|
|
case LocalMember::FrameVariableMember:
|
|
ASSERT(checked_cast<FrameVariable *>(m)->kind == FrameVariable::Parameter);
|
|
returnRef = new (*referenceArena) ParameterSlotReference(checked_cast<FrameVariable *>(m)->frameSlot);
|
|
break;
|
|
}
|
|
}
|
|
keepLooking = false; // don't look beneath the current function, as the slot base pointers aren't relevant
|
|
}
|
|
break;
|
|
case BlockFrameKind:
|
|
{
|
|
bool isEnumerable;
|
|
LocalMember *m = findLocalMember(pf, multiname, ReadAccess, isEnumerable);
|
|
if (m) {
|
|
switch (checked_cast<LocalMember *>(m)->memberKind) {
|
|
case LocalMember::VariableMember:
|
|
*exprType = checked_cast<Variable *>(m)->type;
|
|
break;
|
|
case LocalMember::FrameVariableMember:
|
|
ASSERT(checked_cast<FrameVariable *>(m)->kind == FrameVariable::Local);
|
|
returnRef = new (*referenceArena) FrameSlotReference(checked_cast<FrameVariable *>(m)->frameSlot);
|
|
break;
|
|
}
|
|
keepLooking = false;
|
|
}
|
|
}
|
|
break;
|
|
case ClassKind:
|
|
{
|
|
// look for this identifier in the static members
|
|
// (do we have a this?)
|
|
// If the class allows dynamic members, have to stop the search here
|
|
keepLooking = false;
|
|
}
|
|
break;
|
|
case PackageKind:
|
|
{
|
|
JS2Class *limit = objectType(pf);
|
|
InstanceMember *mBase = findBaseInstanceMember(limit, multiname, ReadAccess);
|
|
if (mBase) {
|
|
InstanceMember *m = getDerivedInstanceMember(*exprType, mBase, ReadAccess);
|
|
switch (m->memberKind) {
|
|
case Member::InstanceVariableMember:
|
|
{
|
|
InstanceVariable *mv = checked_cast<InstanceVariable *>(m);
|
|
*exprType = mv->type;
|
|
}
|
|
break;
|
|
}
|
|
keepLooking = false;
|
|
}
|
|
else {
|
|
js2val base = OBJECT_TO_JS2VAL(pf);
|
|
Member *m = findCommonMember(&base, multiname, ReadAccess, false);
|
|
if (m) {
|
|
switch (m->memberKind) {
|
|
case Member::ForbiddenMember:
|
|
case Member::DynamicVariableMember:
|
|
case Member::FrameVariableMember:
|
|
case Member::VariableMember:
|
|
case Member::ConstructorMethodMember:
|
|
case Member::SetterMember:
|
|
case Member::GetterMember:
|
|
switch (checked_cast<LocalMember *>(m)->memberKind) {
|
|
case LocalMember::VariableMember:
|
|
*exprType = checked_cast<Variable *>(m)->type;
|
|
break;
|
|
case LocalMember::FrameVariableMember:
|
|
ASSERT(checked_cast<FrameVariable *>(m)->kind == FrameVariable::Package);
|
|
returnRef = new (*referenceArena) PackageSlotReference(checked_cast<FrameVariable *>(m)->frameSlot);
|
|
break;
|
|
}
|
|
break;
|
|
case Member::InstanceVariableMember:
|
|
case Member::InstanceMethodMember:
|
|
case Member::InstanceGetterMember:
|
|
case Member::InstanceSetterMember:
|
|
// XXX checked_cast<InstanceMember *>(m)
|
|
break;
|
|
}
|
|
keepLooking = false;
|
|
}
|
|
}
|
|
// XXX if package allows dynamic members, stop looking
|
|
keepLooking = false;
|
|
}
|
|
break;
|
|
}
|
|
fi++;
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::Delete:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (lVal)
|
|
lVal->emitDeleteBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "Delete needs an lValue", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::postIncrement:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (lVal)
|
|
lVal->emitPostIncBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "PostIncrement needs an lValue", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::postDecrement:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (lVal)
|
|
lVal->emitPostDecBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "PostDecrement needs an lValue", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::preIncrement:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (lVal)
|
|
lVal->emitPreIncBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "PreIncrement needs an lValue", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::preDecrement:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *lVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (lVal)
|
|
lVal->emitPreDecBytecode(bCon, p->pos);
|
|
else
|
|
reportError(Exception::semanticError, "PreDecrement needs an lValue", p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::index:
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
Reference *baseVal = SetupExprNode(env, phase, i->op, exprType);
|
|
if (baseVal) baseVal->emitReadBytecode(bCon, p->pos);
|
|
ExprPairList *ep = i->pairs;
|
|
while (ep) { // Validate has made sure there is only one, unnamed argument
|
|
Reference *argVal = SetupExprNode(env, phase, ep->value, exprType);
|
|
if (argVal) argVal->emitReadBytecode(bCon, p->pos);
|
|
ep = ep->next;
|
|
}
|
|
returnRef = new (*referenceArena) BracketReference();
|
|
referenceArena->registerDestructor(returnRef);
|
|
}
|
|
break;
|
|
case ExprNode::dot:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *baseVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (baseVal) baseVal->emitReadBytecode(bCon, p->pos);
|
|
|
|
if (b->op2->getKind() == ExprNode::identifier) {
|
|
IdentifierExprNode *i = checked_cast<IdentifierExprNode *>(b->op2);
|
|
|
|
if (*exprType) {
|
|
Multiname multiname(i->name);
|
|
InstanceMember *mBase = findBaseInstanceMember(*exprType, &multiname, ReadAccess);
|
|
if (mBase) {
|
|
InstanceMember *m = getDerivedInstanceMember(*exprType, mBase, ReadAccess);
|
|
if (m->memberKind == Member::InstanceVariableMember) {
|
|
InstanceVariable *mv = checked_cast<InstanceVariable *>(m);
|
|
*exprType = mv->type;
|
|
returnRef = new (*referenceArena) SlotReference(mv->slotIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (returnRef == NULL) {
|
|
returnRef = new (*referenceArena) DotReference(new (this) Multiname(i->name), bCon);
|
|
referenceArena->registerDestructor(returnRef);
|
|
checked_cast<DotReference *>(returnRef)->propertyMultiname->addNamespace(cxt);
|
|
}
|
|
}
|
|
else {
|
|
if (b->op2->getKind() == ExprNode::qualify) {
|
|
Reference *rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
ASSERT(rVal && checked_cast<LexicalReference *>(rVal));
|
|
returnRef = new (*referenceArena) DotReference(((LexicalReference *)rVal)->variableMultiname, bCon);
|
|
referenceArena->registerDestructor(returnRef);
|
|
checked_cast<DotReference *>(returnRef)->propertyMultiname->addNamespace(cxt);
|
|
}
|
|
// XXX else bracketRef...
|
|
else
|
|
NOT_REACHED("do we support these, or not?");
|
|
}
|
|
}
|
|
break;
|
|
case ExprNode::boolean:
|
|
if (checked_cast<BooleanExprNode *>(p)->value)
|
|
bCon->emitOp(eTrue, p->pos);
|
|
else
|
|
bCon->emitOp(eFalse, p->pos);
|
|
break;
|
|
case ExprNode::arrayLiteral:
|
|
{
|
|
int32 argCount = 0;
|
|
PairListExprNode *plen = checked_cast<PairListExprNode *>(p);
|
|
ExprPairList *e = plen->pairs;
|
|
while (e) {
|
|
if (e->value) {
|
|
Reference *rVal = SetupExprNode(env, phase, e->value, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
argCount++;
|
|
}
|
|
e = e->next;
|
|
}
|
|
bCon->emitOp(eNewArray, p->pos, -argCount + 1); // pop argCount args and push a new array
|
|
bCon->addShort((uint16)argCount);
|
|
}
|
|
break;
|
|
case ExprNode::objectLiteral:
|
|
{
|
|
int32 argCount = 0;
|
|
PairListExprNode *plen = checked_cast<PairListExprNode *>(p);
|
|
ExprPairList *e = plen->pairs;
|
|
while (e) {
|
|
ASSERT(e->field && e->value);
|
|
Reference *rVal = SetupExprNode(env, phase, e->value, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
switch (e->field->getKind()) {
|
|
case ExprNode::identifier:
|
|
bCon->addString(&checked_cast<IdentifierExprNode *>(e->field)->name, p->pos);
|
|
break;
|
|
case ExprNode::string:
|
|
bCon->addString(checked_cast<StringExprNode *>(e->field)->str, p->pos);
|
|
break;
|
|
case ExprNode::number:
|
|
bCon->addString(engine->numberToString(&(checked_cast<NumberExprNode *>(e->field))->value), p->pos);
|
|
break;
|
|
default:
|
|
NOT_REACHED("bad field name");
|
|
}
|
|
argCount++;
|
|
e = e->next;
|
|
}
|
|
bCon->emitOp(eNewObject, p->pos, -argCount + 1); // pop argCount args and push a new object
|
|
bCon->addShort((uint16)argCount);
|
|
}
|
|
break;
|
|
case ExprNode::Typeof:
|
|
{
|
|
UnaryExprNode *u = checked_cast<UnaryExprNode *>(p);
|
|
Reference *rVal = SetupExprNode(env, phase, u->op, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eTypeof, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::call:
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
Reference *rVal = SetupExprNode(env, phase, i->op, exprType);
|
|
if (rVal)
|
|
rVal->emitReadForInvokeBytecode(bCon, p->pos);
|
|
else /* a call doesn't have to have an lValue to execute on,
|
|
* but we use the value as it's own 'this' in that case.
|
|
*/
|
|
bCon->emitOp(eDup, p->pos);
|
|
ExprPairList *args = i->pairs;
|
|
uint16 argCount = 0;
|
|
while (args) {
|
|
Reference *r = SetupExprNode(env, phase, args->value, exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
argCount++;
|
|
args = args->next;
|
|
}
|
|
bCon->emitOp(eCall, p->pos, -(argCount + 2) + 1); // pop argCount args, the base & function, and push a result
|
|
bCon->addShort(argCount);
|
|
}
|
|
break;
|
|
case ExprNode::New:
|
|
{
|
|
// XXX why not?--> if (phase == CompilePhase) reportError(Exception::compileExpressionError, "Inappropriate compile time expression", p->pos);
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
Reference *rVal = SetupExprNode(env, phase, i->op, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
ExprPairList *args = i->pairs;
|
|
uint16 argCount = 0;
|
|
while (args) {
|
|
Reference *r = SetupExprNode(env, phase, args->value, exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
argCount++;
|
|
args = args->next;
|
|
}
|
|
bCon->emitOp(eNew, p->pos, -(argCount + 1) + 1); // pop argCount args, the type or function, and push a result
|
|
bCon->addShort(argCount);
|
|
}
|
|
break;
|
|
case ExprNode::comma:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *r = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(ePopv, p->pos);
|
|
returnRef = SetupExprNode(env, phase, b->op2, exprType);
|
|
}
|
|
break;
|
|
case ExprNode::Instanceof:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eInstanceof, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::In:
|
|
{
|
|
BinaryExprNode *b = checked_cast<BinaryExprNode *>(p);
|
|
Reference *rVal = SetupExprNode(env, phase, b->op1, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
rVal = SetupExprNode(env, phase, b->op2, exprType);
|
|
if (rVal) rVal->emitReadBytecode(bCon, p->pos);
|
|
bCon->emitOp(eIn, p->pos);
|
|
}
|
|
break;
|
|
case ExprNode::functionLiteral:
|
|
{
|
|
FunctionExprNode *f = checked_cast<FunctionExprNode *>(p);
|
|
FunctionInstance *fnInst = f->function.fn;
|
|
CompilationData *oldData = startCompilationUnit(fnInst->fWrap->bCon, bCon->mSource, bCon->mSourceLocation);
|
|
env->addFrame(fnInst->fWrap->compileFrame);
|
|
SetupStmt(env, phase, f->function.body);
|
|
// XXX need to make sure that all paths lead to an exit of some kind
|
|
bCon->emitOp(eReturnVoid, p->pos);
|
|
env->removeTopFrame();
|
|
restoreCompilationUnit(oldData);
|
|
bCon->emitOp(eFunction, p->pos);
|
|
bCon->addObject(fnInst);
|
|
}
|
|
break;
|
|
case ExprNode::superStmt:
|
|
{
|
|
InvokeExprNode *i = checked_cast<InvokeExprNode *>(p);
|
|
ExprPairList *args = i->pairs;
|
|
uint16 argCount = 0;
|
|
while (args) {
|
|
Reference *r = SetupExprNode(env, phase, args->value, exprType);
|
|
if (r) r->emitReadBytecode(bCon, p->pos);
|
|
argCount++;
|
|
args = args->next;
|
|
}
|
|
bCon->emitOp(eSuperCall, p->pos, -argCount); // pop argCount args, no result
|
|
bCon->addShort(argCount);
|
|
}
|
|
break;
|
|
default:
|
|
NOT_REACHED("Not Yet Implemented");
|
|
}
|
|
return returnRef;
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Environment
|
|
*
|
|
************************************************************************************/
|
|
|
|
// If env is from within a class's body, getEnclosingClass(env) returns the
|
|
// innermost such class; otherwise, it returns none.
|
|
JS2Class *Environment::getEnclosingClass()
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
while (fi != end) {
|
|
if ((*fi)->kind == ClassKind)
|
|
return checked_cast<JS2Class *>(*fi);
|
|
fi++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// If env is from with a function's body, return the innermost ParameterFrame for
|
|
// the innermost such function, otherwise return NULL
|
|
ParameterFrame *Environment::getEnclosingParameterFrame(js2val *thisP)
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
while (fi != end) {
|
|
switch ((*fi)->kind) {
|
|
case ClassKind:
|
|
case PackageKind:
|
|
case SystemKind:
|
|
return NULL;
|
|
case ParameterFrameKind:
|
|
{
|
|
ParameterFrame *pFrame = checked_cast<ParameterFrame *>(*fi);
|
|
if (thisP) *thisP = pFrame->thisObject;
|
|
return pFrame;
|
|
}
|
|
case BlockFrameKind:
|
|
case WithFrameKind:
|
|
break;
|
|
}
|
|
fi++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// getRegionalEnvironment(env) returns all frames in env up to and including the first
|
|
// regional frame. A regional frame is either any frame other than a with frame or local
|
|
// block frame, a local block frame directly enclosed in a class, or a local block frame
|
|
// directly enclosed in a with frame directly enclosed in a class.
|
|
// In this implementation, the return value is the iterator at the end of the regional environment.
|
|
FrameListIterator Environment::getRegionalEnvironment()
|
|
{
|
|
FrameListIterator start = getBegin();
|
|
FrameListIterator fi = start;
|
|
while (((*fi)->kind == BlockFrameKind) || ((*fi)->kind == WithFrameKind)) {
|
|
fi++;
|
|
ASSERT(fi != getEnd());
|
|
}
|
|
if ((*fi)->kind == ClassKind) {
|
|
while ((fi != start) && ((*fi)->kind != BlockFrameKind))
|
|
fi--;
|
|
}
|
|
return fi;
|
|
}
|
|
|
|
|
|
// Returns the most specific regional frame.
|
|
// Returns the iterator value for that frame so that the frames neighbors can be accessed
|
|
FrameListIterator Environment::getRegionalFrame()
|
|
{
|
|
FrameListIterator fi = getRegionalEnvironment();
|
|
return fi;
|
|
}
|
|
|
|
// Returns the penultimate frame, always a Package
|
|
Package *Environment::getPackageFrame()
|
|
{
|
|
Frame *result = *(getEnd() - 2);
|
|
ASSERT(result->kind == PackageKind);
|
|
return checked_cast<Package *>(result);
|
|
}
|
|
|
|
js2val Environment::readImplicitThis(JS2Metadata *meta)
|
|
{
|
|
js2val thisVal;
|
|
ParameterFrame *pFrame = getEnclosingParameterFrame(&thisVal);
|
|
if (pFrame == NULL)
|
|
meta->reportError(Exception::referenceError, "Can't access instance members outside an instance method without supplying an instance object", meta->engine->errorPos());
|
|
if ((!JS2VAL_IS_OBJECT(thisVal) || JS2VAL_IS_NULL(thisVal)) || !pFrame->isInstance || !pFrame->isConstructor)
|
|
meta->reportError(Exception::referenceError, "Can't access instance members inside a non-instance method without supplying an instance object", meta->engine->errorPos());
|
|
if (!pFrame->superConstructorCalled)
|
|
meta->reportError(Exception::uninitializedError, "Can't access instance members from within a constructor before the superconstructor has been called", meta->engine->errorPos());
|
|
return thisVal;
|
|
}
|
|
|
|
|
|
// Read the value of a lexical reference - it's an error if that reference
|
|
// doesn't have a binding somewhere.
|
|
// Attempt the read in each frame in the current environment, stopping at the
|
|
// first successful effort. If the property can't be found in any frame, it's
|
|
// an error.
|
|
void Environment::lexicalRead(JS2Metadata *meta, Multiname *multiname, Phase phase, js2val *rval, js2val *base)
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
bool result = false;
|
|
while (fi != end) {
|
|
Frame *f = (*fi);
|
|
switch (f->kind) {
|
|
case ClassKind:
|
|
case PackageKind:
|
|
{
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(f));
|
|
js2val frame = OBJECT_TO_JS2VAL(f);
|
|
result = limit->Read(meta, &frame, multiname, this, phase, rval);
|
|
}
|
|
break;
|
|
case SystemKind:
|
|
case ParameterFrameKind:
|
|
case BlockFrameKind:
|
|
{
|
|
bool isEnumerable;
|
|
LocalMember *m = meta->findLocalMember(f, multiname, ReadAccess, isEnumerable);
|
|
if (m)
|
|
result = meta->readLocalMember(m, phase, rval, f);
|
|
}
|
|
break;
|
|
case WithFrameKind:
|
|
{
|
|
WithFrame *wf = checked_cast<WithFrame *>(f);
|
|
// XXX uninitialized 'with' object?
|
|
js2val withVal = OBJECT_TO_JS2VAL(wf->obj);
|
|
JS2Class *limit = meta->objectType(withVal);
|
|
result = limit->Read(meta, &withVal, multiname, this, phase, rval);
|
|
if (result && base)
|
|
*base = withVal;
|
|
}
|
|
break;
|
|
}
|
|
if (result)
|
|
return;
|
|
fi++;
|
|
}
|
|
meta->reportError(Exception::referenceError, "{0} is undefined", meta->engine->errorPos(), multiname->name);
|
|
}
|
|
|
|
// Attempt the write in the top frame in the current environment - if the property
|
|
// exists, then fine. Otherwise create the property there.
|
|
void Environment::lexicalWrite(JS2Metadata *meta, Multiname *multiname, js2val newValue, bool createIfMissing)
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
bool result = false;
|
|
while (fi != end) {
|
|
Frame *f = (*fi);
|
|
switch (f->kind) {
|
|
case ClassKind:
|
|
case PackageKind:
|
|
{
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(f));
|
|
result = limit->Write(meta, OBJECT_TO_JS2VAL(f), multiname, this, false, newValue, false);
|
|
}
|
|
break;
|
|
case SystemKind:
|
|
case ParameterFrameKind:
|
|
case BlockFrameKind:
|
|
{
|
|
bool isEnumerable;
|
|
LocalMember *m = meta->findLocalMember(f, multiname, WriteAccess, isEnumerable);
|
|
if (m) {
|
|
meta->writeLocalMember(m, newValue, false, f);
|
|
result = true;
|
|
}
|
|
}
|
|
break;
|
|
case WithFrameKind:
|
|
{
|
|
WithFrame *wf = checked_cast<WithFrame *>(f);
|
|
// XXX uninitialized 'with' object?
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(wf->obj));
|
|
result = limit->Write(meta, OBJECT_TO_JS2VAL(wf->obj), multiname, this, false, newValue, false);
|
|
}
|
|
break;
|
|
}
|
|
if (result)
|
|
return;
|
|
fi++;
|
|
}
|
|
if (createIfMissing) {
|
|
Package *pkg = getPackageFrame();
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(pkg));
|
|
result = limit->Write(meta, OBJECT_TO_JS2VAL(pkg), multiname, this, true, newValue, false);
|
|
if (result)
|
|
return;
|
|
}
|
|
if (!meta->cxt.E3compatibility)
|
|
meta->reportError(Exception::referenceError, "{0} is undefined", meta->engine->errorPos(), multiname->name);
|
|
}
|
|
|
|
|
|
// Initialize a variable - it might not be in the immediate frame, because of hoisting
|
|
// but it had darn well better be in the environment somewhere.
|
|
void Environment::lexicalInit(JS2Metadata *meta, Multiname *multiname, js2val newValue)
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
bool result = false;
|
|
while (fi != end) {
|
|
Frame *f = (*fi);
|
|
switch (f->kind) {
|
|
case ClassKind:
|
|
case PackageKind:
|
|
{
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(f));
|
|
result = limit->Write(meta, OBJECT_TO_JS2VAL(f), multiname, this, false, newValue, true);
|
|
}
|
|
break;
|
|
case SystemKind:
|
|
case ParameterFrameKind:
|
|
case BlockFrameKind:
|
|
{
|
|
bool isEnumerable;
|
|
LocalMember *m = meta->findLocalMember(f, multiname, WriteAccess, isEnumerable);
|
|
if (m) {
|
|
meta->writeLocalMember(m, newValue, true, f);
|
|
result = true;
|
|
}
|
|
}
|
|
break;
|
|
case WithFrameKind:
|
|
{
|
|
WithFrame *wf = checked_cast<WithFrame *>(f);
|
|
// XXX uninitialized 'with' object?
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(wf->obj));
|
|
result = limit->Write(meta, OBJECT_TO_JS2VAL(wf->obj), multiname, this, false, newValue, true);
|
|
}
|
|
break;
|
|
}
|
|
if (result)
|
|
return;
|
|
fi++;
|
|
}
|
|
// XXX can reach here? Shouldn't it be defined in the frame/etc already???
|
|
ASSERT(false);
|
|
Package *pkg = getPackageFrame();
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(pkg));
|
|
result = limit->Write(meta, OBJECT_TO_JS2VAL(pkg), multiname, this, true, newValue, true);
|
|
if (result)
|
|
return;
|
|
}
|
|
|
|
// Delete the named property in the current environment, return true if the property
|
|
// can't be found, or the result of the deleteProperty call if it was found.
|
|
bool Environment::lexicalDelete(JS2Metadata *meta, Multiname *multiname, Phase phase)
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
bool result = false;
|
|
while (fi != end) {
|
|
Frame *f = (*fi);
|
|
switch (f->kind) {
|
|
case ClassKind:
|
|
case PackageKind:
|
|
{
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(f));
|
|
if (limit->Delete(meta, OBJECT_TO_JS2VAL(f), multiname, this, &result))
|
|
return result;
|
|
}
|
|
break;
|
|
case SystemKind:
|
|
case ParameterFrameKind:
|
|
case BlockFrameKind:
|
|
{
|
|
bool isEnumerable;
|
|
LocalMember *m = meta->findLocalMember(f, multiname, WriteAccess, isEnumerable);
|
|
if (m)
|
|
return false;
|
|
}
|
|
break;
|
|
case WithFrameKind:
|
|
{
|
|
WithFrame *wf = checked_cast<WithFrame *>(f);
|
|
// XXX uninitialized 'with' object?
|
|
JS2Class *limit = meta->objectType(OBJECT_TO_JS2VAL(wf->obj));
|
|
if (limit->Delete(meta, OBJECT_TO_JS2VAL(wf->obj), multiname, this, &result))
|
|
return result;
|
|
}
|
|
break;
|
|
}
|
|
fi++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Clone the pluralFrame bindings into the singularFrame, instantiating new members for each binding
|
|
void Environment::instantiateFrame(NonWithFrame *pluralFrame, NonWithFrame *singularFrame, bool buildSlots)
|
|
{
|
|
singularFrame->localBindings.clear();
|
|
|
|
for (LocalBindingIterator bi(pluralFrame->localBindings); bi; ++bi) {
|
|
LocalBindingEntry &lbe = *bi;
|
|
lbe.clear();
|
|
}
|
|
for (LocalBindingIterator bi2(pluralFrame->localBindings); bi2; ++bi2) {
|
|
LocalBindingEntry &lbe = *bi2;
|
|
LocalBindingEntry *new_lbe = &singularFrame->localBindings.insert(lbe.name);
|
|
|
|
for (LocalBindingEntry::NS_Iterator i = lbe.begin(), end = lbe.end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
LocalBinding *m = ns.second;
|
|
if (m->content->cloneContent == NULL) {
|
|
m->content->cloneContent = m->content->clone();
|
|
}
|
|
LocalBinding *new_b = new LocalBinding(m->accesses, m->content->cloneContent, m->enumerable);
|
|
new_b->xplicit = m->xplicit;
|
|
new_lbe->bindingList.push_back(LocalBindingEntry::NamespaceBinding(ns.first, new_b));
|
|
}
|
|
|
|
}
|
|
if (buildSlots && pluralFrame->frameSlots) {
|
|
size_t count = pluralFrame->frameSlots->size();
|
|
singularFrame->frameSlots = new std::vector<js2val>(count);
|
|
for (size_t i = 0; i < count; i++)
|
|
(*singularFrame->frameSlots)[i] = (*pluralFrame->frameSlots)[i];
|
|
}
|
|
}
|
|
|
|
// need to mark all the frames in the environment - otherwise a marked frame that
|
|
// came initially from the bytecodeContainer may prevent the markChildren call
|
|
// from finding frames further down the list.
|
|
void Environment::markChildren()
|
|
{
|
|
FrameListIterator fi = getBegin(), end = getEnd();
|
|
while (fi != end) {
|
|
GCMARKOBJECT(*fi);
|
|
fi++;
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Context
|
|
*
|
|
************************************************************************************/
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Multiname
|
|
*
|
|
************************************************************************************/
|
|
|
|
// return true if the given namespace is on the namespace list
|
|
bool Multiname::listContains(Namespace *nameSpace)
|
|
{
|
|
if (nsList->empty())
|
|
return true;
|
|
for (NamespaceListIterator n = nsList->begin(), end = nsList->end(); (n != end); n++) {
|
|
if (*n == nameSpace)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// add all the open namespaces from the given context
|
|
void Multiname::addNamespace(Context &cxt)
|
|
{
|
|
addNamespace(&cxt.openNamespaces);
|
|
}
|
|
|
|
|
|
// add every namespace from the list to this Multiname
|
|
void Multiname::addNamespace(NamespaceList &ns)
|
|
{
|
|
for (NamespaceListIterator nli = ns.begin(), end = ns.end();
|
|
(nli != end); nli++)
|
|
nsList->push_back(*nli);
|
|
}
|
|
|
|
QualifiedName *Multiname::selectPrimaryName(JS2Metadata *meta)
|
|
{
|
|
if (nsList->size() == 1)
|
|
return new QualifiedName(nsList->back(), name);
|
|
else {
|
|
if (listContains(meta->publicNamespace))
|
|
return new QualifiedName(meta->publicNamespace, name);
|
|
else {
|
|
meta->reportError(Exception::propertyAccessError, "No good primary name {0}", meta->engine->errorPos(), name);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void Multiname::markChildren()
|
|
{
|
|
for (NamespaceListIterator n = nsList->begin(), end = nsList->end(); (n != end); n++) {
|
|
GCMARKOBJECT(*n)
|
|
}
|
|
// if (name) JS2Object::mark(name);
|
|
}
|
|
|
|
bool Multiname::subsetOf(Multiname &mn)
|
|
{
|
|
if (name != mn.name)
|
|
return false;
|
|
for (NamespaceListIterator n = nsList->begin(), end = nsList->end(); (n != end); n++) {
|
|
if (!mn.listContains(*n))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* JS2Metadata
|
|
*
|
|
************************************************************************************/
|
|
|
|
// - Define namespaces::id (for all namespaces or at least 'public') in the top frame
|
|
// unless it's there already.
|
|
// - If the binding exists (not forbidden) in lower frames in the regional environment, it's an error.
|
|
// - Define a forbidden binding in all the lower frames.
|
|
//
|
|
Multiname *JS2Metadata::defineLocalMember(Environment *env, const StringAtom &id, NamespaceList *namespaces,
|
|
Attribute::OverrideModifier overrideMod, bool xplicit, Access access,
|
|
LocalMember *m, size_t pos, bool enumerable)
|
|
{
|
|
NonWithFrame *innerFrame = checked_cast<NonWithFrame *>(*(env->getBegin()));
|
|
if ((overrideMod != Attribute::NoOverride) || (xplicit && innerFrame->kind != PackageKind))
|
|
reportError(Exception::definitionError, "Illegal definition", pos);
|
|
|
|
Multiname *multiname = new (this) Multiname(world.identifiers[id]);
|
|
if (!namespaces || namespaces->empty())
|
|
multiname->addNamespace(publicNamespace);
|
|
else
|
|
multiname->addNamespace(namespaces);
|
|
|
|
// Search the local frame for an overlapping definition
|
|
LocalBindingEntry *lbeP = innerFrame->localBindings[id];
|
|
if (lbeP) {
|
|
for (LocalBindingEntry::NS_Iterator i = lbeP->begin(), end = lbeP->end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access) && multiname->listContains(ns.first))
|
|
reportError(Exception::definitionError, "Duplicate definition {0}", pos, id);
|
|
}
|
|
}
|
|
|
|
// Check all frames below the current - up to the RegionalFrame - for a non-forbidden definition
|
|
FrameListIterator fi = env->getBegin();
|
|
Frame *regionalFrame = *(env->getRegionalFrame());
|
|
if (innerFrame != regionalFrame) {
|
|
// The frame iterator is pointing at the top of the environment's
|
|
// frame list, start at the one below that and continue to the frame
|
|
// returned by 'getRegionalFrame()'.
|
|
Frame *fr = *++fi;
|
|
while (true) {
|
|
if (fr->kind != WithFrameKind) {
|
|
NonWithFrame *nwfr = checked_cast<NonWithFrame *>(fr);
|
|
LocalBindingEntry *rbeP = nwfr->localBindings[id];
|
|
if (rbeP) {
|
|
for (LocalBindingEntry::NS_Iterator i = rbeP->begin(), end = rbeP->end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access)
|
|
&& (ns.second->content->memberKind != LocalMember::ForbiddenMember)
|
|
&& multiname->listContains(ns.first))
|
|
reportError(Exception::definitionError, "Duplicate definition {0}", pos, id);
|
|
}
|
|
}
|
|
}
|
|
if (fr == regionalFrame)
|
|
break;
|
|
fr = *++fi;
|
|
ASSERT(fr);
|
|
}
|
|
}
|
|
|
|
// Now insert the id, via all it's namespaces into the local frame
|
|
if (lbeP == NULL)
|
|
lbeP = &innerFrame->localBindings.insert(id);
|
|
for (NamespaceListIterator nli = multiname->nsList->begin(), nlend = multiname->nsList->end(); (nli != nlend); nli++) {
|
|
LocalBinding *new_b = new LocalBinding(access, m, enumerable);
|
|
lbeP->bindingList.push_back(LocalBindingEntry::NamespaceBinding(*nli, new_b));
|
|
}
|
|
// Mark the bindings of multiname as Forbidden in all non-innermost frames in the current
|
|
// region if they haven't been marked as such already.
|
|
if (innerFrame != regionalFrame) {
|
|
fi = env->getBegin();
|
|
Frame *fr = *++fi;
|
|
while (true) {
|
|
if (fr->kind != WithFrameKind) {
|
|
NonWithFrame *nwfr = checked_cast<NonWithFrame *>(fr);
|
|
for (NamespaceListIterator nli = multiname->nsList->begin(), nlend = multiname->nsList->end(); (nli != nlend); nli++) {
|
|
bool foundEntry = false;
|
|
LocalBindingEntry *rbeP = nwfr->localBindings[id];
|
|
if (rbeP) {
|
|
for (LocalBindingEntry::NS_Iterator i = rbeP->begin(), end = rbeP->end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access) && (ns.first == *nli)) {
|
|
ASSERT(ns.second->content->memberKind == LocalMember::ForbiddenMember);
|
|
foundEntry = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!foundEntry) {
|
|
LocalBindingEntry *rbe = &nwfr->localBindings.insert(id);
|
|
LocalBinding *new_b = new LocalBinding(access, forbiddenMember, false);
|
|
rbe->bindingList.push_back(LocalBindingEntry::NamespaceBinding(*nli, new_b));
|
|
}
|
|
}
|
|
}
|
|
if (fr == regionalFrame)
|
|
break;
|
|
fr = *++fi;
|
|
}
|
|
}
|
|
return multiname;
|
|
}
|
|
|
|
// Look through 'c' and all it's super classes for an identifier
|
|
// matching the qualified name and access.
|
|
InstanceMember *JS2Metadata::findInstanceMember(JS2Class *c, QualifiedName *qname, Access access)
|
|
{
|
|
if (qname == NULL)
|
|
return NULL;
|
|
JS2Class *s = c;
|
|
while (s) {
|
|
InstanceBindingEntry *ibeP = c->instanceBindings[qname->name];
|
|
if (ibeP) {
|
|
for (InstanceBindingEntry::NS_Iterator i = ibeP->begin(), end = ibeP->end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access) && (ns.first == qname->nameSpace)) {
|
|
return ns.second->content;
|
|
}
|
|
}
|
|
}
|
|
s = s->super;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
InstanceMember *JS2Metadata::searchForOverrides(JS2Class *c, Multiname *multiname, Access access, size_t pos)
|
|
{
|
|
InstanceMember *mBase = NULL;
|
|
JS2Class *s = c->super;
|
|
if (s) {
|
|
for (NamespaceListIterator nli = multiname->nsList->begin(), nlend = multiname->nsList->end(); (nli != nlend); nli++) {
|
|
Multiname *mn = new (this) Multiname(multiname->name, *nli);
|
|
DEFINE_ROOTKEEPER(this, rk, mn);
|
|
InstanceMember *m = findBaseInstanceMember(s, mn, access);
|
|
if (mBase == NULL)
|
|
mBase = m;
|
|
else
|
|
if (m && (m != mBase))
|
|
reportError(Exception::definitionError, "Illegal override", pos);
|
|
}
|
|
}
|
|
return mBase;
|
|
}
|
|
|
|
InstanceMember *JS2Metadata::defineInstanceMember(JS2Class *c, Context *cxt, const StringAtom &id, NamespaceList &namespaces,
|
|
Attribute::OverrideModifier overrideMod, bool xplicit,
|
|
InstanceMember *m, size_t pos)
|
|
{
|
|
if (xplicit)
|
|
reportError(Exception::definitionError, "Illegal use of explicit", pos);
|
|
Access access = m->instanceMemberAccess();
|
|
Multiname requestedMultiname(id, namespaces);
|
|
Multiname openMultiname(id, cxt);
|
|
Multiname *definedMultiname = NULL;
|
|
Multiname *searchedMultiname = NULL;
|
|
if (requestedMultiname.nsList->empty()) {
|
|
definedMultiname = new (this) Multiname(id, publicNamespace);
|
|
searchedMultiname = &openMultiname;
|
|
}
|
|
else {
|
|
definedMultiname = &requestedMultiname;
|
|
searchedMultiname = &requestedMultiname;
|
|
}
|
|
InstanceMember *mBase = searchForOverrides(c, searchedMultiname, access, pos);
|
|
InstanceMember *mOverridden = NULL;
|
|
if (mBase) {
|
|
mOverridden = getDerivedInstanceMember(c, mBase, access);
|
|
definedMultiname = mOverridden->multiname;
|
|
if (!requestedMultiname.subsetOf(*definedMultiname))
|
|
reportError(Exception::definitionError, "Illegal definition", pos);
|
|
bool goodKind;
|
|
switch (m->memberKind) {
|
|
case Member::InstanceVariableMember:
|
|
goodKind = (mOverridden->memberKind == Member::InstanceVariableMember);
|
|
break;
|
|
case Member::InstanceGetterMember:
|
|
goodKind = ((mOverridden->memberKind == Member::InstanceVariableMember)
|
|
|| (mOverridden->memberKind == Member::InstanceGetterMember));
|
|
break;
|
|
case Member::InstanceSetterMember:
|
|
goodKind = ((mOverridden->memberKind == Member::InstanceVariableMember)
|
|
|| (mOverridden->memberKind == Member::InstanceSetterMember));
|
|
break;
|
|
case Member::InstanceMethodMember:
|
|
goodKind = (mOverridden->memberKind == Member::InstanceMethodMember);
|
|
break;
|
|
}
|
|
if (mOverridden->final || !goodKind)
|
|
reportError(Exception::definitionError, "Illegal override", pos);
|
|
}
|
|
InstanceBindingEntry *ibeP = c->instanceBindings[id];
|
|
if (ibeP) {
|
|
for (InstanceBindingEntry::NS_Iterator i = ibeP->begin(), end = ibeP->end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((access & ns.second->content->instanceMemberAccess()) && (definedMultiname->listContains(ns.first)))
|
|
reportError(Exception::definitionError, "Illegal override", pos);
|
|
}
|
|
}
|
|
switch (overrideMod) {
|
|
case Attribute::NoOverride:
|
|
if (mBase || searchForOverrides(c, &openMultiname, access, pos))
|
|
reportError(Exception::definitionError, "Illegal override", pos);
|
|
break;
|
|
case Attribute::DontOverride:
|
|
if (mBase)
|
|
reportError(Exception::definitionError, "Illegal override", pos);
|
|
break;
|
|
case Attribute::DoOverride:
|
|
if (!mBase)
|
|
reportError(Exception::definitionError, "Illegal override", pos);
|
|
break;
|
|
}
|
|
m->multiname = new (this) Multiname(definedMultiname);
|
|
if (ibeP == NULL)
|
|
ibeP = &c->instanceBindings.insert(id);
|
|
for (NamespaceListIterator nli = definedMultiname->nsList->begin(), nlend = definedMultiname->nsList->end(); (nli != nlend); nli++) {
|
|
// XXX here and in defineLocal... why a new binding for each namespace?
|
|
// (other than it would mess up the destructor sequence :-)
|
|
InstanceBinding *ib = new InstanceBinding(access, m);
|
|
ibeP->bindingList.push_back(InstanceBindingEntry::NamespaceBinding(*nli, ib));
|
|
}
|
|
return mOverridden;
|
|
}
|
|
|
|
FrameVariable *JS2Metadata::makeFrameVariable(NonWithFrame *regionalFrame)
|
|
{
|
|
FrameVariable *result = NULL;
|
|
switch (regionalFrame->kind) {
|
|
case PackageKind:
|
|
result = new FrameVariable(regionalFrame->allocateSlot(), FrameVariable::Package);
|
|
break;
|
|
case ParameterFrameKind:
|
|
result = new FrameVariable(regionalFrame->allocateSlot(), FrameVariable::Parameter);
|
|
break;
|
|
case BlockFrameKind:
|
|
result = new FrameVariable(regionalFrame->allocateSlot(), FrameVariable::Local);
|
|
break;
|
|
default:
|
|
NOT_REACHED("Bad frame kind");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Define a hoisted var in the current frame (either Package or a Function)
|
|
// defineHoistedVar(env, id, initialValue) defines a hoisted variable with the name id in the environment env.
|
|
// Hoisted variables are hoisted to the package or enclosing function scope. Multiple hoisted variables may be
|
|
// defined in the same scope, but they may not coexist with non-hoisted variables with the same name. A hoisted
|
|
// variable can be defined using either a var or a function statement. If it is defined using var, then initialValue
|
|
// is always undefined (if the var statement has an initialiser, then the variable's value will be written later
|
|
// when the var statement is executed). If it is defined using function, then initialValue must be a function
|
|
// instance or open instance. A var hoisted variable may be hoisted into the ParameterFrame if there is already
|
|
// a parameter with the same name; a function hoisted variable is never hoisted into the ParameterFrame and
|
|
// will shadow a parameter with the same name for compatibility with ECMAScript Edition 3.
|
|
// If there are multiple function definitions, the initial value is the last function definition.
|
|
|
|
LocalMember *JS2Metadata::defineHoistedVar(Environment *env, const StringAtom &id, js2val initVal, bool isVar, size_t pos)
|
|
{
|
|
LocalMember *result = NULL;
|
|
FrameListIterator regionalFrameEnd = env->getRegionalEnvironment();
|
|
NonWithFrame *regionalFrame = checked_cast<NonWithFrame *>(*regionalFrameEnd);
|
|
ASSERT((regionalFrame->kind == PackageKind) || (regionalFrame->kind == ParameterFrameKind));
|
|
rescan:
|
|
// run through all the existing bindings, to see if this variable already exists.
|
|
LocalBinding *bindingResult = NULL;
|
|
bool foundMultiple = false;
|
|
LocalBindingEntry *lbeP = regionalFrame->localBindings[id];
|
|
if (lbeP) {
|
|
for (LocalBindingEntry::NS_Iterator i = lbeP->begin(), end = lbeP->end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
if (ns.first == publicNamespace) {
|
|
if (bindingResult) {
|
|
foundMultiple = true;
|
|
break; // it's not important to find more than one duplicate
|
|
}
|
|
else
|
|
bindingResult = ns.second;
|
|
}
|
|
}
|
|
}
|
|
if (((bindingResult == NULL) || !isVar)
|
|
&& (regionalFrame->kind == ParameterFrameKind)
|
|
&& (regionalFrameEnd != env->getBegin())) {
|
|
// re-scan in the frame above the parameter frame
|
|
regionalFrame = checked_cast<NonWithFrame *>(*(regionalFrameEnd - 1));
|
|
goto rescan;
|
|
}
|
|
|
|
if (bindingResult == NULL) {
|
|
if (lbeP == NULL)
|
|
lbeP = ®ionalFrame->localBindings.insert(id);
|
|
result = makeFrameVariable(regionalFrame);
|
|
(*regionalFrame->frameSlots)[checked_cast<FrameVariable *>(result)->frameSlot] = initVal;
|
|
LocalBinding *sb = new LocalBinding(ReadWriteAccess, result, true);
|
|
lbeP->bindingList.push_back(LocalBindingEntry::NamespaceBinding(publicNamespace, sb));
|
|
}
|
|
else {
|
|
if (foundMultiple)
|
|
reportError(Exception::definitionError, "Duplicate definition {0}", pos, id);
|
|
else {
|
|
if ((bindingResult->accesses != ReadWriteAccess)
|
|
|| ((bindingResult->content->memberKind != LocalMember::DynamicVariableMember)
|
|
&& (bindingResult->content->memberKind != LocalMember::FrameVariableMember)))
|
|
reportError(Exception::definitionError, "Illegal redefinition of {0}", pos, id);
|
|
else {
|
|
// in the case of a duplicate parameter, we construct the additional slot
|
|
// so that there's a correspondence with the incoming argument array and
|
|
// change the original parameter to the later slot.
|
|
if (regionalFrame->kind == ParameterFrameKind) {
|
|
result = makeFrameVariable(regionalFrame);
|
|
bindingResult->content = result;
|
|
}
|
|
else {
|
|
result = bindingResult->content;
|
|
writeLocalMember(result, initVal, true, regionalFrame);
|
|
}
|
|
}
|
|
}
|
|
// At this point a hoisted binding of the same var already exists, so there is no need to create another one
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static js2val GlobalObject_isNaN(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 /* argc */)
|
|
{
|
|
float64 d = meta->toFloat64(argv[0]);
|
|
return BOOLEAN_TO_JS2VAL(JSDOUBLE_IS_NaN(d));
|
|
}
|
|
|
|
static js2val GlobalObject_isFinite(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 /* argc */)
|
|
{
|
|
float64 d = meta->toFloat64(argv[0]);
|
|
return BOOLEAN_TO_JS2VAL(JSDOUBLE_IS_FINITE(d));
|
|
}
|
|
|
|
static js2val GlobalObject_toString(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 /* argc */)
|
|
{
|
|
return STRING_TO_JS2VAL(meta->engine->allocString("[object global]"));
|
|
}
|
|
|
|
static js2val GlobalObject_eval(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 argc)
|
|
{
|
|
if (argc) {
|
|
if (!JS2VAL_IS_STRING(argv[0]))
|
|
return argv[0];
|
|
// need to reset the environment to the one in operation when eval was called so
|
|
// that eval code can affect the apppropriate scopes.
|
|
meta->engine->jsr(meta->engine->phase, NULL, meta->engine->sp - meta->engine->execStack, JS2VAL_VOID, meta->engine->activationStackTop[-1].env);
|
|
js2val result = meta->readEvalString(*meta->toString(argv[0]), widenCString("Eval Source"));
|
|
meta->engine->rts();
|
|
return result;
|
|
}
|
|
else
|
|
return JS2VAL_UNDEFINED;
|
|
}
|
|
|
|
/* See ECMA-262 15.1.2.5 */
|
|
static js2val GlobalObject_unescape(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 argc)
|
|
{
|
|
const String *str = meta->toString(argv[0]);
|
|
const char16 *chars = str->data();
|
|
uint32 length = str->length();
|
|
|
|
/* Don't bother allocating less space for the new string. */
|
|
char16 *newchars = new char16[length + 1];
|
|
|
|
uint32 ni = 0;
|
|
uint32 i = 0;
|
|
char16 ch;
|
|
while (i < length) {
|
|
ch = chars[i++];
|
|
if (ch == '%') {
|
|
if (i + 1 < length &&
|
|
JS7_ISHEX(chars[i]) && JS7_ISHEX(chars[i + 1]))
|
|
{
|
|
ch = JS7_UNHEX(chars[i]) * 16 + JS7_UNHEX(chars[i + 1]);
|
|
i += 2;
|
|
} else if (i + 4 < length && chars[i] == 'u' &&
|
|
JS7_ISHEX(chars[i + 1]) && JS7_ISHEX(chars[i + 2]) &&
|
|
JS7_ISHEX(chars[i + 3]) && JS7_ISHEX(chars[i + 4]))
|
|
{
|
|
ch = (((((JS7_UNHEX(chars[i + 1]) << 4)
|
|
+ JS7_UNHEX(chars[i + 2])) << 4)
|
|
+ JS7_UNHEX(chars[i + 3])) << 4)
|
|
+ JS7_UNHEX(chars[i + 4]);
|
|
i += 5;
|
|
}
|
|
}
|
|
newchars[ni++] = ch;
|
|
}
|
|
newchars[ni] = 0;
|
|
String s(newchars, ni);
|
|
return meta->engine->allocString(s);
|
|
}
|
|
|
|
// Taken from jsstr.c...
|
|
/*
|
|
* Stuff to emulate the old libmocha escape, which took a second argument
|
|
* giving the type of escape to perform. Retained for compatibility, and
|
|
* copied here to avoid reliance on net.h, mkparse.c/NET_EscapeBytes.
|
|
*/
|
|
|
|
#define URL_XALPHAS ((uint8) 1)
|
|
#define URL_XPALPHAS ((uint8) 2)
|
|
#define URL_PATH ((uint8) 4)
|
|
|
|
static const uint8 urlCharType[256] =
|
|
/* Bit 0 xalpha -- the alphas
|
|
* Bit 1 xpalpha -- as xalpha but
|
|
* converts spaces to plus and plus to %20
|
|
* Bit 2 ... path -- as xalphas but doesn't escape '/'
|
|
*/
|
|
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
|
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x */
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1x */
|
|
0,0,0,0,0,0,0,0,0,0,7,4,0,7,7,4, /* 2x !"#$%&'()*+,-./ */
|
|
7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0, /* 3x 0123456789:;<=>? */
|
|
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 4x @ABCDEFGHIJKLMNO */
|
|
7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,7, /* 5X PQRSTUVWXYZ[\]^_ */
|
|
0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* 6x `abcdefghijklmno */
|
|
7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0, /* 7X pqrstuvwxyz{\}~ DEL */
|
|
0, };
|
|
|
|
/* This matches the ECMA escape set when mask is 7 (default.) */
|
|
|
|
#define IS_OK(C, mask) (urlCharType[((uint8) (C))] & (mask))
|
|
|
|
/* See ECMA-262 15.1.2.4. */
|
|
static js2val GlobalObject_escape(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 argc)
|
|
{
|
|
uint32 newlength;
|
|
char16 ch;
|
|
const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
|
|
|
int32 mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
|
|
if (argc > 1) {
|
|
float64 d = meta->toFloat64(argv[1]);
|
|
if (!JSDOUBLE_IS_FINITE(d) ||
|
|
(mask = (int32)d) != d ||
|
|
mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))
|
|
{
|
|
meta->reportError(Exception::badValueError, "Need integral non-zero mask for escape", meta->engine->errorPos());
|
|
}
|
|
}
|
|
|
|
const String *str = meta->toString(argv[0]);
|
|
const char16 *chars = str->data();
|
|
uint32 length = newlength = str->length();
|
|
|
|
/* Take a first pass and see how big the result string will need to be. */
|
|
uint32 i;
|
|
for (i = 0; i < length; i++) {
|
|
if ((ch = chars[i]) < 128 && IS_OK(ch, mask))
|
|
continue;
|
|
if (ch < 256) {
|
|
if (mask == URL_XPALPHAS && ch == ' ')
|
|
continue; /* The character will be encoded as '+' */
|
|
newlength += 2; /* The character will be encoded as %XX */
|
|
} else {
|
|
newlength += 5; /* The character will be encoded as %uXXXX */
|
|
}
|
|
}
|
|
|
|
char16 *newchars = new char16[newlength + 1];
|
|
uint32 ni;
|
|
for (i = 0, ni = 0; i < length; i++) {
|
|
if ((ch = chars[i]) < 128 && IS_OK(ch, mask)) {
|
|
newchars[ni++] = ch;
|
|
} else if (ch < 256) {
|
|
if (mask == URL_XPALPHAS && ch == ' ') {
|
|
newchars[ni++] = '+'; /* convert spaces to pluses */
|
|
} else {
|
|
newchars[ni++] = '%';
|
|
newchars[ni++] = digits[ch >> 4];
|
|
newchars[ni++] = digits[ch & 0xF];
|
|
}
|
|
} else {
|
|
newchars[ni++] = '%';
|
|
newchars[ni++] = 'u';
|
|
newchars[ni++] = digits[ch >> 12];
|
|
newchars[ni++] = digits[(ch & 0xF00) >> 8];
|
|
newchars[ni++] = digits[(ch & 0xF0) >> 4];
|
|
newchars[ni++] = digits[ch & 0xF];
|
|
}
|
|
}
|
|
ASSERT(ni == newlength);
|
|
newchars[newlength] = 0;
|
|
String s(newchars, newlength);
|
|
return meta->engine->allocString(s);
|
|
}
|
|
|
|
static js2val GlobalObject_parseInt(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 argc)
|
|
{
|
|
if (argc > 0) {
|
|
const String *str = meta->toString(argv[0]);
|
|
const char16 *chars = str->data();
|
|
uint32 length = str->length();
|
|
const char16 *numEnd;
|
|
uint base = 0;
|
|
|
|
// we need to handle the prefix & radix ourselves as 'stringToInteger' does it differently
|
|
const char16 *strEnd = chars + length;
|
|
const char16 *strStart = skipWhiteSpace(chars, strEnd);
|
|
if (strStart == strEnd)
|
|
return meta->engine->nanValue;
|
|
|
|
bool neg = false;
|
|
if (*strStart == '-') {
|
|
neg = true;
|
|
strStart++;
|
|
}
|
|
else {
|
|
if (*strStart == '+')
|
|
strStart++;
|
|
}
|
|
|
|
if (argc > 1) {
|
|
float64 d = meta->toFloat64(argv[1]);
|
|
base = JS2Engine::float64toInt32(d);
|
|
if ((base != 0) && ((base < 2) || (base > 36)))
|
|
return meta->engine->nanValue;
|
|
}
|
|
if (*strStart == '0') {
|
|
if ((strStart[1] & ~0x20) == 'X') {
|
|
base = 16;
|
|
strStart += 2;
|
|
}
|
|
else {
|
|
// backwards octal support
|
|
if ((argc == 1) && ((strStart + 1) != strEnd) && meta->cxt.E3compatibility) {
|
|
base = 8;
|
|
strStart++;
|
|
}
|
|
}
|
|
}
|
|
if (strStart == strEnd)
|
|
return meta->engine->nanValue;
|
|
|
|
float64 d = stringToInteger(strStart, strEnd, numEnd, base);
|
|
|
|
return meta->engine->allocNumber((neg) ? -d : d);
|
|
}
|
|
else
|
|
return meta->engine->nanValue;
|
|
|
|
}
|
|
|
|
static js2val GlobalObject_parseFloat(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 argc)
|
|
{
|
|
if (argc > 0) {
|
|
const String *str = meta->toString(argv[0]);
|
|
const char16 *chars = str->data();
|
|
uint32 length = str->length();
|
|
const char16 *numEnd;
|
|
const char16 *strEnd = chars + length;
|
|
const char16 *strStart = skipWhiteSpace(chars, strEnd);
|
|
if (strStart == strEnd)
|
|
return meta->engine->nanValue;
|
|
|
|
float64 d = stringToDouble(strStart, strEnd, numEnd);
|
|
if (numEnd == strStart)
|
|
return meta->engine->nanValue;
|
|
return meta->engine->allocNumber(d);
|
|
}
|
|
else
|
|
return meta->engine->nanValue;
|
|
}
|
|
|
|
|
|
static js2val GlobalObject_version(JS2Metadata *meta, const js2val /* thisValue */, js2val argv[], uint32 argc)
|
|
{
|
|
if (argc > 0 && JS2VAL_IS_INT(argv[0])) {
|
|
int32 v = JS2VAL_TO_INT(argv[0]);
|
|
int32 oldV = meta->version;
|
|
meta->version = v;
|
|
return INT_TO_JS2VAL(oldV);
|
|
}
|
|
else
|
|
return INT_TO_JS2VAL(meta->version);
|
|
}
|
|
|
|
void JS2Metadata::addGlobalObjectFunction(char *name, NativeCode *code, uint32 length)
|
|
{
|
|
FunctionInstance *fInst = createFunctionInstance(env, true, true, code, length, NULL);
|
|
DEFINE_ROOTKEEPER(this, rk1, fInst);
|
|
createDynamicProperty(glob, world.identifiers[name], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, true);
|
|
}
|
|
|
|
static js2val Object_Constructor(JS2Metadata *meta, const js2val thisValue, js2val argv[], uint32 argc)
|
|
{
|
|
if ((argc == 0) || JS2VAL_IS_NULL(argv[0]) || JS2VAL_IS_UNDEFINED(argv[0]))
|
|
return OBJECT_TO_JS2VAL(new (meta) SimpleInstance(meta, meta->objectClass->prototype, meta->objectClass));
|
|
else {
|
|
if (JS2VAL_IS_OBJECT(argv[0]))
|
|
return argv[0]; // XXX special handling for host objects?
|
|
else
|
|
return meta->toObject(argv[0]);
|
|
}
|
|
}
|
|
|
|
static js2val Object_toString(JS2Metadata *meta, const js2val thisValue, js2val /* argv */ [], uint32 /* argc */)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(thisValue);
|
|
if ((obj->kind == PackageKind) && meta->cxt.E3compatibility) {
|
|
// special case this for now, ECMA3 test sanity...
|
|
return GlobalObject_toString(meta, thisValue, NULL, 0);
|
|
}
|
|
else {
|
|
if (obj->kind == ClassKind && meta->cxt.E3compatibility) {
|
|
String s = widenCString("[object Function]");
|
|
return STRING_TO_JS2VAL(meta->engine->allocString(s));
|
|
}
|
|
else {
|
|
JS2Class *type = (checked_cast<SimpleInstance *>(obj))->type;
|
|
String s = "[object " + type->name + "]";
|
|
return STRING_TO_JS2VAL(meta->engine->allocString(s));
|
|
}
|
|
}
|
|
}
|
|
|
|
static js2val Object_underbarProtoGet(JS2Metadata *meta, const js2val thisValue, js2val /* argv */ [], uint32 /* argc */)
|
|
{
|
|
JS2Object *obj;
|
|
if (!JS2VAL_IS_OBJECT(thisValue))
|
|
obj = JS2VAL_TO_OBJECT(meta->toObject(thisValue));
|
|
else
|
|
obj = JS2VAL_TO_OBJECT(thisValue);
|
|
if (obj->kind == SimpleInstanceKind)
|
|
return (checked_cast<SimpleInstance *>(obj))->super;
|
|
else
|
|
return JS2VAL_UNDEFINED;
|
|
}
|
|
|
|
static js2val Object_underbarProtoSet(JS2Metadata *meta, const js2val thisValue, js2val *argv, uint32 argc)
|
|
{
|
|
JS2Object *obj;
|
|
if (!JS2VAL_IS_OBJECT(thisValue))
|
|
obj = JS2VAL_TO_OBJECT(meta->toObject(thisValue));
|
|
else
|
|
obj = JS2VAL_TO_OBJECT(thisValue);
|
|
if ((argc > 0) && (obj->kind == SimpleInstanceKind)) {
|
|
(checked_cast<SimpleInstance *>(obj))->super = argv[0];
|
|
return argv[0];
|
|
}
|
|
return JS2VAL_UNDEFINED;
|
|
}
|
|
|
|
static js2val Object_hasOwnProperty(JS2Metadata *meta, const js2val thisValue, js2val *argv, uint32 argc)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(thisValue);
|
|
Multiname mn(meta->world.identifiers[*meta->toString(argv[0])]);
|
|
bool isEnumerable;
|
|
if (!meta->findLocalMember(obj, &mn, ReadAccess, isEnumerable))
|
|
return JS2VAL_FALSE;
|
|
return JS2VAL_TRUE;
|
|
}
|
|
|
|
static js2val Object_isPropertyOf(JS2Metadata *meta, const js2val thisValue, js2val *argv, uint32 argc)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
js2val v = argv[0];
|
|
while (JS2VAL_IS_OBJECT(v)) {
|
|
JS2Object *v_obj = JS2VAL_TO_OBJECT(v);
|
|
if (v_obj->kind != SimpleInstanceKind)
|
|
break;
|
|
v = checked_cast<SimpleInstance *>(v_obj)->super;
|
|
if (JS2VAL_IS_NULL(v))
|
|
break;
|
|
if (v == thisValue)
|
|
return JS2VAL_TRUE;
|
|
}
|
|
return JS2VAL_FALSE;
|
|
}
|
|
|
|
static js2val Object_propertyIsEnumerble(JS2Metadata *meta, const js2val thisValue, js2val *argv, uint32 argc)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(thisValue);
|
|
Multiname mn(meta->world.identifiers[*meta->toString(argv[0])]);
|
|
bool isEnumerable;
|
|
LocalMember *m = meta->findLocalMember(obj, &mn, ReadAccess, isEnumerable);
|
|
if ((m == NULL) || !isEnumerable)
|
|
return JS2VAL_FALSE;
|
|
return JS2VAL_TRUE;
|
|
}
|
|
|
|
|
|
static js2val class_underbarProtoGet(JS2Metadata *meta, const js2val thisValue, js2val /* argv */ [], uint32 /* argc */)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(thisValue);
|
|
ASSERT(obj->kind == ClassKind);
|
|
// JS2Class *c = checked_cast<JS2Class *>(obj);
|
|
return OBJECT_TO_JS2VAL(meta->functionClass->prototype);
|
|
}
|
|
|
|
js2val Array_lengthGet(JS2Metadata *meta, const js2val thisValue, js2val /* argv */ [], uint32 /* argc */)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(thisValue);
|
|
ArrayInstance *arrInst = checked_cast<ArrayInstance *>(obj);
|
|
|
|
return meta->engine->allocNumber(arrInst->length);
|
|
}
|
|
|
|
js2val Array_lengthSet(JS2Metadata *meta, const js2val thisValue, js2val *argv, uint32 argc)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisValue));
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(thisValue);
|
|
ArrayInstance *arrInst = checked_cast<ArrayInstance *>(obj);
|
|
|
|
uint32 newLength = meta->valToUInt32(argv[0]);
|
|
if (newLength < arrInst->length) {
|
|
// need to delete all the elements above the new length
|
|
bool deleteResult;
|
|
for (uint32 i = newLength; i < arrInst->length; i++) {
|
|
const String *str = meta->engine->numberToString(i);
|
|
if (meta->world.identifiers.hasEntry(*str)) {
|
|
Multiname mn(meta->world.identifiers[*str], meta->publicNamespace);
|
|
meta->arrayClass->Delete(meta, thisValue, &mn, NULL, &deleteResult);
|
|
}
|
|
}
|
|
}
|
|
arrInst->length = newLength;
|
|
return JS2VAL_UNDEFINED;
|
|
}
|
|
|
|
static js2val Object_valueOf(JS2Metadata *meta, const js2val thisValue, js2val /* argv */ [], uint32 /* argc */)
|
|
{
|
|
return thisValue;
|
|
}
|
|
|
|
|
|
#define MAKEBUILTINCLASS(c, super, dynamic, final, name, defaultVal) c = new (this) JS2Class(super, JS2VAL_NULL, new (this) Namespace(engine->private_StringAtom), dynamic, final, name); c->complete = true; c->defaultValue = defaultVal;
|
|
|
|
JS2Metadata::JS2Metadata(World &world) :
|
|
world(world),
|
|
// engine(new JS2Engine(world)),
|
|
// publicNamespace(new Namespace(engine->public_StringAtom)),
|
|
bCon(new BytecodeContainer()),
|
|
// glob(new Package(widenCString("global"), new Namespace(&world.identifiers["internal"]))),
|
|
// env(new Environment(new MetaData::SystemFrame(), glob)),
|
|
flags(JS1),
|
|
version(JS2VERSION_DEFAULT),
|
|
showTrees(false),
|
|
referenceArena(NULL),
|
|
pond(POND_SIZE, NULL)
|
|
{
|
|
engine = new JS2Engine(this, world);
|
|
publicNamespace = new (this) Namespace(engine->public_StringAtom);
|
|
glob = new (this) Package(widenCString("global"), new (this) Namespace(world.identifiers["internal"]));
|
|
env = new (this) Environment(new (this) SystemFrame(), glob);
|
|
|
|
|
|
rngInitialized = false;
|
|
// engine->meta = this;
|
|
|
|
cxt.openNamespaces.clear();
|
|
cxt.openNamespaces.push_back(publicNamespace);
|
|
|
|
MAKEBUILTINCLASS(objectClass, NULL, false, false, engine->Object_StringAtom, JS2VAL_VOID);
|
|
MAKEBUILTINCLASS(undefinedClass, objectClass, false, true, engine->undefined_StringAtom, JS2VAL_VOID);
|
|
nullClass = new (this) JS2NullClass(objectClass, JS2VAL_NULL, new (this) Namespace(engine->private_StringAtom), false, true, engine->null_StringAtom); nullClass->complete = true; nullClass->defaultValue = JS2VAL_NULL;
|
|
MAKEBUILTINCLASS(booleanClass, objectClass, false, true, world.identifiers["Boolean"], JS2VAL_FALSE);
|
|
MAKEBUILTINCLASS(generalNumberClass, objectClass, false, false, world.identifiers["general number"], engine->nanValue);
|
|
MAKEBUILTINCLASS(numberClass, generalNumberClass, false, true, world.identifiers["Number"], engine->nanValue);
|
|
integerClass = new (this) JS2IntegerClass(numberClass, JS2VAL_NULL, new (this) Namespace(engine->private_StringAtom), false, true, world.identifiers["Integer"]); integerClass->complete = true; integerClass->defaultValue = JS2VAL_ZERO;
|
|
MAKEBUILTINCLASS(characterClass, objectClass, false, true, world.identifiers["Character"], JS2VAL_ZERO);
|
|
stringClass = new (this) JS2StringClass(objectClass, JS2VAL_NULL, new (this) Namespace(engine->private_StringAtom), false, true, world.identifiers["String"]); stringClass->complete = true; stringClass->defaultValue = JS2VAL_NULL;
|
|
MAKEBUILTINCLASS(namespaceClass, objectClass, false, true, world.identifiers["namespace"], JS2VAL_NULL);
|
|
MAKEBUILTINCLASS(attributeClass, objectClass, false, true, world.identifiers["attribute"], JS2VAL_NULL);
|
|
MAKEBUILTINCLASS(classClass, objectClass, false, true, world.identifiers["Class"], JS2VAL_NULL);
|
|
MAKEBUILTINCLASS(functionClass, objectClass, true, true, engine->Function_StringAtom, JS2VAL_NULL);
|
|
MAKEBUILTINCLASS(packageClass, objectClass, true, true, world.identifiers["Package"], JS2VAL_NULL);
|
|
argumentsClass = new (this) JS2ArgumentsClass(objectClass, JS2VAL_NULL, new (this) Namespace(engine->private_StringAtom), false, true, world.identifiers["Object"]); argumentsClass->complete = true; argumentsClass->defaultValue = JS2VAL_NULL;
|
|
|
|
// A 'forbidden' member, used to mark hidden bindings
|
|
forbiddenMember = new LocalMember(Member::ForbiddenMember, true);
|
|
forbiddenMember->acquire();
|
|
|
|
FunctionInstance *fInst = NULL;
|
|
DEFINE_ROOTKEEPER(this, rk1, fInst);
|
|
Variable *v;
|
|
|
|
// XXX Built-in Attributes... XXX
|
|
/*
|
|
XXX see EvalAttributeExpression, where identifiers are being handled for now...
|
|
|
|
CompoundAttribute *attr = new CompoundAttribute();
|
|
attr->dynamic = true;
|
|
v = new Variable(attributeClass, OBJECT_TO_JS2VAL(attr), true);
|
|
defineLocalMember(env, &world.identifiers["dynamic"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0);
|
|
*/
|
|
|
|
/*** ECMA 3 Global Object ***/
|
|
// Non-function properties of the global object : 'undefined', 'NaN' and 'Infinity'
|
|
createDynamicProperty(glob, engine->undefined_StringAtom, JS2VAL_UNDEFINED, ReadAccess, true, false);
|
|
createDynamicProperty(glob, world.identifiers["NaN"], engine->nanValue, ReadAccess, true, false);
|
|
createDynamicProperty(glob, world.identifiers["Infinity"], engine->posInfValue, ReadAccess, true, false);
|
|
|
|
|
|
/*** ECMA 3 Object Class ***/
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(objectClass), true);
|
|
defineLocalMember(env, world.identifiers["Object"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
// Function properties of the Object prototype object
|
|
objectClass->prototype = OBJECT_TO_JS2VAL(new (this) SimpleInstance(this, JS2VAL_NULL, objectClass));
|
|
objectClass->construct = Object_Constructor;
|
|
objectClass->call = Object_Constructor;
|
|
// Adding "prototype" as a static member of the class - not a dynamic property
|
|
env->addFrame(objectClass);
|
|
v = new Variable(objectClass, OBJECT_TO_JS2VAL(objectClass->prototype), true);
|
|
defineLocalMember(env, engine->prototype_StringAtom, NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, false);
|
|
v = new Variable(objectClass, INT_TO_JS2VAL(1), true);
|
|
defineLocalMember(env, engine->length_StringAtom, NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, false);
|
|
env->removeTopFrame();
|
|
|
|
glob->super = objectClass->prototype;
|
|
|
|
/*** ECMA 3 Function Class ***/
|
|
// Need this initialized early, as subsequent FunctionInstances need the Function.prototype value
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(functionClass), true);
|
|
defineLocalMember(env, world.identifiers["Function"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initFunctionObject(this);
|
|
|
|
// Function properties of the global object
|
|
addGlobalObjectFunction("isNaN", GlobalObject_isNaN, 1);
|
|
addGlobalObjectFunction("isFinite", GlobalObject_isFinite, 1);
|
|
addGlobalObjectFunction("eval", GlobalObject_eval, 1);
|
|
addGlobalObjectFunction("toString", GlobalObject_toString, 1);
|
|
addGlobalObjectFunction("valueOf", GlobalObject_toString, 1);
|
|
addGlobalObjectFunction("unescape", GlobalObject_unescape, 1);
|
|
addGlobalObjectFunction("escape", GlobalObject_escape, 1);
|
|
addGlobalObjectFunction("parseInt", GlobalObject_parseInt, 2);
|
|
addGlobalObjectFunction("parseFloat", GlobalObject_parseFloat, 1);
|
|
addGlobalObjectFunction("version", GlobalObject_version, 1);
|
|
|
|
|
|
// Add __proto__ as a getter/setter instance member to Object & class
|
|
Multiname proto_mn(world.identifiers["__proto__"], publicNamespace);
|
|
fInst = createFunctionInstance(env, true, true, class_underbarProtoGet, 0, NULL);
|
|
InstanceGetter *g = new InstanceGetter(&proto_mn, fInst, objectClass, true, true);
|
|
defineInstanceMember(classClass, &cxt, proto_mn.name, *proto_mn.nsList, Attribute::NoOverride, false, g, 0);
|
|
fInst = createFunctionInstance(env, true, true, class_underbarProtoGet, 0, NULL);
|
|
InstanceSetter *s = new InstanceSetter(&proto_mn, fInst, objectClass, true, true);
|
|
defineInstanceMember(classClass, &cxt, proto_mn.name, *proto_mn.nsList, Attribute::NoOverride, false, s, 0);
|
|
|
|
fInst = createFunctionInstance(env, true, true, Object_underbarProtoGet, 0, NULL);
|
|
g = new InstanceGetter(&proto_mn, fInst, objectClass, true, true);
|
|
defineInstanceMember(objectClass, &cxt, proto_mn.name, *proto_mn.nsList, Attribute::NoOverride, false, g, 0);
|
|
fInst = createFunctionInstance(env, true, true, Object_underbarProtoSet, 0, NULL);
|
|
s = new InstanceSetter(&proto_mn, fInst, objectClass, true, true);
|
|
defineInstanceMember(objectClass, &cxt, proto_mn.name, *proto_mn.nsList, Attribute::NoOverride, false, s, 0);
|
|
|
|
|
|
// Adding 'toString' etc to the Object.prototype XXX Or make this a static class member?
|
|
fInst = createFunctionInstance(env, true, true, Object_toString, 0, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(objectClass->prototype), engine->toString_StringAtom, OBJECT_TO_JS2VAL(fInst), ReadAccess, true, false);
|
|
// and 'valueOf'
|
|
fInst = createFunctionInstance(env, true, true, Object_valueOf, 0, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(objectClass->prototype), engine->valueOf_StringAtom, OBJECT_TO_JS2VAL(fInst), ReadAccess, true, false);
|
|
// and 'constructor'
|
|
fInst = createFunctionInstance(env, true, true, Object_Constructor, 0, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(objectClass->prototype), world.identifiers["constructor"], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, false);
|
|
// and various property/enumerable functions
|
|
fInst = createFunctionInstance(env, true, true, Object_hasOwnProperty, 0, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(objectClass->prototype), world.identifiers["hasOwnProperty"], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, false);
|
|
fInst = createFunctionInstance(env, true, true, Object_isPropertyOf, 0, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(objectClass->prototype), world.identifiers["isPropertyOf"], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, false);
|
|
fInst = createFunctionInstance(env, true, true, Object_propertyIsEnumerble, 0, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(objectClass->prototype), world.identifiers["propertyIsEnumerable"], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, false);
|
|
|
|
|
|
/*** ECMA 4 Integer Class ***/
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(integerClass), true);
|
|
defineLocalMember(env, world.identifiers["Integer"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
|
|
|
|
/*** ECMA 3 Date Class ***/
|
|
MAKEBUILTINCLASS(dateClass, objectClass, true, true, world.identifiers["Date"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(dateClass), true);
|
|
defineLocalMember(env, world.identifiers["Date"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initDateObject(this);
|
|
|
|
/*** ECMA 3 RegExp Class ***/
|
|
MAKEBUILTINCLASS(regexpClass, objectClass, true, true, world.identifiers["RegExp"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(regexpClass), true);
|
|
defineLocalMember(env, world.identifiers["RegExp"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initRegExpObject(this);
|
|
|
|
/*** ECMA 3 String Class ***/
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(stringClass), true);
|
|
defineLocalMember(env, world.identifiers["String"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initStringObject(this);
|
|
|
|
/*** ECMA 3 Number Class ***/
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(numberClass), true);
|
|
defineLocalMember(env, world.identifiers["Number"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initNumberObject(this);
|
|
|
|
/*** ECMA 3 Boolean Class ***/
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(booleanClass), true);
|
|
defineLocalMember(env, world.identifiers["Boolean"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initBooleanObject(this);
|
|
|
|
/*** ECMA 3 Math Object ***/
|
|
MAKEBUILTINCLASS(mathClass, objectClass, false, true, world.identifiers["Math"], JS2VAL_FALSE);
|
|
SimpleInstance *mathObject = new (this) SimpleInstance(this, objectClass->prototype, mathClass);
|
|
v = new Variable(objectClass, OBJECT_TO_JS2VAL(mathObject), true);
|
|
defineLocalMember(env, world.identifiers["Math"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initMathObject(this, mathObject);
|
|
|
|
/*** ECMA 3 Array Class ***/
|
|
arrayClass = new (this) JS2ArrayClass(objectClass, JS2VAL_NULL, new (this) Namespace(engine->private_StringAtom), true, true, world.identifiers["Array"]); arrayClass->complete = true; arrayClass->defaultValue = JS2VAL_NULL;
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(arrayClass), true);
|
|
defineLocalMember(env, world.identifiers["Array"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initArrayObject(this);
|
|
/*
|
|
Multiname length_mn(&world.identifiers["length"], publicNamespace);
|
|
fInst = createFunctionInstance(env, true, true, Array_lengthGet, 0, NULL);
|
|
g = new InstanceGetter(&length_mn, fInst, objectClass, true, true);
|
|
defineInstanceMember(arrayClass, &cxt, length_mn.name, *length_mn.nsList, Attribute::NoOverride, false, g, 0);
|
|
fInst = createFunctionInstance(env, true, true, Array_lengthSet, 0, NULL);
|
|
s = new InstanceSetter(&length_mn, fInst, objectClass, true, true);
|
|
defineInstanceMember(arrayClass, &cxt, length_mn.name, *length_mn.nsList, Attribute::NoOverride, false, s, 0);
|
|
*/
|
|
/*** ECMA 3 Error Classes ***/
|
|
MAKEBUILTINCLASS(errorClass, objectClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(errorClass), true);
|
|
defineLocalMember(env, world.identifiers["Error"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
MAKEBUILTINCLASS(evalErrorClass, errorClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(evalErrorClass), true);
|
|
defineLocalMember(env, world.identifiers["EvalError"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
MAKEBUILTINCLASS(rangeErrorClass, errorClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(rangeErrorClass), true);
|
|
defineLocalMember(env, world.identifiers["RangeError"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
MAKEBUILTINCLASS(referenceErrorClass, errorClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(referenceErrorClass), true);
|
|
defineLocalMember(env, world.identifiers["ReferenceError"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
MAKEBUILTINCLASS(syntaxErrorClass, errorClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(syntaxErrorClass), true);
|
|
defineLocalMember(env, world.identifiers["SyntaxError"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
MAKEBUILTINCLASS(typeErrorClass, errorClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(typeErrorClass), true);
|
|
defineLocalMember(env, world.identifiers["TypeError"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
MAKEBUILTINCLASS(uriErrorClass, errorClass, true, true, world.identifiers["Error"], JS2VAL_NULL);
|
|
v = new Variable(classClass, OBJECT_TO_JS2VAL(uriErrorClass), true);
|
|
defineLocalMember(env, world.identifiers["URIError"], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, true);
|
|
initErrorObject(this);
|
|
|
|
}
|
|
|
|
JS2Metadata::~JS2Metadata()
|
|
{
|
|
bConList.clear();
|
|
targetList.clear();
|
|
clear(); // don't blow off the contents of 'this' as the destructors for
|
|
// embedded objects will get messed up (as they run on exit).
|
|
delete engine;
|
|
delete forbiddenMember;
|
|
if (bCon) delete bCon;
|
|
}
|
|
|
|
JS2Class *JS2Metadata::objectType(JS2Object *obj)
|
|
{
|
|
switch (obj->kind) {
|
|
case AttributeObjectKind:
|
|
return attributeClass;
|
|
case MultinameKind:
|
|
return namespaceClass;
|
|
case ClassKind:
|
|
return classClass;
|
|
|
|
case SimpleInstanceKind:
|
|
return checked_cast<SimpleInstance *>(obj)->type;
|
|
|
|
case PackageKind:
|
|
return packageClass;
|
|
|
|
case ParameterFrameKind:
|
|
return objectClass;
|
|
|
|
case SystemKind:
|
|
case BlockFrameKind:
|
|
default:
|
|
ASSERT(false);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// objectType(o) returns an OBJECT o's most specific type.
|
|
JS2Class *JS2Metadata::objectType(js2val objVal)
|
|
{
|
|
if (JS2VAL_IS_NULL(objVal))
|
|
return nullClass;
|
|
if (JS2VAL_IS_OBJECT(objVal))
|
|
return objectType(JS2VAL_TO_OBJECT(objVal));
|
|
if (JS2VAL_IS_VOID(objVal))
|
|
return undefinedClass;
|
|
if (JS2VAL_IS_NUMBER(objVal))
|
|
return numberClass;
|
|
if (JS2VAL_IS_STRING(objVal)) {
|
|
if (JS2VAL_TO_STRING(objVal)->length() == 1)
|
|
return stringClass; // XXX characterClass; Need the connection from class Character to
|
|
// class String - i.e. access to all the functions in 'String.prototype' -
|
|
// but (some of) those routines depened on being called on a StringInstance...
|
|
else
|
|
return stringClass;
|
|
}
|
|
if (JS2VAL_IS_BOOLEAN(objVal))
|
|
return booleanClass;
|
|
ASSERT(false);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// hasType(o, c) returns true if o is an instance of class c (or one of c's subclasses).
|
|
// It considers null to be an instance of the classes Null and Object only.
|
|
bool JS2Metadata::hasType(js2val objVal, JS2Class *c)
|
|
{
|
|
return c->isAncestor(objectType(objVal));
|
|
}
|
|
|
|
InstanceMember *JS2Metadata::findLocalInstanceMember(JS2Class *limit, Multiname *multiname, Access access)
|
|
{
|
|
InstanceMember *result = NULL;
|
|
InstanceBindingEntry *ibeP = limit->instanceBindings[multiname->name];
|
|
if (ibeP) {
|
|
for (InstanceBindingEntry::NS_Iterator i = ibeP->begin(), end = ibeP->end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access) && multiname->listContains(ns.first)) {
|
|
if (result && (ns.second->content != result))
|
|
reportError(Exception::propertyAccessError, "Ambiguous reference to {0}", engine->errorPos(), multiname->name);
|
|
else
|
|
result = ns.second->content;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Start from the root class (Object) and proceed through more specific classes that are ancestors of c
|
|
InstanceMember *JS2Metadata::findBaseInstanceMember(JS2Class *c, Multiname *multiname, Access access)
|
|
{
|
|
InstanceMember *result = NULL;
|
|
if (c->super) {
|
|
result = findBaseInstanceMember(c->super, multiname, access);
|
|
if (result) return result;
|
|
}
|
|
return findLocalInstanceMember(c, multiname, access);
|
|
}
|
|
|
|
// getDerivedInstanceMember returns the most derived instance member whose name includes that of mBase and
|
|
// whose access includes access. The caller of getDerivedInstanceMember ensures that such a member always exists.
|
|
// If accesses is readWrite then it is possible that this search could find both a getter and a setter defined in the same class;
|
|
// in this case either the getter or the setter is returned at the implementation's discretion
|
|
InstanceMember *JS2Metadata::getDerivedInstanceMember(JS2Class *c, InstanceMember *mBase, Access access)
|
|
{
|
|
InstanceBindingEntry *ibeP = c->instanceBindings[mBase->multiname->name];
|
|
if (ibeP) {
|
|
for (InstanceBindingEntry::NS_Iterator i = ibeP->begin(), end = ibeP->end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access) && mBase->multiname->listContains(ns.first))
|
|
return ns.second->content;
|
|
}
|
|
}
|
|
ASSERT(c->super);
|
|
return getDerivedInstanceMember(c->super, mBase, access);
|
|
}
|
|
|
|
LocalMember *JS2Metadata::findLocalMember(JS2Object *container, Multiname *multiname, Access access, bool &enumerable)
|
|
{
|
|
LocalMember *found = NULL;
|
|
LocalBindingMap *lMap;
|
|
if (container->kind == SimpleInstanceKind)
|
|
lMap = &checked_cast<SimpleInstance *>(container)->localBindings;
|
|
else
|
|
lMap = &checked_cast<NonWithFrame *>(container)->localBindings;
|
|
|
|
if (lMap->size()) {
|
|
LocalBindingEntry *lbeP = (*lMap)[multiname->name];
|
|
if (lbeP) {
|
|
for (LocalBindingEntry::NS_Iterator i = lbeP->begin(), end = lbeP->end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding &ns = *i;
|
|
if ((ns.second->accesses & access) && multiname->listContains(ns.first)) {
|
|
if (found && (ns.second->content != found))
|
|
reportError(Exception::propertyAccessError, "Ambiguous reference to {0}", engine->errorPos(), multiname->name);
|
|
else {
|
|
found = ns.second->content;
|
|
enumerable = ns.second->enumerable;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
js2val JS2Metadata::getSuperObject(JS2Object *obj)
|
|
{
|
|
switch (obj->kind) {
|
|
case SimpleInstanceKind:
|
|
return checked_cast<SimpleInstance *>(obj)->super;
|
|
break;
|
|
case PackageKind:
|
|
return checked_cast<Package *>(obj)->super;
|
|
break;
|
|
case ClassKind:
|
|
return OBJECT_TO_JS2VAL(checked_cast<JS2Class *>(obj)->super);
|
|
default:
|
|
return JS2VAL_NULL;
|
|
}
|
|
}
|
|
|
|
Member *JS2Metadata::findCommonMember(js2val *base, Multiname *multiname, Access access, bool flat)
|
|
{
|
|
bool isEnumerable;
|
|
Member *m = NULL;
|
|
if (JS2VAL_IS_PRIMITIVE(*base) && cxt.E3compatibility) {
|
|
*base = toObject(*base); // XXX E3 compatibility...
|
|
}
|
|
JS2Object *baseObj = JS2VAL_TO_OBJECT(*base);
|
|
switch (baseObj->kind) {
|
|
case SimpleInstanceKind:
|
|
m = findLocalMember(baseObj, multiname, access, isEnumerable);
|
|
break;
|
|
case PackageKind:
|
|
m = findLocalMember(baseObj, multiname, access, isEnumerable);
|
|
break;
|
|
case ClassKind:
|
|
m = findLocalMember(baseObj, multiname, access, isEnumerable);
|
|
if (m == NULL)
|
|
m = findLocalInstanceMember(checked_cast<JS2Class *>(baseObj), multiname, access);
|
|
break;
|
|
default:
|
|
return NULL; // non-primitive, but not one of the above
|
|
}
|
|
if (m)
|
|
return m;
|
|
js2val superVal = getSuperObject(baseObj);
|
|
if (!JS2VAL_IS_NULL(superVal) && !JS2VAL_IS_UNDEFINED(superVal)) {
|
|
m = findCommonMember(&superVal, multiname, access, flat);
|
|
if ((m != NULL) && flat
|
|
&& ((m->memberKind == Member::DynamicVariableMember)
|
|
|| (m->memberKind == Member::FrameVariableMember)))
|
|
m = NULL;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
bool JS2Metadata::readInstanceMember(js2val containerVal, JS2Class *c, InstanceMember *mBase, Phase phase, js2val *rval)
|
|
{
|
|
InstanceMember *m = getDerivedInstanceMember(c, mBase, ReadAccess);
|
|
switch (m->memberKind) {
|
|
case Member::InstanceVariableMember:
|
|
{
|
|
InstanceVariable *mv = checked_cast<InstanceVariable *>(m);
|
|
if ((phase == CompilePhase) && !mv->immutable)
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
Slot *s = findSlot(containerVal, mv);
|
|
if (JS2VAL_IS_UNINITIALIZED(s->value))
|
|
reportError(Exception::uninitializedError, "Reference to uninitialized instance variable", engine->errorPos());
|
|
*rval = s->value;
|
|
return true;
|
|
}
|
|
case Member::InstanceMethodMember:
|
|
{
|
|
InstanceMethod *im = checked_cast<InstanceMethod *>(m);
|
|
if (phase == CompilePhase)
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
FunctionInstance *fInst = new (this) FunctionInstance(this, functionClass->prototype, functionClass);
|
|
fInst->isMethodClosure = true;
|
|
fInst->fWrap = im->fInst->fWrap;
|
|
fInst->thisObject = containerVal;
|
|
*rval = OBJECT_TO_JS2VAL(fInst);
|
|
return true;
|
|
}
|
|
case Member::InstanceGetterMember:
|
|
{
|
|
InstanceGetter *ig = checked_cast<InstanceGetter *>(m);
|
|
*rval = invokeFunction(ig->fInst, containerVal, NULL, 0, NULL);
|
|
return true;
|
|
}
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JS2Metadata::writeInstanceMember(js2val containerVal, JS2Class *c, InstanceMember *mBase, js2val newValue)
|
|
{
|
|
InstanceMember *m = getDerivedInstanceMember(c, mBase, WriteAccess);
|
|
switch (m->memberKind) {
|
|
case Member::InstanceVariableMember:
|
|
{
|
|
InstanceVariable *mv = checked_cast<InstanceVariable *>(m);
|
|
Slot *s = findSlot(containerVal, mv);
|
|
if (mv->immutable && !JS2VAL_IS_UNINITIALIZED(s->value))
|
|
reportError(Exception::compileExpressionError, "Reinitialization of constant", engine->errorPos());
|
|
s->value = mv->type->ImplicitCoerce(this, newValue);
|
|
return true;
|
|
}
|
|
case Member::InstanceSetterMember:
|
|
{
|
|
InstanceSetter *is = checked_cast<InstanceSetter *>(m);
|
|
invokeFunction(is->fInst, containerVal, &newValue, 1, NULL);
|
|
return true;
|
|
}
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool JS2Metadata::readLocalMember(LocalMember *m, Phase phase, js2val *rval, Frame *container)
|
|
{
|
|
switch (m->memberKind) {
|
|
case LocalMember::ForbiddenMember:
|
|
reportError(Exception::propertyAccessError, "Forbidden access", engine->errorPos());
|
|
break;
|
|
case LocalMember::VariableMember:
|
|
{
|
|
Variable *v = checked_cast<Variable *>(m);
|
|
if ((phase == CompilePhase) && !v->immutable)
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
*rval = v->value;
|
|
return true;
|
|
}
|
|
case LocalMember::FrameVariableMember:
|
|
{
|
|
ASSERT(container);
|
|
if (phase == CompilePhase)
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
FrameVariable *fv = checked_cast<FrameVariable *>(m);
|
|
switch (fv->kind) {
|
|
case FrameVariable::Package:
|
|
{
|
|
ASSERT(container->kind == PackageKind);
|
|
*rval = (*(checked_cast<Package *>(container))->frameSlots)[fv->frameSlot];
|
|
}
|
|
break;
|
|
case FrameVariable::Local:
|
|
{
|
|
ASSERT(container->kind == BlockFrameKind);
|
|
*rval = (*(checked_cast<NonWithFrame *>(container))->frameSlots)[fv->frameSlot];
|
|
}
|
|
break;
|
|
case FrameVariable::Parameter:
|
|
{
|
|
ASSERT(container->kind == ParameterFrameKind);
|
|
ASSERT(fv->frameSlot < (checked_cast<ParameterFrame *>(container))->frameSlots->size());
|
|
*rval = (checked_cast<ParameterFrame *>(container))->argSlots[fv->frameSlot];
|
|
// *rval = (*(checked_cast<ParameterFrame *>(container))->frameSlots)[fv->frameSlot];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
case LocalMember::DynamicVariableMember:
|
|
if (phase == CompilePhase)
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
*rval = (checked_cast<DynamicVariable *>(m))->value;
|
|
return true;
|
|
case LocalMember::ConstructorMethodMember:
|
|
if (phase == CompilePhase)
|
|
reportError(Exception::compileExpressionError, "Inappropriate compile time expression", engine->errorPos());
|
|
*rval = (checked_cast<ConstructorMethod *>(m))->value;
|
|
return true;
|
|
case LocalMember::GetterMember:
|
|
{
|
|
Getter *g = checked_cast<Getter *>(m);
|
|
*rval = invokeFunction(g->code, JS2VAL_VOID, NULL, 0, NULL);
|
|
}
|
|
return true;
|
|
case LocalMember::SetterMember:
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
NOT_REACHED("Bad member kind");
|
|
return false;
|
|
}
|
|
|
|
// Write a value to the local member
|
|
bool JS2Metadata::writeLocalMember(LocalMember *m, js2val newValue, bool initFlag, Frame *container)
|
|
{
|
|
switch (m->memberKind) {
|
|
case LocalMember::ForbiddenMember:
|
|
case LocalMember::ConstructorMethodMember:
|
|
reportError(Exception::propertyAccessError, "Forbidden access", engine->errorPos());
|
|
break;
|
|
case LocalMember::VariableMember:
|
|
{
|
|
Variable *v = checked_cast<Variable *>(m);
|
|
if (!initFlag
|
|
&& (JS2VAL_IS_INACCESSIBLE(v->value)
|
|
|| (v->immutable && !JS2VAL_IS_UNINITIALIZED(v->value))))
|
|
if (!cxt.E3compatibility)
|
|
reportError(Exception::propertyAccessError, "Forbidden access", engine->errorPos());
|
|
else // quietly ignore the write for JS1 compatibility
|
|
return true;
|
|
v->value = v->type->ImplicitCoerce(this, newValue);
|
|
}
|
|
return true;
|
|
case LocalMember::FrameVariableMember:
|
|
{
|
|
ASSERT(container);
|
|
FrameVariable *fv = checked_cast<FrameVariable *>(m);
|
|
switch (fv->kind) {
|
|
case FrameVariable::Package:
|
|
{
|
|
ASSERT(container->kind == PackageKind);
|
|
(*(checked_cast<Package *>(container))->frameSlots)[fv->frameSlot] = newValue;
|
|
}
|
|
break;
|
|
case FrameVariable::Local:
|
|
{
|
|
ASSERT(container->kind == BlockFrameKind);
|
|
(*(checked_cast<NonWithFrame *>(container))->frameSlots)[fv->frameSlot] = newValue;
|
|
}
|
|
break;
|
|
case FrameVariable::Parameter:
|
|
{
|
|
ASSERT(container->kind == ParameterFrameKind);
|
|
ASSERT(fv->frameSlot < (checked_cast<ParameterFrame *>(container))->frameSlots->size());
|
|
(checked_cast<ParameterFrame *>(container))->argSlots[fv->frameSlot] = newValue;
|
|
// (*(checked_cast<ParameterFrame *>(container))->frameSlots)[fv->frameSlot] = newValue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
case LocalMember::DynamicVariableMember:
|
|
(checked_cast<DynamicVariable *>(m))->value = newValue;
|
|
return true;
|
|
case LocalMember::GetterMember:
|
|
ASSERT(false);
|
|
break;
|
|
case LocalMember::SetterMember:
|
|
{
|
|
Setter *s = checked_cast<Setter *>(m);
|
|
invokeFunction(s->code, JS2VAL_VOID, &newValue, 1, NULL);
|
|
}
|
|
return true;
|
|
}
|
|
NOT_REACHED("Bad member kind");
|
|
return false;
|
|
}
|
|
|
|
bool JS2Metadata::hasOwnProperty(JS2Object *obj, const StringAtom &name)
|
|
{
|
|
Multiname *mn = new (this) Multiname(name, publicNamespace);
|
|
DEFINE_ROOTKEEPER(this, rk, mn);
|
|
js2val val = OBJECT_TO_JS2VAL(obj);
|
|
return (findCommonMember(&val, mn, ReadWriteAccess, true) != NULL);
|
|
}
|
|
|
|
// Add the local member with access etc. by name into the map, using the public namespace. An entry may or may not be in the map already
|
|
void JS2Metadata::addPublicVariableToLocalMap(LocalBindingMap *lMap, const StringAtom &name, LocalMember *v, Access access, bool enumerable)
|
|
{
|
|
LocalBinding *new_b = new LocalBinding(access, v, enumerable);
|
|
LocalBindingEntry *lbeP = (*lMap)[name];
|
|
if (lbeP == NULL)
|
|
lbeP = &lMap->insert(name);
|
|
lbeP->bindingList.push_back(LocalBindingEntry::NamespaceBinding(publicNamespace, new_b));
|
|
}
|
|
|
|
// The caller must make sure that the created property does not already exist and does not conflict with any other property.
|
|
DynamicVariable *JS2Metadata::createDynamicProperty(JS2Object *obj, const StringAtom &name, js2val initVal, Access access, bool sealed, bool enumerable)
|
|
{
|
|
DynamicVariable *dv = new DynamicVariable(initVal, sealed);
|
|
LocalBindingMap *lMap;
|
|
if (obj->kind == SimpleInstanceKind)
|
|
lMap = &checked_cast<SimpleInstance *>(obj)->localBindings;
|
|
else
|
|
if (obj->kind == PackageKind)
|
|
lMap = &checked_cast<Package *>(obj)->localBindings;
|
|
else
|
|
ASSERT(false);
|
|
addPublicVariableToLocalMap(lMap, name, dv, access, enumerable);
|
|
return dv;
|
|
}
|
|
|
|
|
|
// Use the slotIndex from the instanceVariable to access the slot
|
|
Slot *JS2Metadata::findSlot(js2val thisObjVal, InstanceVariable *id)
|
|
{
|
|
ASSERT(JS2VAL_IS_OBJECT(thisObjVal)
|
|
&& (JS2VAL_TO_OBJECT(thisObjVal)->kind == SimpleInstanceKind));
|
|
JS2Object *thisObj = JS2VAL_TO_OBJECT(thisObjVal);
|
|
return &checked_cast<SimpleInstance *>(thisObj)->fixedSlots[id->slotIndex];
|
|
}
|
|
|
|
|
|
// gc-mark all contained JS2Objects and their children
|
|
// and then invoke mark on all other structures that contain JS2Objects
|
|
void JS2Metadata::markChildren()
|
|
{
|
|
// XXX - maybe have a separate pool to allocate chunks
|
|
// that are meant to be never collected?
|
|
GCMARKOBJECT(publicNamespace);
|
|
forbiddenMember->mark();
|
|
|
|
GCMARKOBJECT(objectClass);
|
|
GCMARKOBJECT(undefinedClass);
|
|
GCMARKOBJECT(nullClass);
|
|
GCMARKOBJECT(booleanClass);
|
|
GCMARKOBJECT(generalNumberClass);
|
|
GCMARKOBJECT(integerClass);
|
|
GCMARKOBJECT(numberClass);
|
|
GCMARKOBJECT(mathClass);
|
|
GCMARKOBJECT(characterClass);
|
|
GCMARKOBJECT(stringClass);
|
|
GCMARKOBJECT(namespaceClass);
|
|
GCMARKOBJECT(attributeClass);
|
|
GCMARKOBJECT(classClass);
|
|
GCMARKOBJECT(functionClass);
|
|
GCMARKOBJECT(packageClass);
|
|
GCMARKOBJECT(dateClass);
|
|
GCMARKOBJECT(regexpClass);
|
|
GCMARKOBJECT(arrayClass);
|
|
GCMARKOBJECT(errorClass);
|
|
GCMARKOBJECT(evalErrorClass);
|
|
GCMARKOBJECT(rangeErrorClass);
|
|
GCMARKOBJECT(referenceErrorClass);
|
|
GCMARKOBJECT(syntaxErrorClass);
|
|
GCMARKOBJECT(typeErrorClass);
|
|
GCMARKOBJECT(uriErrorClass);
|
|
GCMARKOBJECT(argumentsClass);
|
|
|
|
for (BConListIterator i = bConList.begin(), end = bConList.end(); (i != end); i++)
|
|
(*i)->mark();
|
|
if (bCon)
|
|
bCon->mark();
|
|
if (engine)
|
|
engine->mark();
|
|
GCMARKOBJECT(env);
|
|
|
|
GCMARKOBJECT(glob);
|
|
|
|
}
|
|
|
|
/*
|
|
* Throw an exception of the specified kind, indicating the position 'pos' and
|
|
* attaching the given message. If 'arg' is specified, replace {0} in the message
|
|
* with the argument value. [This is intended to be widened into a more complete
|
|
* argument handling scheme].
|
|
*/
|
|
void JS2Metadata::reportError(Exception::Kind kind, const char *message, size_t pos, const char *arg)
|
|
{
|
|
const char16 *lineBegin;
|
|
const char16 *lineEnd;
|
|
String x = widenCString(message);
|
|
if (arg) {
|
|
// XXX handle multiple occurences and extend to {1} etc.
|
|
uint32 a = x.find(widenCString("{0}"));
|
|
x.replace(a, 3, widenCString(arg));
|
|
}
|
|
Reader reader(engine->bCon->mSource, engine->bCon->mSourceLocation, 1);
|
|
reader.fillLineStartsTable();
|
|
uint32 lineNum = reader.posToLineNum(pos);
|
|
size_t linePos = reader.getLine(lineNum, lineBegin, lineEnd);
|
|
ASSERT(lineBegin && lineEnd && linePos <= pos);
|
|
throw Exception(kind, x, reader.sourceLocation,
|
|
lineNum, pos - linePos, pos, lineBegin, lineEnd);
|
|
}
|
|
|
|
// Accepts a String as the error argument and converts to char *
|
|
void JS2Metadata::reportError(Exception::Kind kind, const char *message, size_t pos, const String &name)
|
|
{
|
|
std::string str(name.length(), char());
|
|
std::transform(name.begin(), name.end(), str.begin(), narrow);
|
|
reportError(kind, message, pos, str.c_str());
|
|
}
|
|
|
|
// Accepts a String * as the error argument and converts to char *
|
|
void JS2Metadata::reportError(Exception::Kind kind, const char *message, size_t pos, const String *name)
|
|
{
|
|
std::string str(name->length(), char());
|
|
std::transform(name->begin(), name->end(), str.begin(), narrow);
|
|
reportError(kind, message, pos, str.c_str());
|
|
}
|
|
|
|
// Called after initBuiltinClass and after the prototype object is constructed
|
|
void JS2Metadata::initBuiltinClassPrototype(JS2Class *builtinClass, FunctionData *protoFunctions)
|
|
{
|
|
env->addFrame(builtinClass);
|
|
{
|
|
Variable *v = new Variable(builtinClass, OBJECT_TO_JS2VAL(builtinClass->prototype), true);
|
|
defineLocalMember(env, engine->prototype_StringAtom, NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, false);
|
|
}
|
|
env->removeTopFrame();
|
|
|
|
// Add "constructor" as a dynamic property of the prototype
|
|
/*
|
|
FunctionInstance *fInst = createFunctionInstance(env, true, true, builtinClass->construct, 0, NULL);
|
|
ASSERT(JS2VAL_IS_OBJECT(builtinClass->prototype));
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(builtinClass->prototype), &world.identifiers["constructor"], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, false);
|
|
*/
|
|
// XXX Set the class object itself as the value of 'constructor'
|
|
ASSERT(JS2VAL_IS_OBJECT(builtinClass->prototype));
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(builtinClass->prototype), world.identifiers["constructor"], OBJECT_TO_JS2VAL(builtinClass), ReadWriteAccess, false, false);
|
|
|
|
FunctionData *pf = protoFunctions;
|
|
if (pf) {
|
|
while (pf->name) {
|
|
/*
|
|
SimpleInstance *callInst = new SimpleInstance(this, functionClass->prototype, functionClass);
|
|
callInst->fWrap = new FunctionWrapper(true, new ParameterFrame(JS2VAL_INACCESSIBLE, true), pf->code, env);
|
|
callInst->fWrap->length = pf->length;
|
|
Multiname *mn = new Multiname(&world.identifiers[pf->name], publicNamespace);
|
|
InstanceMember *m = new InstanceMethod(mn, callInst, true, false);
|
|
defineInstanceMember(builtinClass, &cxt, mn->name, *mn->nsList, Attribute::NoOverride, false, m, 0);
|
|
*/
|
|
FunctionInstance *fInst = createFunctionInstance(env, true, true, pf->code, pf->length, NULL);
|
|
createDynamicProperty(JS2VAL_TO_OBJECT(builtinClass->prototype), world.identifiers[pf->name], OBJECT_TO_JS2VAL(fInst), ReadWriteAccess, false, false);
|
|
pf++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void JS2Metadata::initBuiltinClass(JS2Class *builtinClass, FunctionData *staticFunctions, NativeCode *construct, NativeCode *call)
|
|
{
|
|
FunctionData *pf;
|
|
|
|
builtinClass->construct = construct;
|
|
builtinClass->call = call;
|
|
|
|
// Adding "prototype" & "length", etc as static members of the class - not dynamic properties; XXX
|
|
env->addFrame(builtinClass);
|
|
{
|
|
Variable *v = new Variable(integerClass, INT_TO_JS2VAL(1), true);
|
|
defineLocalMember(env, engine->length_StringAtom, NULL, Attribute::NoOverride, false, ReadAccess, v, 0, false);
|
|
|
|
pf = staticFunctions;
|
|
if (pf) {
|
|
while (pf->name) {
|
|
FunctionInstance *callInst = createFunctionInstance(env, true, true, pf->code, pf->length, NULL);
|
|
v = new Variable(functionClass, OBJECT_TO_JS2VAL(callInst), true);
|
|
defineLocalMember(env, world.identifiers[pf->name], NULL, Attribute::NoOverride, false, ReadWriteAccess, v, 0, false);
|
|
pf++;
|
|
}
|
|
}
|
|
}
|
|
env->removeTopFrame();
|
|
|
|
}
|
|
|
|
// Add a pointer to the (address of a) gc-allocated object to the root list
|
|
// (Note - we hand out an iterator, so it's essential to
|
|
// use something like std::list that doesn't mess with locations)
|
|
RootIterator JS2Metadata::addRoot(RootKeeper *t)
|
|
{
|
|
return rootList.insert(rootList.end(), t);
|
|
}
|
|
|
|
// Remove a root pointer
|
|
void JS2Metadata::removeRoot(RootIterator ri)
|
|
{
|
|
rootList.erase(ri);
|
|
}
|
|
|
|
// Remove everything, regardless of rootlist
|
|
void JS2Metadata::clear()
|
|
{
|
|
pond.resetMarks();
|
|
pond.moveUnmarkedToFreeList(this);
|
|
}
|
|
|
|
// Mark all reachable objects and put the rest back on the freelist
|
|
uint32 JS2Metadata::gc()
|
|
{
|
|
pond.resetMarks();
|
|
markChildren();
|
|
// Anything on the root list may also be a pointer to a JS2Object.
|
|
for (RootIterator i = rootList.begin(), end = rootList.end(); (i != end); i++) {
|
|
RootKeeper *r = *i;
|
|
PondScum *scum = NULL;
|
|
if (r->is_js2val) {
|
|
if (r->js2val_count) {
|
|
js2val *valp = *((js2val **)(r->p));
|
|
for (uint32 c = 0; c < r->js2val_count; c++)
|
|
JS2Object::markJS2Value(*valp++);
|
|
}
|
|
else {
|
|
js2val *valp = (js2val *)(r->p);
|
|
JS2Object::markJS2Value(*valp);
|
|
}
|
|
}
|
|
else {
|
|
JS2Object **objp = (JS2Object **)(r->p);
|
|
if (*objp) {
|
|
scum = ((PondScum *)(*objp) - 1);
|
|
ASSERT(scum->owner && (scum->getSize() >= sizeof(PondScum)) && (scum->owner->sanity == POND_SANITY));
|
|
if (scum->isJS2Object()) {
|
|
JS2Object *obj = (JS2Object *)(scum + 1);
|
|
GCMARKOBJECT(obj)
|
|
}
|
|
else
|
|
JS2Object::mark(scum + 1);
|
|
}
|
|
}
|
|
}
|
|
return pond.moveUnmarkedToFreeList(this);
|
|
}
|
|
|
|
// Allocate a chunk of size s
|
|
void *JS2Metadata::alloc(size_t s, PondScum::ScumFlag flag)
|
|
{
|
|
s += sizeof(PondScum);
|
|
// make sure that the thing is a multiple of 16 bytes
|
|
if (s & 0xF) s += 16 - (s & 0xF);
|
|
ASSERT(s <= 0x7FFFFFFF);
|
|
void *p = pond.allocFromPond(this, s, flag);
|
|
ASSERT(((ptrdiff_t)p & 0xF) == 0);
|
|
return p;
|
|
}
|
|
|
|
// Release a chunk back to it's pond
|
|
void JS2Metadata::unalloc(void *t)
|
|
{
|
|
PondScum *p = (PondScum *)t - 1;
|
|
ASSERT(p->owner && (p->getSize() >= sizeof(PondScum)) && (p->owner->sanity == POND_SANITY));
|
|
p->owner->returnToPond(p);
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* JS2Class
|
|
*
|
|
************************************************************************************/
|
|
|
|
JS2Class::JS2Class(JS2Class *super, js2val proto, Namespace *privateNamespace, bool dynamic, bool final, const StringAtom &name)
|
|
: NonWithFrame(ClassKind),
|
|
super(super),
|
|
instanceInitOrder(NULL),
|
|
complete(false),
|
|
name(name),
|
|
prototype(proto),
|
|
typeofString(name),
|
|
privateNamespace(privateNamespace),
|
|
dynamic(dynamic),
|
|
final(final),
|
|
defaultValue(JS2VAL_NULL),
|
|
call(NULL),
|
|
construct(JS2Engine::defaultConstructor),
|
|
init(NULL),
|
|
slotCount(super ? super->slotCount : 0)
|
|
{
|
|
}
|
|
|
|
JS2Class::~JS2Class()
|
|
{
|
|
for (InstanceBindingIterator rib(instanceBindings); rib; ++rib) {
|
|
InstanceBindingEntry &ibe = *rib;
|
|
for (InstanceBindingEntry::NS_Iterator i = ibe.begin(), end = ibe.end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding ns = *i;
|
|
delete ns.second;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void JS2Class::markChildren()
|
|
{
|
|
NonWithFrame::markChildren();
|
|
GCMARKOBJECT(super)
|
|
GCMARKVALUE(prototype);
|
|
GCMARKOBJECT(privateNamespace)
|
|
GCMARKOBJECT(init)
|
|
GCMARKVALUE(defaultValue);
|
|
for (InstanceBindingIterator rib(instanceBindings); rib; ++rib) {
|
|
InstanceBindingEntry &ibe = *rib;
|
|
for (InstanceBindingEntry::NS_Iterator i = ibe.begin(), end = ibe.end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding ns = *i;
|
|
ns.second->content->mark();
|
|
}
|
|
}
|
|
}
|
|
|
|
// return true if 'heir' is this class or is any antecedent
|
|
bool JS2Class::isAncestor(JS2Class *heir)
|
|
{
|
|
JS2Class *kinsman = this;
|
|
do {
|
|
if (heir == kinsman)
|
|
return true;
|
|
kinsman = kinsman->super;
|
|
} while (kinsman);
|
|
return false;
|
|
}
|
|
|
|
void JS2Class::emitDefaultValue(BytecodeContainer *bCon, size_t pos)
|
|
{
|
|
if (JS2VAL_IS_NULL(defaultValue))
|
|
bCon->emitOp(eNull, pos);
|
|
else
|
|
if (JS2VAL_IS_VOID(defaultValue))
|
|
bCon->emitOp(eUndefined, pos);
|
|
else
|
|
if (JS2VAL_IS_BOOLEAN(defaultValue) && !JS2VAL_TO_BOOLEAN(defaultValue))
|
|
bCon->emitOp(eFalse, pos);
|
|
else
|
|
if ((JS2VAL_IS_LONG(defaultValue) || JS2VAL_IS_ULONG(defaultValue))
|
|
&& (*JS2VAL_TO_LONG(defaultValue) == 0))
|
|
bCon->emitOp(eLongZero, pos);
|
|
else
|
|
if (JS2VAL_IS_INT(defaultValue)) {
|
|
bCon->emitOp(eInteger, pos);
|
|
bCon->addInt32(JS2VAL_TO_INT(defaultValue));
|
|
}
|
|
else
|
|
NOT_REACHED("unrecognized default value");
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* LocalBindingEntry
|
|
*
|
|
************************************************************************************/
|
|
|
|
// Clear all the clone contents for this entry
|
|
void LocalBindingEntry::clear()
|
|
{
|
|
for (NS_Iterator i = bindingList.begin(), end = bindingList.end(); (i != end); i++) {
|
|
NamespaceBinding &ns = *i;
|
|
ns.second->content->cloneContent = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* SimpleInstance
|
|
*
|
|
************************************************************************************/
|
|
|
|
|
|
void SimpleInstance::initializeSlots(JS2Class *type)
|
|
{
|
|
if (type->super)
|
|
initializeSlots(type->super);
|
|
for (InstanceBindingIterator rib(type->instanceBindings); rib; ++rib) {
|
|
InstanceBindingEntry &ibe = *rib;
|
|
for (InstanceBindingEntry::NS_Iterator i = ibe.begin(), end = ibe.end(); (i != end); i++) {
|
|
InstanceBindingEntry::NamespaceBinding ns = *i;
|
|
InstanceMember *im = ns.second->content;
|
|
if (im->memberKind == Member::InstanceVariableMember) {
|
|
InstanceVariable *iv = checked_cast<InstanceVariable *>(im);
|
|
fixedSlots[iv->slotIndex].value = iv->defaultValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct a Simple instance of a class. Set the
|
|
// initial value of all slots to uninitialized.
|
|
SimpleInstance::SimpleInstance(JS2Metadata *meta, js2val parent, JS2Class *type)
|
|
: JS2Object(SimpleInstanceKind),
|
|
super(parent),
|
|
sealed(false),
|
|
type(type),
|
|
fixedSlots(new Slot[type->slotCount])
|
|
{
|
|
for (uint32 i = 0; i < type->slotCount; i++) {
|
|
fixedSlots[i].value = JS2VAL_UNINITIALIZED;
|
|
}
|
|
initializeSlots(type);
|
|
}
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void SimpleInstance::markChildren()
|
|
{
|
|
GCMARKOBJECT(type)
|
|
GCMARKVALUE(super);
|
|
if (fixedSlots) {
|
|
ASSERT(type);
|
|
for (uint32 i = 0; (i < type->slotCount); i++) {
|
|
GCMARKVALUE(fixedSlots[i].value);
|
|
}
|
|
}
|
|
for (LocalBindingIterator bi(localBindings); bi; ++bi) {
|
|
LocalBindingEntry &lbe = *bi;
|
|
for (LocalBindingEntry::NS_Iterator i = lbe.begin(), end = lbe.end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding ns = *i;
|
|
ns.second->content->mark();
|
|
}
|
|
}
|
|
}
|
|
|
|
SimpleInstance::~SimpleInstance()
|
|
{
|
|
for (LocalBindingIterator bi(localBindings); bi; ++bi) {
|
|
LocalBindingEntry &lbe = *bi;
|
|
for (LocalBindingEntry::NS_Iterator i = lbe.begin(), end = lbe.end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding ns = *i;
|
|
delete ns.second;
|
|
}
|
|
}
|
|
delete [] fixedSlots;
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* ArrayInstance
|
|
*
|
|
************************************************************************************/
|
|
|
|
ArrayInstance::ArrayInstance(JS2Metadata *meta, js2val parent, JS2Class *type)
|
|
: SimpleInstance(meta, parent, type)
|
|
{
|
|
length = 0;
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* FunctionInstance
|
|
*
|
|
************************************************************************************/
|
|
|
|
FunctionInstance::FunctionInstance(JS2Metadata *meta, js2val parent, JS2Class *type)
|
|
: SimpleInstance(meta, parent, type), fWrap(NULL), isMethodClosure(false), thisObject(JS2VAL_VOID), sourceText(NULL)
|
|
{
|
|
// Add prototype property
|
|
JS2Object *result = this;
|
|
DEFINE_ROOTKEEPER(meta, rk1, result);
|
|
|
|
JS2Object *protoObj = new (meta) SimpleInstance(meta, meta->objectClass->prototype, meta->objectClass);
|
|
DEFINE_ROOTKEEPER(meta, rk2, protoObj);
|
|
|
|
meta->createDynamicProperty(this, meta->engine->prototype_StringAtom, OBJECT_TO_JS2VAL(protoObj), ReadWriteAccess, true, false);
|
|
meta->createDynamicProperty(protoObj, meta->world.identifiers["constructor"], OBJECT_TO_JS2VAL(this), ReadWriteAccess, true, false);
|
|
}
|
|
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void FunctionInstance::markChildren()
|
|
{
|
|
SimpleInstance::markChildren();
|
|
if (fWrap) {
|
|
GCMARKOBJECT(fWrap->env);
|
|
GCMARKOBJECT(fWrap->compileFrame);
|
|
if (fWrap->bCon)
|
|
fWrap->bCon->mark();
|
|
}
|
|
GCMARKVALUE(thisObject);
|
|
if (sourceText) JS2Object::mark(sourceText);
|
|
}
|
|
|
|
FunctionInstance::~FunctionInstance()
|
|
{
|
|
if (fWrap && !isMethodClosure)
|
|
delete fWrap;
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* ArgumentsInstance
|
|
*
|
|
************************************************************************************/
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void ArgumentsInstance::markChildren()
|
|
{
|
|
SimpleInstance::markChildren();
|
|
if (mSlots) {
|
|
for (uint32 i = 0; i < count; i++)
|
|
GCMARKVALUE(mSlots[i]);
|
|
}
|
|
if (mSplitValue) {
|
|
for (uint32 i = 0; i < count; i++)
|
|
GCMARKVALUE(mSplitValue[i]);
|
|
}
|
|
}
|
|
|
|
ArgumentsInstance::~ArgumentsInstance()
|
|
{
|
|
if (mSlots)
|
|
delete mSlots;
|
|
if (mSplit)
|
|
delete mSplit;
|
|
if (mSplitValue)
|
|
delete mSplitValue;
|
|
}
|
|
|
|
RegExpInstance::~RegExpInstance() { if (mRegExp) { js_DestroyRegExp(mRegExp); free(mRegExp);} }
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* InstanceMethod
|
|
*
|
|
************************************************************************************/
|
|
|
|
InstanceMethod::~InstanceMethod()
|
|
{
|
|
delete fInst;
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Frame
|
|
*
|
|
************************************************************************************/
|
|
|
|
// Allocate a new value slot in this frame and stick it
|
|
// on the list (which may need to be created) for gc tracking.
|
|
uint16 NonWithFrame::allocateSlot()
|
|
{
|
|
if (frameSlots == NULL)
|
|
frameSlots = new std::vector<js2val>;
|
|
uint16 result = (uint16)(frameSlots->size());
|
|
frameSlots->push_back(JS2VAL_VOID);
|
|
return result;
|
|
}
|
|
|
|
NonWithFrame::~NonWithFrame()
|
|
{
|
|
for (LocalBindingIterator bi(localBindings); bi; ++bi) {
|
|
LocalBindingEntry &lbe = *bi;
|
|
for (LocalBindingEntry::NS_Iterator i = lbe.begin(), end = lbe.end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding ns = *i;
|
|
delete ns.second;
|
|
}
|
|
}
|
|
if (frameSlots)
|
|
delete frameSlots;
|
|
}
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void NonWithFrame::markChildren()
|
|
{
|
|
GCMARKOBJECT(pluralFrame)
|
|
for (LocalBindingIterator bi(localBindings); bi; ++bi) {
|
|
LocalBindingEntry &lbe = *bi;
|
|
for (LocalBindingEntry::NS_Iterator i = lbe.begin(), end = lbe.end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding ns = *i;
|
|
ns.second->content->mark();
|
|
}
|
|
}
|
|
if (frameSlots) {
|
|
for (std::vector<js2val>::iterator i = frameSlots->begin(), end = frameSlots->end(); (i != end); i++)
|
|
GCMARKVALUE(*i);
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Package
|
|
*
|
|
************************************************************************************/
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void Package::markChildren()
|
|
{
|
|
NonWithFrame::markChildren();
|
|
GCMARKVALUE(super);
|
|
GCMARKOBJECT(internalNamespace)
|
|
for (LocalBindingIterator bi(localBindings); bi; ++bi) {
|
|
LocalBindingEntry &lbe = *bi;
|
|
for (LocalBindingEntry::NS_Iterator i = lbe.begin(), end = lbe.end(); (i != end); i++) {
|
|
LocalBindingEntry::NamespaceBinding ns = *i;
|
|
ns.second->content->mark();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* ParameterFrame
|
|
*
|
|
************************************************************************************/
|
|
|
|
void ParameterFrame::instantiate(Environment *env)
|
|
{
|
|
// ASSERT(pluralFrame->kind == ParameterFrameKind);
|
|
// ParameterFrame *plural = checked_cast<ParameterFrame *>(pluralFrame);
|
|
// env->instantiateFrame(pluralFrame, this, !plural->buildArguments);
|
|
}
|
|
|
|
// Assume that instantiate has been called, the plural frame will contain
|
|
// the cloned Variables assigned into this (singular) frame. Use the
|
|
// incoming values to initialize the positionals.
|
|
// Pad out with undefined values if argc is insufficient
|
|
void ParameterFrame::assignArguments(JS2Metadata *meta, JS2Object *fnObj, js2val *argv, uint32 argc)
|
|
{
|
|
uint32 i;
|
|
|
|
ArgumentsInstance *argsObj = NULL;
|
|
DEFINE_ROOTKEEPER(meta, rk2, argsObj);
|
|
|
|
// argCount is the number of slots acquired by the frame, it will
|
|
// be the larger of the number of parameters defined or the number
|
|
// of arguments passed.
|
|
argCount = (frameSlots) ? frameSlots->size() : 0;
|
|
if (argc > argCount)
|
|
argCount = argc;
|
|
|
|
if (buildArguments) {
|
|
// If we're building an arguments object, the slots for the parameter frame are located
|
|
// there so that the arguments object itself can survive beyond the life of the function.
|
|
argsObj = new (meta) ArgumentsInstance(meta, meta->objectClass->prototype, meta->argumentsClass);
|
|
if (argCount) {
|
|
argsObj->mSlots = new js2val[argCount];
|
|
argsObj->count = argCount;
|
|
argsObj->mSplit = new bool[argCount];
|
|
for (i = 0; (i < argCount); i++)
|
|
argsObj->mSplit[i] = false;
|
|
}
|
|
argSlots = argsObj->mSlots;
|
|
// Add the 'arguments' property
|
|
|
|
const StringAtom &name = meta->world.identifiers["arguments"];
|
|
LocalBindingEntry *lbeP = localBindings[name];
|
|
if (lbeP == NULL) {
|
|
LocalBinding *sb = new LocalBinding(ReadWriteAccess, new Variable(meta->objectClass, OBJECT_TO_JS2VAL(argsObj), false), false);
|
|
LocalBindingEntry *lbe = &localBindings.insert(name);
|
|
lbe->bindingList.push_back(LocalBindingEntry::NamespaceBinding(meta->publicNamespace, sb));
|
|
|
|
}
|
|
else {
|
|
LocalBindingEntry::NamespaceBinding &ns = *(lbeP->begin());
|
|
ASSERT(ns.first == meta->publicNamespace);
|
|
ASSERT(ns.second->content->memberKind == Member::VariableMember);
|
|
(checked_cast<Variable *>(ns.second->content))->value = OBJECT_TO_JS2VAL(argsObj);
|
|
}
|
|
}
|
|
else {
|
|
if (argSlots && argSlotsOwner)
|
|
delete [] argSlots;
|
|
if (argCount) {
|
|
if (argCount <= argc) {
|
|
argSlots = argv;
|
|
argSlotsOwner = false;
|
|
return;
|
|
}
|
|
else {
|
|
argSlots = new js2val[argCount];
|
|
argSlotsOwner = true;
|
|
}
|
|
}
|
|
else
|
|
argSlots = NULL;
|
|
}
|
|
|
|
for (i = 0; (i < argc); i++) {
|
|
if (i < argCount) {
|
|
argSlots[i] = argv[i];
|
|
}
|
|
}
|
|
for ( ; (i < argCount); i++) {
|
|
argSlots[i] = JS2VAL_UNDEFINED;
|
|
}
|
|
if (buildArguments) {
|
|
setLength(meta, argsObj, argc);
|
|
meta->argumentsClass->WritePublic(meta, OBJECT_TO_JS2VAL(argsObj), meta->world.identifiers["callee"], true, OBJECT_TO_JS2VAL(fnObj));
|
|
}
|
|
}
|
|
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void ParameterFrame::markChildren()
|
|
{
|
|
NonWithFrame::markChildren();
|
|
GCMARKVALUE(thisObject);
|
|
if (argSlots) {
|
|
for (uint32 i = 0; i < argCount; i++)
|
|
GCMARKVALUE(argSlots[i]);
|
|
}
|
|
}
|
|
|
|
void ParameterFrame::releaseArgs()
|
|
{
|
|
if (!buildArguments && argSlots && argSlotsOwner)
|
|
delete [] argSlots;
|
|
argSlots = NULL;
|
|
}
|
|
|
|
|
|
ParameterFrame::~ParameterFrame()
|
|
{
|
|
releaseArgs();
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Variable
|
|
*
|
|
************************************************************************************/
|
|
|
|
void Variable::mark()
|
|
{
|
|
GCMARKVALUE(value);
|
|
GCMARKOBJECT(type)
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* BlockFrame
|
|
*
|
|
************************************************************************************/
|
|
|
|
void BlockFrame::instantiate(Environment *env)
|
|
{
|
|
if (pluralFrame)
|
|
env->instantiateFrame(pluralFrame, this, true);
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* InstanceVariable
|
|
*
|
|
************************************************************************************/
|
|
|
|
// An instance variable type could be future'd when a gc runs (i.e. validate
|
|
// has executed, but the pre-eval stage has yet to determine the actual type)
|
|
|
|
void InstanceVariable::mark()
|
|
{
|
|
InstanceMember::mark();
|
|
if (type != FUTURE_TYPE)
|
|
GCMARKOBJECT(type);
|
|
}
|
|
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* InstanceMethod
|
|
*
|
|
************************************************************************************/
|
|
|
|
// gc-mark all contained JS2Objects and visit contained structures to do likewise
|
|
void InstanceMethod::mark()
|
|
{
|
|
InstanceMember::mark();
|
|
GCMARKOBJECT(fInst);
|
|
}
|
|
|
|
|
|
/************************************************************************************
|
|
*
|
|
* JS2Object
|
|
*
|
|
************************************************************************************/
|
|
|
|
void JS2Object::markJS2Value(js2val v)
|
|
{
|
|
if (JS2VAL_IS_OBJECT(v)) {
|
|
JS2Object *obj = JS2VAL_TO_OBJECT(v);
|
|
GCMARKOBJECT(obj);
|
|
}
|
|
else
|
|
if (JS2VAL_IS_STRING(v))
|
|
JS2Object::mark(JS2VAL_TO_STRING(v));
|
|
else
|
|
if (JS2VAL_IS_DOUBLE(v))
|
|
JS2Object::mark(JS2VAL_TO_DOUBLE(v));
|
|
else
|
|
if (JS2VAL_IS_LONG(v))
|
|
JS2Object::mark(JS2VAL_TO_LONG(v));
|
|
else
|
|
if (JS2VAL_IS_ULONG(v))
|
|
JS2Object::mark(JS2VAL_TO_ULONG(v));
|
|
else
|
|
if (JS2VAL_IS_FLOAT(v))
|
|
JS2Object::mark(JS2VAL_TO_FLOAT(v));
|
|
}
|
|
|
|
/************************************************************************************
|
|
*
|
|
* Pond
|
|
*
|
|
************************************************************************************/
|
|
|
|
Pond::Pond(size_t sz, Pond *next) : sanity(POND_SANITY), pondSize(sz + POND_SIZE), pondBase(new uint8[pondSize]), pondBottom(pondBase), pondTop(pondBase), freeHeader(NULL), nextPond(next)
|
|
{
|
|
/*
|
|
* Make sure the allocation base is at (n mod 16) == 8.
|
|
* That way, each allocated chunk will have it's returned pointer
|
|
* at (n mod 16) == 0 after allowing for the PondScum header at the
|
|
* beginning.
|
|
*/
|
|
int32 offset = ((ptrdiff_t)pondBottom % 16);
|
|
if (offset != 8) {
|
|
if (offset > 8)
|
|
pondBottom += 8 + (16 - offset);
|
|
else
|
|
pondBottom += 8 - offset;
|
|
}
|
|
pondTop = pondBottom;
|
|
pondSize -= (pondTop - pondBase);
|
|
}
|
|
|
|
// Allocate from this or the next Pond (make a new one if necessary)
|
|
void *Pond::allocFromPond(JS2Metadata *meta, size_t sz, PondScum::ScumFlag flag)
|
|
{
|
|
// See if there's room left...
|
|
if (sz > pondSize) {
|
|
// If not, try the free list...
|
|
PondScum *p = freeHeader;
|
|
PondScum *pre = NULL;
|
|
while (p) {
|
|
ASSERT(p->getSize() > 0);
|
|
if (p->getSize() >= sz) {
|
|
if (pre)
|
|
pre->owner = p->owner;
|
|
else
|
|
freeHeader = (PondScum *)(p->owner);
|
|
p->owner = this;
|
|
p->resetMark(); // might have lingering mark from previous gc
|
|
p->clearFlags();
|
|
p->setFlag(flag);
|
|
#ifdef DEBUG
|
|
memset((p + 1), 0xB7, p->getSize() - sizeof(PondScum));
|
|
#endif
|
|
return (p + 1);
|
|
}
|
|
pre = p;
|
|
p = (PondScum *)(p->owner);
|
|
}
|
|
// ok, then try the next Pond
|
|
if (nextPond == NULL) {
|
|
// there isn't one; run the gc
|
|
uint32 released = meta->gc();
|
|
if (released > sz)
|
|
return meta->alloc(sz - sizeof(PondScum), flag);
|
|
nextPond = new Pond(sz, nextPond);
|
|
}
|
|
return nextPond->allocFromPond(meta, sz, flag);
|
|
}
|
|
// there was room, so acquire it
|
|
PondScum *p = (PondScum *)pondTop;
|
|
#ifdef DEBUG
|
|
memset(p, 0xB7, sz);
|
|
#endif
|
|
p->owner = this;
|
|
p->setSize(sz);
|
|
p->setFlag(flag);
|
|
pondTop += sz;
|
|
pondSize -= sz;
|
|
return (p + 1);
|
|
}
|
|
|
|
// Stick the chunk at the start of the free list
|
|
uint32 Pond::returnToPond(PondScum *p)
|
|
{
|
|
p->owner = (Pond *)freeHeader;
|
|
uint8 *t = (uint8 *)(p + 1);
|
|
#ifdef DEBUG
|
|
memset(t, 0xB3, p->getSize() - sizeof(PondScum));
|
|
#endif
|
|
freeHeader = p;
|
|
return p->getSize() - sizeof(PondScum);
|
|
}
|
|
|
|
// Clear the mark bit from all PondScums
|
|
void Pond::resetMarks()
|
|
{
|
|
uint8 *t = pondBottom;
|
|
while (t != pondTop) {
|
|
PondScum *p = (PondScum *)t;
|
|
p->resetMark();
|
|
t += p->getSize();
|
|
}
|
|
if (nextPond)
|
|
nextPond->resetMarks();
|
|
}
|
|
|
|
// Anything left unmarked is now moved to the free list
|
|
uint32 Pond::moveUnmarkedToFreeList(JS2Metadata *meta)
|
|
{
|
|
uint32 released = 0;
|
|
uint8 *t = pondBottom;
|
|
while (t != pondTop) {
|
|
PondScum *p = (PondScum *)t;
|
|
if (!p->isMarked() && (p->owner == this)) { // (owner != this) ==> already on free list
|
|
if (p->isJS2Object()) {
|
|
JS2Object *obj = (JS2Object *)(p + 1);
|
|
obj->finalize();
|
|
delete obj;
|
|
}
|
|
else
|
|
if (p->isString()) {
|
|
String t;
|
|
String *s = (String *)(p + 1);
|
|
*s = t;
|
|
}
|
|
released += returnToPond(p);
|
|
}
|
|
t += p->getSize();
|
|
}
|
|
if (nextPond)
|
|
released += nextPond->moveUnmarkedToFreeList(meta);
|
|
return released;
|
|
}
|
|
|
|
}; // namespace MetaData
|
|
}; // namespace Javascript
|