internal override void AnalyzeNode() { // throw a warning discouraging the use of this statement Context.HandleError(JSError.WithNotRecommended, false); // hold onto the with-scope in case we need to do something with it BlockScope withScope = (m_block == null ? null : m_block.BlockScope); if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements) && m_block != null && m_block.IsDebuggerStatement) { m_block = null; } // recurse base.AnalyzeNode(); // we'd have to know what the object (obj) evaluates to before we // can figure out what to add to the scope -- not possible without actually // running the code. This could throw a whole bunch of 'undefined' errors. if (m_block != null && m_block.Count == 0) { m_block = null; } // we got rid of the block -- tidy up the no-longer-needed scope if (m_block == null && withScope != null) { // because the scope is empty, we now know it (it does nothing) withScope.IsKnownAtCompileTime = true; } }
internal override void AnalyzeNode() { // verify the syntax try { // just try instantiating a Regex object with this string. // if it's invalid, it will throw an exception. // we don't need to pass the flags -- we're just interested in the pattern Regex re = new Regex(m_pattern, RegexOptions.ECMAScript); // basically we have this test here so the re variable is referenced // and FxCop won't throw an error. There really aren't any cases where // the constructor will return null (other than out-of-memory) if (re == null) { Context.HandleError(JSError.RegExpSyntax, true); } } catch (System.ArgumentException e) { System.Diagnostics.Debug.WriteLine(e.ToString()); Context.HandleError(JSError.RegExpSyntax, true); } // don't bother calling the base -- there are no children }
internal override void AnalyzeNode() { // first we want to make sure that we are indeed within a function scope. // it makes no sense to have a return outside of a function ActivationObject scope = ScopeStack.Peek(); while (scope != null && !(scope is FunctionScope)) { scope = scope.Parent; } if (scope == null) { Context.HandleError(JSError.BadReturn); } // now just do the default analyze base.AnalyzeNode(); }
internal override void AnalyzeScope() { if (Context != null) { // get the parent global/function scope where variables defined within our // scope are REALLY defined ActivationObject definingScope = this; do { definingScope = definingScope.Parent; } while (definingScope is BlockScope); // see if there is a variable already defined in that scope with the same name JSVariableField outerField = definingScope[m_name]; if (outerField != null) { // there is one defined already!!! // but if it isn't referenced, it's safe to just use it // as the outer field for our catch variable so our names // stay in sync when we rename stuff if (outerField.IsReferenced) { // but the outer field IS referenced somewhere! We have a possible ambiguous // catch variable problem that behaves differently in IE and non-IE browsers. Context.HandleError(JSError.AmbiguousCatchVar); } } else { // there isn't one defined -- add one and hook it to our argument // field as the outer reference so the name doesn't collide with any // other fields in that scope if we are renaming fields outerField = definingScope.CreateField(m_name, null, 0); outerField.IsPlaceholder = true; definingScope.AddField(outerField); } // point our inner catch variable to the outer variable this[m_name].OuterField = outerField; } base.AnalyzeScope(); }
internal virtual void AnalyzeScope() { // check for unused local fields or arguments foreach (JSVariableField variableField in m_nameTable.Values) { JSLocalField locField = variableField as JSLocalField; if (locField != null && !locField.IsReferenced && locField.OriginalContext != null) { if (locField.FieldValue is FunctionObject) { Context ctx = ((FunctionObject)locField.FieldValue).IdContext; if (ctx == null) { ctx = locField.OriginalContext; } ctx.HandleError(JSError.FunctionNotReferenced, false); } else if (!locField.IsGenerated) { JSArgumentField argumentField = locField as JSArgumentField; if (argumentField != null) { // we only want to throw this error if it's possible to remove it // from the argument list. And that will only happen if there are // no REFERENCED arguments after this one in the formal parameter list. // Assertion: because this is a JSArgumentField, this should be a function scope, // let's walk up to the first function scope we find, just in case. FunctionScope functionScope = this as FunctionScope; if (functionScope == null) { ActivationObject scope = this.Parent; while (scope != null) { functionScope = scope as FunctionScope; if (scope != null) { break; } } } if (functionScope == null || functionScope.IsArgumentTrimmable(argumentField)) { locField.OriginalContext.HandleError( JSError.ArgumentNotReferenced, false ); } } else if (locField.OuterField == null || !locField.OuterField.IsReferenced) { locField.OriginalContext.HandleError( JSError.VariableDefinedNotReferenced, false ); } } } } // rename fields if we need to RenameFields(); // recurse foreach (ActivationObject activationObject in m_childScopes) { try { Parser.ScopeStack.Push(activationObject); activationObject.AnalyzeScope(); } finally { Parser.ScopeStack.Pop(); } } }
internal override void AnalyzeNode() { // see if this is a member (we'll need it for a couple checks) Member member = m_func as Member; if (Parser.Settings.StripDebugStatements && Parser.Settings.IsModificationAllowed(TreeModifications.StripDebugStatements)) { // if this is a member, and it's a debugger object, and it's a constructor.... if (member != null && member.IsDebuggerStatement && m_isConstructor) { // we need to replace our debugger object with a generic Object m_func = new Lookup("Object", m_func.Context, Parser); // and make sure the node list is empty if (m_args != null && m_args.Count > 0) { m_args = new AstNodeList(m_args.Context, Parser); } } } // if this is a constructor and we want to collapse // some of them to literals... if (m_isConstructor && Parser.Settings.CollapseToLiteral) { // see if this is a lookup, and if so, if it's pointing to one // of the two constructors we want to collapse Lookup lookup = m_func as Lookup; if (lookup != null) { if (lookup.Name == "Object" && Parser.Settings.IsModificationAllowed(TreeModifications.NewObjectToObjectLiteral)) { // no arguments -- the Object constructor with no arguments is the exact same as an empty // object literal if (m_args == null || m_args.Count == 0) { // replace our node with an object literal ObjectLiteral objLiteral = new ObjectLiteral(Context, Parser, null, null); if (Parent.ReplaceChild(this, objLiteral)) { // and bail now. No need to recurse -- it's an empty literal return; } } else if (m_args.Count == 1) { // one argument // check to see if it's an object literal. ObjectLiteral objectLiteral = m_args[0] as ObjectLiteral; if (objectLiteral != null) { // the Object constructor with an argument that is a JavaScript object merely returns the // argument. Since the argument is an object literal, it is by definition a JavaScript object // and therefore we can replace the constructor call with the object literal Parent.ReplaceChild(this, objectLiteral); // don't forget to recurse the object now objectLiteral.AnalyzeNode(); // and then bail -- we don't want to process this call // operation any more; we've gotten rid of it return; } } } else if (lookup.Name == "Array" && Parser.Settings.IsModificationAllowed(TreeModifications.NewArrayToArrayLiteral)) { // Array is trickier. // If there are no arguments, then just use []. // if there are multiple arguments, then use [arg0,arg1...argN]. // but if there is one argument and it's numeric, we can't crunch it. // also can't crunch if it's a function call or a member or something, since we won't // KNOW whether or not it's numeric. // // so first see if it even is a single-argument constant wrapper. ConstantWrapper constWrapper = (m_args != null && m_args.Count == 1 ? m_args[0] as ConstantWrapper : null); // if the argument count is not one, then we crunch. // if the argument count IS one, we only crunch if we have a constant wrapper, // AND it's not numeric. if (m_args == null || m_args.Count != 1 || (constWrapper != null && !constWrapper.IsNumericLiteral)) { // create the new array literal object ArrayLiteral arrayLiteral = new ArrayLiteral(Context, Parser, m_args); // replace ourself within our parent if (Parent.ReplaceChild(this, arrayLiteral)) { // recurse arrayLiteral.AnalyzeNode(); // and bail -- we don't want to recurse this node any more return; } } } } } // if we are replacing resource references with strings generated from resource files // and this is a brackets call: lookup[args] ResourceStrings resourceStrings = Parser.ResourceStrings; if (m_inBrackets && resourceStrings != null && resourceStrings.Count > 0) { // see if the root object is a lookup that corresponds to the // global value (not a local field) for our resource object // (same name) Lookup rootLookup = m_func as Lookup; if (rootLookup != null && rootLookup.LocalField == null && string.CompareOrdinal(rootLookup.Name, resourceStrings.Name) == 0) { // we're going to replace this node with a string constant wrapper // but first we need to make sure that this is a valid lookup. // if the parameter contains anything that would vary at run-time, // then we need to throw an error. // the parser will always have either one or zero nodes in the arguments // arg list. We're not interested in zero args, so just make sure there is one if (m_args.Count == 1) { // must be a constant wrapper ConstantWrapper argConstant = m_args[0] as ConstantWrapper; if (argConstant != null) { string resourceName = argConstant.Value.ToString(); // get the localized string from the resources object ConstantWrapper resourceLiteral = new ConstantWrapper( resourceStrings[resourceName], PrimitiveType.String, Context, Parser); // replace this node with localized string, analyze it, and bail // so we don't anaylze the tree we just replaced Parent.ReplaceChild(this, resourceLiteral); resourceLiteral.AnalyzeNode(); return; } else { // error! must be a constant Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } else { // error! can only be a single constant argument to the string resource object. // the parser will only have zero or one arguments, so this must be zero // (since the parser won't pass multiple args to a [] operator) Context.HandleError( JSError.ResourceReferenceMustBeConstant, true); } } } // and finally, if this is a backets call and the argument is a constantwrapper that can // be an identifier, just change us to a member node: obj["prop"] to obj.prop. // but ONLY if the string value is "safe" to be an identifier. Even though the ECMA-262 // spec says certain Unicode categories are okay, in practice the various major browsers // all seem to have problems with certain characters in identifiers. Rather than risking // some browsers breaking when we change this syntax, don't do it for those "danger" categories. if (m_inBrackets && m_args != null) { // see if there is a single, constant argument string argText = m_args.SingleConstantArgument; if (argText != null) { // see if we want to replace the name string newName; if (Parser.Settings.HasRenamePairs && Parser.Settings.ManualRenamesProperties && Parser.Settings.IsModificationAllowed(TreeModifications.PropertyRenaming) && !string.IsNullOrEmpty(newName = Parser.Settings.GetNewName(argText))) { // yes -- we are going to replace the name, either as a string literal, or by converting // to a member-dot operation. // See if we can't turn it into a dot-operator. If we can't, then we just want to replace the operator with // a new constant wrapper. Otherwise we'll just replace the operator with a new constant wrapper. if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(newName) && !JSScanner.IsKeyword(newName)) { // the new name is safe to convert to a member-dot operator. // but we don't want to convert the node to the NEW name, because we still need to Analyze the // new member node -- and it might convert the new name to something else. So instead we're // just going to convert this existing string to a member node WITH THE OLD STRING, // and THEN analyze it (which will convert the old string to newName) Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); // this analyze call will convert the old-name member to the newName value replacementMember.AnalyzeNode(); return; } else { // nope; can't convert to a dot-operator. // we're just going to replace the first argument with a new string literal // and continue along our merry way. m_args.ReplaceChild(m_args[0], new ConstantWrapper(newName, PrimitiveType.String, m_args[0].Context, Parser)); } } else if (Parser.Settings.IsModificationAllowed(TreeModifications.BracketMemberToDotMember) && JSScanner.IsSafeIdentifier(argText) && !JSScanner.IsKeyword(argText)) { // not a replacement, but the string literal is a safe identifier. So we will // replace this call node with a Member-dot operation Member replacementMember = new Member(Context, Parser, m_func, argText); Parent.ReplaceChild(this, replacementMember); replacementMember.AnalyzeNode(); return; } } } // call the base class to recurse base.AnalyzeNode(); // call this AFTER recursing to give the fields a chance to resolve, because we only // want to make this replacement if we are working on the global Date object. if (!m_inBrackets && !m_isConstructor && (m_args == null || m_args.Count == 0) && member != null && string.CompareOrdinal(member.Name, "getTime") == 0 && Parser.Settings.IsModificationAllowed(TreeModifications.DateGetTimeToUnaryPlus)) { // this is not a constructor and it's not a brackets call, and there are no arguments. // if the function is a member operation to "getTime" and the object of the member is a // constructor call to the global "Date" object (not a local), then we want to replace the call // with a unary plus on the Date constructor. Converting to numeric type is the same as // calling getTime, so it's the equivalent with much fewer bytes. CallNode dateConstructor = member.Root as CallNode; if (dateConstructor != null && dateConstructor.IsConstructor) { // lookup for the predifined (not local) "Date" field Lookup lookup = dateConstructor.Function as Lookup; if (lookup != null && string.CompareOrdinal(lookup.Name, "Date") == 0 && lookup.LocalField == null) { // this is in the pattern: (new Date()).getTime() // we want to replace it with +new Date // use the same date constructor node as the operand NumericUnary unary = new NumericUnary(Context, Parser, dateConstructor, JSToken.Plus); // replace us (the call to the getTime method) with this unary operator Parent.ReplaceChild(this, unary); // don't need to AnalyzeNode on the unary operator. The operand has already // been analyzed when we recursed, and the unary operator wouldn't do anything // special anyway (since the operand is not a numeric constant) } } } else if (Parser.Settings.EvalTreatment != EvalTreatment.Ignore) { // if this is a window.eval call, then we need to mark this scope as unknown just as // we would if this was a regular eval call. // (unless, of course, the parser settings say evals are safe) // call AFTER recursing so we know the left-hand side properties have had a chance to // lookup their fields to see if they are local or global if (member != null && string.CompareOrdinal(member.Name, "eval") == 0) { if (member.LeftHandSide.IsWindowLookup) { // this is a call to window.eval() // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } else { CallNode callNode = m_func as CallNode; if (callNode != null && callNode.InBrackets && callNode.LeftHandSide.IsWindowLookup && callNode.Arguments.IsSingleConstantArgument("eval")) { // this is a call to window["eval"] // mark this scope as unknown so we don't crunch out locals // we might reference in the eval at runtime ScopeStack.Peek().IsKnownAtCompileTime = false; } } } /* REVIEW: may be too late. lookups may alread have been analyzed and * found undefined * // check to see if this is an assignment to a window["prop"] structure * BinaryOperator binaryOp = Parent as BinaryOperator; * if (binaryOp != null && binaryOp.IsAssign * && m_inBrackets * && m_func.IsWindowLookup * && m_args != null) * { * // and IF the property is a non-empty constant that isn't currently * // a global field... * string propertyName = m_args.SingleConstantArgument; * if (!string.IsNullOrEmpty(propertyName) * && Parser.GlobalScope[propertyName] == null) * { * // we want to also add it to the global fields so it's not undefined * Parser.GlobalScope.DeclareField(propertyName, null, 0); * } * } */ }
//--------------------------------------------------------------------------------------- // ForceReportInfo // // Generate a parser error (info), does not change the error state in the parse //--------------------------------------------------------------------------------------- private static void ForceReportInfo(Context context, JSError errorId) { Debug.Assert(context != null); context.HandleError(errorId); }
//--------------------------------------------------------------------------------------- // ReportError // // Generate a parser error. // The function is told whether or not next call to GetToken() should return the same // token or not //--------------------------------------------------------------------------------------- private void ReportError(JSError errorId, Context context, bool skipToken) { Debug.Assert(context != null); int previousSeverity = m_severity; m_severity = (new JScriptException(errorId)).Severity; // EOF error is special and it's the last error we can possibly get if (JSToken.EndOfFile == context.Token) EOFError(errorId); // EOF context is special else { // report the error if not in error condition and the // error for this token is not worse than the one for the // previous token if (m_goodTokensProcessed > 0 || m_severity < previousSeverity) context.HandleError(errorId); // reset proper info if (skipToken) m_goodTokensProcessed = -1; else { m_errorToken = m_currentToken; m_goodTokensProcessed = 0; } } }
public VariableDeclaration(Context context, JSParser parser, string identifier, Context idContext, AstNode initializer, FieldAttributes fieldAttributes, bool ignoreDuplicates) : base(context, parser) { // identifier cannot be null m_identifier = identifier; // initializer may be null m_initializer = initializer; if (m_initializer != null) { m_initializer.Parent = this; } // we'll need to do special stuff if the initializer if a function expression, // so try the conversion now FunctionObject functionValue = m_initializer as FunctionObject; string name = m_identifier.ToString(); ActivationObject currentScope = ScopeStack.Peek(); ActivationObject definingScope = currentScope; if (definingScope is BlockScope) { // block scope -- the variable is ACTUALLY defined in the containing function/global scope, // so we need to check THERE for duplicate defines. do { definingScope = definingScope.Parent; } while (definingScope is BlockScope); } JSVariableField field = definingScope[name]; if (field != null && (functionValue == null || functionValue != field.FieldValue)) { // this is a declaration that already has a field declared. // if the field is a named function expression, we want to fire an // ambiguous named function expression error -- and we know it's an NFE // if the FieldValue is a function object OR if the field // has already been marked ambiguous if (field.IsAmbiguous || field.FieldValue is FunctionObject) { if (idContext != null) { idContext.HandleError( JSError.AmbiguousNamedFunctionExpression, true ); } else if (context != null) { // not identifier context???? Try the whole statment context. // if neither context is set, then we don't get an error! context.HandleError( JSError.AmbiguousNamedFunctionExpression, true ); } // if we are preserving function names, then we need to mark this field // as not crunchable if (Parser.Settings.PreserveFunctionNames) { field.CanCrunch = false; } } else if (!ignoreDuplicates) { if (idContext != null) { // otherwise just a normal duplicate error idContext.HandleError( JSError.DuplicateName, field.IsLiteral ); } else if (context != null) { // otherwise just a normal duplicate error context.HandleError( JSError.DuplicateName, field.IsLiteral ); } } } bool isLiteral = ((fieldAttributes & FieldAttributes.Literal) != 0); // normally the value will be null. // but if there is no initializer, we'll use Missing so we can tell the difference. // and if this is a literal, we'll set it to the actual literal astnode object val = null; if (m_initializer == null) { val = Missing.Value; } else if (isLiteral || (functionValue != null)) { val = m_initializer; } m_field = currentScope.DeclareField( m_identifier, val, fieldAttributes ); m_field.OriginalContext = idContext; // we are now declared by a var statement m_field.IsDeclared = true; // if we are declaring a variable inside a with statement, then we will be declaring // a local variable in the enclosing scope if the with object doesn't have a property // of that name. But if it does, we won't actually be creating a variable field -- we'll // just use the property. So if we use an initializer in this declaration, then we will // actually be referencing the value. // SO, if this is a with-scope and this variable declaration has an initializer, we're going // to go ahead and bump up the reference. if (currentScope is WithScope && m_initializer != null) { m_field.AddReference(currentScope); } // special case the ambiguous function expression test. If we are var-ing a variable // with the same name as the function expression, then it's okay. We won't have an ambiguous // reference and it will be okay to use the name to reference the function expression if (functionValue != null && string.CompareOrdinal(m_identifier, functionValue.Name) == 0) { // null out the link to the named function expression // and make the function object point to the PROPER variable: the local within its own scope // and the inner is not pointing to the outer. functionValue.DetachFromOuterField(false); m_field.IsFunction = false; } }
internal override void AnalyzeNode() { // figure out if our reference type is a function or a constructor if (Parent is CallNode) { m_refType = ( ((CallNode)Parent).IsConstructor ? ReferenceType.Constructor : ReferenceType.Function ); } ActivationObject scope = ScopeStack.Peek(); VariableField = scope.FindReference(m_name); if (VariableField == null) { // this must be a global. if it isn't in the global space, throw an error // this name is not in the global space. // if it isn't generated, then we want to throw an error // we also don't want to report an undefined variable if it is the object // of a typeof operator if (!m_isGenerated && !(Parent is TypeOfNode)) { // report this undefined reference Context.ReportUndefined(this); // possibly undefined global (but definitely not local) Context.HandleError( (Parent is CallNode && ((CallNode)Parent).Function == this ? JSError.UndeclaredFunction : JSError.UndeclaredVariable), null, false ); } if (!(scope is GlobalScope)) { // add it to the scope so we know this scope references the global scope.AddField(new JSGlobalField( m_name, Missing.Value, 0 )); } } else { // BUT if this field is a place-holder in the containing scope of a named // function expression, then we need to throw an ambiguous named function expression // error because this could cause problems. // OR if the field is already marked as ambiguous, throw the error if (VariableField.NamedFunctionExpression != null || VariableField.IsAmbiguous) { // mark it as a field that's referenced ambiguously VariableField.IsAmbiguous = true; // throw as an error Context.HandleError(JSError.AmbiguousNamedFunctionExpression, true); // if we are preserving function names, then we need to mark this field // as not crunchable if (Parser.Settings.PreserveFunctionNames) { VariableField.CanCrunch = false; } } // see if this scope already points to this name if (scope[m_name] == null) { // create an inner reference so we don't keep walking up the scope chain for this name VariableField = scope.CreateInnerField(VariableField); } // add the reference VariableField.AddReference(scope); if (VariableField is JSPredefinedField) { // this is a predefined field. If it's Nan or Infinity, we should // replace it with the numeric value in case we need to later combine // some literal expressions. if (string.CompareOrdinal(m_name, "NaN") == 0) { // don't analyze the new ConstantWrapper -- we don't want it to take part in the // duplicate constant combination logic should it be turned on. Parent.ReplaceChild(this, new ConstantWrapper(double.NaN, PrimitiveType.Number, Context, Parser)); } else if (string.CompareOrdinal(m_name, "Infinity") == 0) { // don't analyze the new ConstantWrapper -- we don't want it to take part in the // duplicate constant combination logic should it be turned on. Parent.ReplaceChild(this, new ConstantWrapper(double.PositiveInfinity, PrimitiveType.Number, Context, Parser)); } } } }