private static void ResolveLookups(JsActivationObject scope, JsSettings settings) { // resolve each lookup this scope contains foreach (var lookup in scope.ScopeLookups) { ResolveLookup(scope, lookup, settings); } // and recurse foreach (var childScope in scope.ChildScopes) { ResolveLookups(childScope, settings); } // mark any variables defined in this scope that don't have any references // so we can throw warnings later. We can't rely on the reference count because // we might remove references while optimizing code -- if we throw an error when // the count gets to zero, then we would be reporting errors that don't exist. // but we DO know right now what isn't referenced at all. foreach (var field in scope.NameTable.Values) { if (field.RefCount == 0) { field.HasNoReferences = true; } } }
protected JsActivationObject(JsActivationObject parent, JsSettings codeSettings) { m_isKnownAtCompileTime = true; m_useStrict = false; m_settings = codeSettings; Parent = parent; NameTable = new Dictionary <string, JsVariableField>(); ChildScopes = new List <JsActivationObject>(); // if our parent is a scope.... if (parent != null) { // add us to the parent's list of child scopes parent.ChildScopes.Add(this); // if the parent is strict, so are we UseStrict = parent.UseStrict; } // create the two lists of declared items for this scope ScopeLookups = new HashSet <JsLookup>(); VarDeclaredNames = new HashSet <IJsNameDeclaration>(); LexicallyDeclaredNames = new HashSet <IJsNameDeclaration>(); GhostedCatchParameters = new HashSet <JsParameterDeclaration>(); GhostedFunctions = new HashSet <JsFunctionObject>(); }
private bool m_useStrict; //= false; #endregion Fields #region Constructors protected JsActivationObject(JsActivationObject parent, JsSettings codeSettings) { m_isKnownAtCompileTime = true; m_useStrict = false; m_settings = codeSettings; Parent = parent; NameTable = new Dictionary<string, JsVariableField>(); ChildScopes = new List<JsActivationObject>(); // if our parent is a scope.... if (parent != null) { // add us to the parent's list of child scopes parent.ChildScopes.Add(this); // if the parent is strict, so are we UseStrict = parent.UseStrict; } // create the two lists of declared items for this scope ScopeLookups = new HashSet<JsLookup>(); VarDeclaredNames = new HashSet<IJsNameDeclaration>(); LexicallyDeclaredNames = new HashSet<IJsNameDeclaration>(); GhostedCatchParameters = new HashSet<JsParameterDeclaration>(); GhostedFunctions = new HashSet<JsFunctionObject>(); }
private static void ResolveGhostedCatchParameter(JsActivationObject scope, JsParameterDeclaration catchParameter) { // check to see if the name exists in the outer variable scope. var ghostField = scope[catchParameter.Name]; if (ghostField == null) { // set up a ghost field to keep track of the relationship ghostField = new JsVariableField(JsFieldType.GhostCatch, catchParameter.Name, 0, null) { OriginalContext = catchParameter.Context }; scope.AddField(ghostField); } else if (ghostField.FieldType == JsFieldType.GhostCatch) { // there is, but it's another ghost catch variable. That's fine; just use it. // don't even flag it as ambiguous because if someone is later referencing the error variable // used in a couple catch variables, we'll say something then because other browsers will have that // variable undefined or from an outer scope. } else { // there is, and it's NOT another ghosted catch variable. Possible naming // collision in IE -- if an error happens, it will clobber the existing field's value, // although that MAY be the intention; we don't know for sure. But it IS a cross- // browser behavior difference. ghostField.IsAmbiguous = true; if (ghostField.OuterField != null) { // and to make matters worse, it's actually bound to an OUTER field // in modern browsers, but will bind to this catch variable in older // versions of IE! Definitely a cross-browser difference! // throw a cross-browser issue error. catchParameter.Context.HandleError(JsError.AmbiguousCatchVar); } } // link them so they all keep the same name going forward // (since they are named the same in the sources) catchParameter.VariableField.OuterField = ghostField; // TODO: this really should be a LIST of ghosted fields, since multiple // elements can ghost to the same field. ghostField.GhostedField = catchParameter.VariableField; // if the actual field has references, we want to bubble those up // since we're now linking those fields if (catchParameter.VariableField.RefCount > 0) { // add the catch parameter's references to the ghost field ghostField.AddReferences(catchParameter.VariableField.References); } }
public JsBlockScope(JsActivationObject parent, JsContext context, JsSettings settings) : base(parent, settings) { if (context == null) { throw new ArgumentNullException("context"); } m_context = context.Clone(); }
private JsContext m_context; // = null; #endregion Fields #region Constructors public JsBlockScope(JsActivationObject parent, JsContext context, JsSettings settings) : base(parent, settings) { if (context == null) { throw new ArgumentNullException("context"); } m_context = context.Clone(); }
private JsResolutionVisitor(JsActivationObject rootScope, JsSettings settings) { // create the lexical and variable scope stacks and push the root scope onto them m_lexicalStack = new Stack <JsActivationObject>(); m_lexicalStack.Push(rootScope); m_variableStack = new Stack <JsActivationObject>(); m_variableStack.Push(rootScope); m_settings = settings; }
private static void CreateFields(JsActivationObject scope) { // declare this scope scope.DeclareScope(); // and recurse foreach (var childScope in scope.ChildScopes) { CreateFields(childScope); } }
private JsResolutionVisitor(JsActivationObject rootScope, JsSettings settings) { // create the lexical and variable scope stacks and push the root scope onto them m_lexicalStack = new Stack<JsActivationObject>(); m_lexicalStack.Push(rootScope); m_variableStack = new Stack<JsActivationObject>(); m_variableStack.Push(rootScope); m_settings = settings; }
internal JsFunctionScope(JsActivationObject parent, bool isExpression, JsSettings settings, JsFunctionObject funcObj) : base(parent, settings) { m_refScopes = new HashSet <JsActivationObject>(); if (isExpression) { // parent scopes automatically reference enclosed function expressions AddReference(Parent); } FunctionObject = funcObj; }
private static void CollapseBlockScope(JsActivationObject blockScope) { // copy over the stuff we want to carry over to the parent blockScope.ScopeLookups.CopyItemsTo(blockScope.Parent.ScopeLookups); blockScope.VarDeclaredNames.CopyItemsTo(blockScope.Parent.VarDeclaredNames); blockScope.ChildScopes.CopyItemsTo(blockScope.Parent.ChildScopes); blockScope.GhostedCatchParameters.CopyItemsTo(blockScope.Parent.GhostedCatchParameters); blockScope.GhostedFunctions.CopyItemsTo(blockScope.Parent.GhostedFunctions); // remove it from its parent's collection of child scopes blockScope.Parent.ChildScopes.Remove(blockScope); }
internal JsFunctionScope(JsActivationObject parent, bool isExpression, JsSettings settings, JsFunctionObject funcObj) : base(parent, settings) { m_refScopes = new HashSet<JsActivationObject>(); if (isExpression) { // parent scopes automatically reference enclosed function expressions AddReference(Parent); } FunctionObject = funcObj; }
internal void AddReference(JsActivationObject scope) { // we don't want to include block scopes or with scopes -- they are really // contained within their parents while (scope != null && scope is JsBlockScope) { scope = scope.Parent; } if (scope != null) { // add the scope to the hash m_refScopes.Add(scope); } }
private static void AddGhostedFields(JsActivationObject scope) { foreach (var catchParameter in scope.GhostedCatchParameters) { ResolveGhostedCatchParameter(scope, catchParameter); } foreach (var ghostFunc in scope.GhostedFunctions) { ResolveGhostedFunctions(scope, ghostFunc); } // recurse foreach (var childScope in scope.ChildScopes) { AddGhostedFields(childScope); } }
public static void Apply(JsAstNode node, JsActivationObject scope, JsSettings settings) { if (node != null && scope != null) { // create the visitor and run it. This will create all the child // scopes and populate all the scopes with the var-decl, lex-decl, // and lookup references within them. var visitor = new JsResolutionVisitor(scope, settings); node.Accept(visitor); // now that all the scopes are created and they all know what decls // they contains, create all the fields CreateFields(scope); // now that all the fields have been created in all the scopes, // let's go through and resolve all the references ResolveLookups(scope, settings); // now that everything is declared and resolved as per the language specs, // we need to go back and add ghosted fields for older versions of IE that // incorrectly implement catch-variables and named function expressions. AddGhostedFields(scope); } }
public JsWithScope(JsActivationObject parent, JsContext context, JsSettings settings) : base(parent, context, settings) { IsInWithScope = true; }
internal JsCatchScope(JsActivationObject parent, JsContext catchContext, JsSettings settings, JsParameterDeclaration catchParameter) : base(parent, catchContext, settings) { CatchParameter = catchParameter; }
private static void ResolveLookup(JsActivationObject scope, JsLookup lookup, JsSettings settings) { // resolve lookup via the lexical scope lookup.VariableField = scope.FindReference(lookup.Name); if (lookup.VariableField.FieldType == JsFieldType.UndefinedGlobal) { // couldn't find it. // if the lookup isn't generated and isn't the object of a typeof operator, // then we want to throw an error. if (!lookup.IsGenerated) { var parentUnaryOp = lookup.Parent as JsUnaryOperator; if (parentUnaryOp != null && parentUnaryOp.OperatorToken == JsToken.TypeOf) { // this undefined lookup is the target of a typeof operator. // I think it's safe to assume we're going to use it. Don't throw an error // and instead add it to the "known" expected globals of the global scope MakeExpectedGlobal(lookup.VariableField); } else { // report this undefined reference lookup.Context.ReportUndefined(lookup); // possibly undefined global (but definitely not local). // see if this is a function or a variable. var callNode = lookup.Parent as JsCallNode; var isFunction = callNode != null && callNode.Function == lookup; lookup.Context.HandleError((isFunction ? JsError.UndeclaredFunction : JsError.UndeclaredVariable), false); } } } else if (lookup.VariableField.FieldType == JsFieldType.Predefined) { if (string.CompareOrdinal(lookup.Name, "window") == 0) { // it's the global window object // see if it's the child of a member or call-brackets node var member = lookup.Parent as JsMember; if (member != null) { // we have window.XXXX. Add XXXX to the known globals if it // isn't already a known item. scope.AddGlobal(member.Name); } else { var callNode = lookup.Parent as JsCallNode; if (callNode != null && callNode.InBrackets && callNode.Arguments.Count == 1 && callNode.Arguments[0] is JsConstantWrapper && callNode.Arguments[0].FindPrimitiveType() == JsPrimitiveType.String) { // we have window["XXXX"]. See if XXXX is a valid identifier. // TODO: we could get rid of the ConstantWrapper restriction and use an Evaluate visitor // to evaluate the argument, since we know for sure that it's a string. var identifier = callNode.Arguments[0].ToString(); if (JsScanner.IsValidIdentifier(identifier)) { // Add XXXX to the known globals if it isn't already a known item. scope.AddGlobal(identifier); } } } } else if (settings.EvalTreatment != JsEvalTreatment.Ignore && string.CompareOrdinal(lookup.Name, "eval") == 0) { // it's an eval -- but are we calling it? // TODO: what if we are assigning it to a variable? Should we track that variable and see if we call it? // What about passing it as a parameter to a function? track that as well in case the function calls it? var parentCall = lookup.Parent as JsCallNode; if (parentCall != null && parentCall.Function == lookup) { scope.IsKnownAtCompileTime = false; } } } // add the reference lookup.VariableField.AddReference(lookup); // we are actually referencing this field, so it's no longer a placeholder field if it // happens to have been one. lookup.VariableField.IsPlaceholder = false; }
private static void ResolveGhostedFunctions(JsActivationObject scope, JsFunctionObject funcObject) { var functionField = funcObject.VariableField; // let's check on ghosted names in the outer variable scope var ghostField = scope[funcObject.Name]; if (ghostField == null) { // nothing; good to go. Add a ghosted field to keep track of it. ghostField = new JsVariableField(JsFieldType.GhostFunction, funcObject.Name, 0, funcObject) { OriginalContext = functionField.OriginalContext, CanCrunch = funcObject.VariableField.IfNotNull(v => v.CanCrunch) }; scope.AddField(ghostField); } else if (ghostField.FieldType == JsFieldType.GhostFunction) { // there is, but it's another ghosted function expression. // what if a lookup is resolved to this field later? We probably still need to // at least flag it as ambiguous. We will only need to throw an error, though, // if someone actually references the outer ghost variable. ghostField.IsAmbiguous = true; } else { // something already exists. Could be a naming collision for IE or at least a // a cross-browser behavior difference if it's not coded properly. // mark this field as a function, even if it wasn't before ghostField.IsFunction = true; if (ghostField.OuterField != null) { // if the pre-existing field we are ghosting is a reference to // an OUTER field, then we actually have a problem that creates a BIG // difference between older IE browsers and everything else. // modern browsers will have the link to the outer field, but older // IE browsers will link to this function expression! // fire a cross-browser error warning ghostField.IsAmbiguous = true; funcObject.IdContext.HandleError(JsError.AmbiguousNamedFunctionExpression); } else if (ghostField.IsReferenced) { // if the ghosted field isn't even referenced, then who cares? // but it is referenced. Let's see if it matters. // something like: var nm = function nm() {} // is TOTALLY cool common cross-browser syntax. var parentVarDecl = funcObject.Parent as JsVariableDeclaration; if (parentVarDecl == null || parentVarDecl.Name != funcObject.Name) { // see if it's a simple assignment. // something like: var nm; nm = function nm(){}, // would also be cool, although less-common than the vardecl version. JsLookup lookup; var parentAssignment = funcObject.Parent as JsBinaryOperator; if (parentAssignment == null || parentAssignment.OperatorToken != JsToken.Assign || parentAssignment.Operand2 != funcObject || (lookup = parentAssignment.Operand1 as JsLookup) == null || lookup.Name != funcObject.Name) { // something else. Flag it as ambiguous. ghostField.IsAmbiguous = true; } } } } // link them so they all keep the same name going forward // (since they are named the same in the sources) functionField.OuterField = ghostField; // TODO: this really should be a LIST of ghosted fields, since multiple // elements can ghost to the same field. ghostField.GhostedField = functionField; // if the actual field has references, we want to bubble those up // since we're now linking those fields if (functionField.RefCount > 0) { // add the function's references to the ghost field ghostField.AddReferences(functionField.References); } }