blob: de2ea961315c9aed09a9ec9e06ed3408df8c4031 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package macromedia.asc.embedding;
import static macromedia.asc.embedding.avmplus.RuntimeConstants.TYPE_bool;
import static macromedia.asc.embedding.avmplus.RuntimeConstants.TYPE_boolean;
import static macromedia.asc.embedding.avmplus.RuntimeConstants.TYPE_double;
import static macromedia.asc.embedding.avmplus.RuntimeConstants.TYPE_decimal;
import static macromedia.asc.embedding.avmplus.RuntimeConstants.TYPE_int;
import static macromedia.asc.embedding.avmplus.RuntimeConstants.TYPE_uint;
import macromedia.asc.embedding.avmplus.*;
import macromedia.asc.util.*;
import macromedia.asc.parser.*;
import macromedia.asc.semantics.*;
import static macromedia.asc.parser.Tokens.*;
import static macromedia.asc.embedding.WarningConstants.*;
import static macromedia.asc.semantics.Slot.*;
import java.util.*;
import java.io.*;
/**
* This is the Evaluator for the -coach compiler option. It gives warnings for
* problems that may be valid AS3 code but will likely not work as the author intended.
*/
public final class LintEvaluator extends Emitter implements Evaluator, ErrorConstants
{
static boolean debug = false;
private static final String newline = System.getProperty("line.separator");
private static class CodeLocation
{
int pos; // position within <input> where error occurred
InputBuffer input; // buffer for the file where this error comes from.
}
// Structure used to record an occurance of a warning in the source code. These are queued up as they are recorded
// and then dumped out at the end of the evaluation, grouped by category.
private static class WarningRecord
{
CodeLocation loc;
int lineNum;
int colNum;
int code;
String errStringArg1;
String errStringArg2;
String errStringArg3;
WarningRecord(CodeLocation location, int l, int c, int co,String s1, String s2, String s3)
{
loc = location;
lineNum = l;
colNum = c;
code = co;
errStringArg1 = s1;
errStringArg2 = s2;
errStringArg3 = s3;
};
};
public boolean checkFeature(Context cx, Node node)
{
return true;
}
public Value evaluate( Context cx, Node node )
{
return null;
}
public Value evaluate( Context cx, IdentifierNode node )
{
if( node.ref != null )
{
return node.ref.getType(cx).getTypeValue(); // getDerivedType(cx);
}
return cx.noType();
}
// logs a warning if the slot is marked with [Deprecated] metadata
private void checkDeprecatedSlot(Context cx, Node node, ReferenceValue ref, Slot s)
{
if( s != null && s.getMetadata() != null )
{
ArrayList<MetaData> md = s.getMetadata();
for( int i =0, size = md.size(); i < size; ++i )
{
MetaData md_node = md.get(i);
if( "Deprecated".equals(md_node.id) )
{
String since = null;
String message = null;
String replacement = null;
for( int r = 0, val_size = md_node.count(); r < val_size; ++r )
{
final Value val = md_node.values[r];
if( val instanceof MetaDataEvaluator.KeyValuePair )
{
final MetaDataEvaluator.KeyValuePair temp = (MetaDataEvaluator.KeyValuePair)val;
if( "message".equals(temp.key) )
message = temp.obj;
else if ( "replacement".equals(temp.key) )
replacement = temp.obj;
else if ( "since".equals(temp.key) )
since = temp.obj;
}
else if( val instanceof MetaDataEvaluator.KeylessValue )
{
// [Deprecated("foo")]
final MetaDataEvaluator.KeylessValue temp = (MetaDataEvaluator.KeylessValue)val;
message = temp.obj;
}
}
logDeprecationWarning(node, cx, ref.name, since, message, replacement);
}
}
}
}
/**
* Logs the appropriate Deprecation warning based on available information in the metadata;
* will not log anything if since, message, and replacement are null.
*/
//*** IF YOU MODIFY THIS, update flex2.compiler.mxml.builder::checkLogDeprecationWarning()
private void logDeprecationWarning(Node node, Context cx, String name,
String since, String message, String replacement)
{
final int pos = node.getPosition();
final InputBuffer input = cx.input;
assert ((name != null) &&
(name.length() > 0));
final boolean hasSince = (since != null) && (since.length() > 0);
final boolean hasMessage = (message != null) && (message.length() > 0);
final boolean hasReplacement = (replacement != null) && (replacement.length() > 0);
if (hasMessage)
{
// [Deprecated("foo")]
// [Deprecated(message="foo")]
warning(pos, input, kWarning_DeprecatedMessage, message);
}
else if (hasReplacement)
{
if (hasSince)
{
// [Deprecated(since="1983", replacement="foo")]
warning(pos, input, kWarning_DeprecatedSince, name, since, replacement);
}
else
{
// [Deprecated(replacement="foo")]
warning(pos, input, kWarning_DeprecatedUseReplacement, name, replacement);
}
}
else if (hasSince)
{
// [Deprecated(since="1983")]
warning(pos, input, kWarning_DeprecatedSinceNoReplacement, name, since);
}
else
{
// this case differs from MXMLC's implementation:
// [Deprecated]
// [Deprecated(since="")]
// [Deprecated(message="")]
// [Deprecated(replacement="")]
warning(pos, input, kWarning_Deprecated, name);
}
}
private Value evaluateGenericCallExpression( Context cx, CallExpressionNode node )
{
if (first_pass)
{
if( node.args != null )
{
TypeValue baseType = baseType_context.last();
baseType_context.removeLast();
baseRef_context.add(null);
node.args.evaluate(cx,this);
baseType_context.add(baseType);
baseRef_context.removeLast();
}
node.expr.evaluate(cx,this);
return cx.noType(); // doesn't matter what we return during first_pass.
}
Slot s = (node.ref == null ? null : node.ref.getSlot( cx, (node.is_new ? NEW_TOKEN : EMPTY_TOKEN)));
if (node.ref != null && s == null)
{
TypeValue searchType = baseType_context.last(); // todo: why is this more reliable than node->ref->base
// if this is an attempt to access a static member of a class, switch searchType to the class's type
// This is necessary to allow lookup in the unsupportedMethodsMap table.
if (searchType != null && "Class".equals(searchType.name.toString()))
{
ReferenceValue br = baseRef_context.back();
s = (br != null ? br.getSlot(cx,GET_TOKEN) : null);
// c++ variant accesses union member typValue directly, java stores value in objValue
TypeValue t = (s != null && s.getObjectValue() instanceof TypeValue) ? (TypeValue)(s.getObjectValue()) : null;
searchType = (t != null) ? t : searchType;
}
// cn: note we can check xml/xmlList for unknown methods, but not unknown properties. Dynamically defined props due to xml children will
// trump any instance props at runtime. Not true for methods, however. A method not known at compile time will never be defined
// at runtime by the xml data itself. It would have to be defined by the author as a dynamic property of the instance (i.e. just like Date, RegExp and Error: unlikely).
if (searchType == types[kDateType] || searchType == cx.regExpType() || searchType == cx.xmlType() || searchType == cx.xmlListType() ||
(types[kErrorType] != null && types[kErrorType].includes(cx,searchType))) // these types are dynamic for backwards compatability, so ! doesn't catch this. Its unlikely anyone is adding dynamic props to them
{
warning(node.pos(), cx.input, kWarning_BadES3TypeMethod, node.ref.name, searchType.name.name);
}
else
{
Map<TypeValue,Integer> search = unsupportedMethodsMap.get(node.ref.name);
if (search != null && ! search.isEmpty())
{
for(TypeValue type : search.keySet())
{
if (type != null && type.includes(cx,searchType))
{
warning(node.getPosition(), cx.input, kWarning_DepricatedFunctionError, node.ref.name,
warningConstantsMap.get(search.get(type)));
}
}
}
}
}
if (node.ref != null && node.is_new)
{
Slot callSlot = node.ref.getSlot(cx,EMPTY_TOKEN);
if ( callSlot != null && callSlot.getType().getTypeValue() != cx.typeType() ) // don't warn for non-functions or functions actually declared to return a Class.
{
// in ES3.0, function A() { var local = new Object(); local.c = 20; return c; }; var dd = new A();
// dd is assigned the new object returned by A, rather than a new instance of A. In AS2.0, dd is
// assigned a new instance of A. While not something people would do on purpose, its not an unlikely
// accident that a function intended for new instance creation might have a return in it. Warn if it does
Slot getSlot = node.ref.getSlot(cx,GET_TOKEN);
ObjectValue base = node.ref.getBase(); // new obj.func() always worked as expected in AS2, however. Don't warn if there's a base
if (base == null && getSlot != null && slot_GetHasReturnValue(s))
{
warning(node.getPosition(), cx.input, kWarning_ConstructorReturnsValue, node.ref.name, node.ref.name);
}
}
}
if( s != null )
checkDeprecatedSlot(cx, node, node.ref, s);
if( node.args != null )
{
TypeValue baseType = baseType_context.last();
baseType_context.removeLast();
baseRef_context.add(null);
node.args.evaluate(cx,this);
baseRef_context.removeLast();
baseType_context.add(baseType);
}
node.expr.evaluate(cx,this);
if (node.ref == null)
return cx.noType();
// fix returnType if this is a "new" expression and function doesn't return a type
if (node.is_new)
{
if ("Boolean".equals(node.ref.name) && !(node.args != null && node.args.size() != 0))
{
warning(node.getPosition(), cx.input, kWarning_BooleanConstructorWithNoArgs);
}
/* Reactivated, but more specific (only for blank strings)... Old comment follows:
* This warning is kind of obscure and not likely to affect a lot of
* people compared to the volume of warnings it will generate. */
// cn: not sure where this came from (bug assigned to Jono?), but this is so special
// cased as to not be useful. We should remove this warning.
else if ("Number".equals(node.ref.name) && node.args != null && node.args.items.size() == 1)
{
Node arg = node.args.items.get(0);
Value argTypeVal = arg.evaluate(cx, this);
if (argTypeVal == cx.stringType()
&& (arg instanceof LiteralStringNode)
&& "".equals(((LiteralStringNode)arg).value.trim()))
{
warning(node.getPosition(), cx.input, kWarning_NumberFromStringChanges);
}
}
else if ("XML".equals(node.ref.name)) // AS2 XML class has been renamed to XMLDocument, AS3 XML is better and different
{
warning(node.getPosition(), cx.input, kWarning_XML_ClassHasChanged);
}
}
else
{
if ("Array".equals(node.ref.name)) // Array(x) cast is same as constructor "new Array(x)"
{
warning(node.getPosition(), cx.input, kWarning_BadArrayCast);
}
else if ("Date".equals(node.ref.name)) // Date(x) cast is same as "new Date().toString()", x is ignored, string returned
{
warning(node.getPosition(), cx.input, kWarning_BadDateCast);
}
else if ("XML".equals(node.ref.name)) // AS2 XML class has been renamed to XMLDocument, AS3 XML is better and different
{
warning(node.getPosition(), cx.input, kWarning_XML_ClassHasChanged);
}
}
Value returnVal = ( (s != null && s.getType() != null) ? s.getType().getTypeValue() : cx.noType());
return returnVal;
}
public Value evaluate( Context cx, CallExpressionNode node )
{
TypeValue baseType = baseType_context.last(); // baseType is void if this is a global function call
boolean evaluated = false;
Value returnVal = null;
if (first_pass)
{
// mark the slot of all functions which are arguements to addEventListener
// so we can detect the old AS2 eventListener event handlers which
// were declared, but not registered via this method.
if( node.ref != null && node.ref.name.compareTo("addEventListener") == 0)
{
if ((node.args != null) && node.args.items.size() > 1)
{
Node arg2 = node.args.items.get(1);
// There seems to be multiple, redundant coerce toObject nodes wrapping
// the member function get.
CoerceNode cnode = (arg2 instanceof CoerceNode) ? (CoerceNode) arg2 : null;
if (cnode != null)
arg2 = cnode.expr;
cnode = (arg2 instanceof CoerceNode) ? (CoerceNode) arg2 : null;
if (cnode != null)
arg2 = cnode.expr;
if (arg2.isMemberExpression())
{
MemberExpressionNode memb = (MemberExpressionNode)arg2;
if (memb.ref != null && memb.selector.isGetExpression())
{
Slot s = memb.ref.getSlot(cx,GET_TOKEN);
if (s != null)
slot_markAsRegisteredForEvent(s,true);
}
}
}
}
// no warning reporting during first pass
return evaluateGenericCallExpression(cx,node);
}
// check if this is a deprecated function
if (node.is_new || node.ref == null )
{
// Evaluate args, check for change in "new" behavior, get the return type
if (! evaluated)
returnVal = evaluateGenericCallExpression(cx,node); // must process args *after* processing call expression name
return returnVal;
}
else if (node.ref != null)
{
int numArgs = (node.args == null ? 0 : node.args.items.size());
if ("__resolve".equals(node.ref.name))
{
warning(node.getPosition(), cx.input, kWarning_ChangesInResolve);
}
/* Reactivated, but more specific (only for blank strings)... Old comment follows:
* This warning is kind of obscure and not likely to affect a lot of
* people compared to the volume of warnings it will generate.
*
* PS: I don't know whether this case ever gets called, but the other place
* it is checked in kWarning_NumberFromStringChanges does. */
// cn: not sure where this came from (bug assigned to Jono?), but this is so special
// cased as to not be useful. We should remove this warning.
else if ((baseType == cx.nullType()
&& ("Number".equals(node.ref.name)
&& numArgs == 1)))
{
Node arg = node.args.items.get(0);
Value argTypeVal = arg.evaluate(cx, this);
evaluated = true;
returnVal = cx.doubleType(); // RES don't bother with numberUsage issues here
if (argTypeVal == cx.stringType()
&& (arg instanceof LiteralStringNode)
&& "".equals(((LiteralStringNode)arg).value.trim()))
{
warning(node.getPosition(), cx.input, kWarning_NumberFromStringChanges);
}
}
// TODO: toString is special-cased... unsure exactly why
else if ( ((baseType == cx.nullType()) && ("String".equals(node.ref.name)) && (numArgs == 1))
|| ((baseType != cx.nullType()) && ("toString".equals(node.ref.name)) && (numArgs == 0)) )
{
TypeValue argTypeVal = null;
if (numArgs == 1)
{
Node arg = node.args.items.get(0);
Value argType = arg.evaluate(cx,this);
argTypeVal = (argType instanceof TypeValue) ? (TypeValue) argType : null;
}
else
{
argTypeVal = baseType_context.last();
}
evaluated = true;
returnVal = cx.stringType();
if (argTypeVal == cx.arrayType())
{
warning(node.getPosition(), cx.input, kWarning_ArrayToStringChanges);
}
final Slot s = node.ref.getSlot( cx, (node.is_new ? NEW_TOKEN : EMPTY_TOKEN));
checkDeprecatedSlot(cx, node, node.ref, s);
}
/*
else if ( (baseType != cx.nullType()) && ("split".equals(node.ref.name)) )
{
warning(node.position, cx.input, kWarning_PotentialSplitChanges, node.ref.name);
}
*/
}
// Evaluate args, check for change in "new" behavior, get the return type
if (! evaluated)
returnVal = evaluateGenericCallExpression(cx,node); // must process args *after* processing call expression name
return returnVal;
}
public Value evaluate( Context cx, InvokeNode node )
{
if( node.args != null )
{
node.args.evaluate(cx,this);
}
Value result=null;
if( "[[HasMoreNames]]".equals(node.name) )
{
result = cx.booleanType();
}
else if( "[[NextValue]]".equals(node.name) )
{
result = cx.noType();
}
else if ("[[NextName]]".equals(node.name) )
{
result = cx.stringType();
}
else if ( "[[ToXMLString]]".equals(node.name) )
{
result = cx.stringType();
}
else
{
// erik: [[CheckFilterOperand]] (etc...) was added a long time ago,
// and probably doesn’t affect LintEvaluator at all.
//assert(false) : node.name;
}
return result;
}
public Value evaluate( Context cx, DeleteExpressionNode node )
{
node.expr.evaluate(cx,this);
Slot slot = (node.ref != null ? node.ref.getSlot(cx,GET_TOKEN) : null);
if( slot != null && !in_with )
{
warning(node.expr.pos(), cx.input, kWarning_DeleteOfFixedProperty, node.ref.name);
}
return (node.void_result ? cx.voidType() : cx.booleanType() );
}
public Value evaluate(Context cx, ApplyTypeExprNode node)
{
node.typeArgs.evaluate(cx, this);
return null;
}
public Value evaluate( Context cx, GetExpressionNode node )
{
Value result = null;
if (first_pass)
{
if (node.ref == null)
node.expr.evaluate(cx,this);
return result; // doesn't matter what we return during first pass
}
if( node.getMode()==LEFTBRACKET_TOKEN ) // no compliance errors possible with bracket access.
{
// No reference, then must be dynamic (indexed) access.
if( node.expr.isLiteralInteger() )
{
result = cx.noType();
}
else // todo: add isLiteralString, see if there's a slot for that literal? if (node.expr.isLiteralString)
{
node.expr.evaluate(cx,this);
result = cx.noType();
}
}
else if( node.ref != null)
{
// nothing to evaluate, its an identifier
Slot slot = node.ref.getSlot(cx,GET_TOKEN); // check for base object type?
// special case for looking up a value of a class. ConstantEvaluator
// doesn't recognize direct Class references outside of the global scope, so
// it fails to set the ref's base correctly. It has to do this because when
// the function is called, its possible it will be called from a context where
// the global class definition has been superceeded by a local definition.
// This is pretty unlikely, however, and messes up the undeclared property reference
// detection. If the base of this expression (appears) to be the global definition for
// a class, temporarily reset the ref's base and use that definition.
TypeValue basetype = baseType_context.last();
if (slot == null && basetype != null && "Class".equals(basetype.name.toString()) )
{
ReferenceValue baseRef = baseRef_context.last();
if (baseRef != null)
{
ObjectValue realBase = (ObjectValue)(baseRef.getValue(cx));
ObjectValue oldBase = node.ref.getBase();
node.ref.setBase(realBase);
slot = node.ref.getSlot(cx,GET_TOKEN);
node.ref.setBase(oldBase);
}
}
// The last ditch, for handling Inteface base types. If there's a base type, see if its defined in its prototype.
// This definition is not good enough for code generation, but its good enough for a warning. Its pretty unlikely
// that the definition is superceeded at runtime.
if (slot == null && basetype != null && basetype.prototype != null && node.ref.name != null)
{
slot = getSlot(basetype.prototype, cx, node, GET_TOKEN);
}
if (slot != null )
checkDeprecatedSlot(cx, node, node.ref, slot);
TypeInfo ti = (slot != null) ? slot.getType() : null;
TypeValue type = (ti != null) ? ti.getTypeValue() : null;
result = type;
if (result == null)
{
result = cx.voidType();
}
else if (node.ref.name.compareTo("undefined")==0)
{
result = undefinedLiteral;
}
//TODO : remove this when these identifiers are declared in playerglobal.as
Boolean ignoreKeyword = hackIgnoreIdentifierMap.get(node.ref.name);
int base_index = node.ref.getScopeIndex(GET_TOKEN);
int slot_index = node.ref.getSlotIndex(GET_TOKEN);
boolean is_globalref = base_index == 0;
boolean is_dotref = base_index == -2;
boolean is_unbound_lexref = base_index == -1;
boolean is_unbound_dotref = is_dotref && slot_index < 0;
boolean is_unbound_globalref = is_globalref && slot_index < 0;
boolean is_unbound_ref = is_unbound_dotref || is_unbound_lexref || is_unbound_globalref;
if ( slot == null && (ignoreKeyword == null || !ignoreKeyword) && is_unbound_ref )
{
boolean unsupported = false;
// special case to avoid warning on access to a Class's prototype property. This
// property can't be expressed in global.as because you can't both declare a class
// and declare it to be an instance of the Class class.
if (basetype != null && "Class".equals(basetype.name.toString()) && "prototype".equals(node.ref.name))
{
return node.expr.evaluate(cx,this);
}
if (basetype == types[kDateType] || basetype == cx.regExpType() ||
(types[kErrorType] != null && types[kErrorType].includes(cx,basetype))) // these types are dynamic for backwards compatability, so ! doesn't catch this. Its unlikely anyone is adding dynamic props to them
{
warning(node.pos(), cx.input, kWarning_BadES3TypeProp, node.ref.name, basetype.name.name);
unsupported = true;
}
else
{
Map<TypeValue,Integer> search = unsupportedPropsMap.get(node.ref.name);
if (search != null && !search.isEmpty() ) // && search.second.empty() == false)
{
TypeValue searchType = baseType_context.last(); // todo: why is this more reliable than node->ref->base
// if this is an attempt to access a static member of a class, switch searchType to the class's type
// This is necessary to allow lookup in the unsupportedPropsMap table.
if (searchType != null && "Class".equals(searchType.name.toString()))
{
ReferenceValue br = baseRef_context.back();
Slot s = (br != null ? br.getSlot(cx,GET_TOKEN) : null);
// c++ variant accesses union member typValue directly, java stores value in objValue
TypeValue t = (s != null && s.getObjectValue() instanceof TypeValue) ? (TypeValue)(s.getObjectValue()) : null;
searchType = (t != null) ? t : searchType;
}
for(TypeValue matchType : search.keySet())
{
if (matchType != null && matchType.includes(cx,searchType))
{
unsupported = true;
warning(node.getPosition(), cx.input, kWarning_DepricatedPropertyError, node.ref.name,
warningConstantsMap.get(search.get(matchType)));
}
}
}
if ( !unsupported && node.ref.name.startsWith("_level") && (node.base == null) )
{
unsupported = true;
warning(node.getPosition(), cx.input, kWarning_LevelNotSupported);
}
// return * for the value type if we are accessing a prop of a dynamic class instance
ObjectValue base = node.ref.getBase();
return (base != null && base.isDynamic() ? cx.noType() : cx.voidType());
}
}
if (type == cx.functionType())
{
slot = node.ref.getSlot(cx,EMPTY_TOKEN);
if (slot != null && basetype != cx.xmlType() && basetype != cx.xmlListType())
{
warning( node.getPosition(), cx.input, kWarning_ScopingChangeInThis, node.ref.name, (node.base != null) ? node.base.name : "" );
}
}
}
else
{
// If there is no reference, then node.expr is a general
// expression that needs to be evaluated here.
result = node.expr.evaluate(cx,this);
}
return result;
}
private Slot getSlot(ObjectValue proto, Context cx, SelectorNode node, int type)
{
Slot slot = null;
Namespaces ns = proto.hasNames(cx, type, node.ref.name, node.ref.getImmutableNamespaces());
if (ns != null)
{
// we just grab the first namespace value, which I assume is what would happen at runtime
ObjectValue last = ns.first();
int slotIndex = proto.getSlotIndex(cx, type, node.ref.name, last);
slot = proto.getSlot(cx, slotIndex);
}
return slot;
}
public Value evaluate( Context cx, SetExpressionNode node )
{
if (first_pass)
{
return node.args.evaluate(cx,this);
}
Slot slot = null;
if( node.ref != null )
{
slot = node.ref.getSlot(cx,GET_TOKEN);
// special case for looking up a value of a class. ConstantEvaluator
// doesn't recognize direct Class references outside of the global scope, so
// it fails to set the ref's base correctly. It has to do this because when
// the function is called, its possible it will be called from a context where
// the global class definition has been superceeded by a local definition.
// This is pretty unlikely, however, and messes up the undeclared property reference
// detection. If the base of this expression (appears) to be the global definition for
// a class, temporarily reset the ref's base and use that definition.
TypeValue baseType = baseType_context.last();
if (baseType != null && "Class".equals(baseType.name.toString()))
{
ReferenceValue baseRef = baseRef_context.last();
if (baseRef != null)
{
ObjectValue realBase = (ObjectValue)(baseRef.getValue(cx));
ObjectValue oldBase = node.ref.getBase();
node.ref.setBase(realBase);
slot = node.ref.getSlot(cx,GET_TOKEN);
node.ref.setBase(oldBase);
}
}
// The last ditch, for handling Inteface base types. If there's a base type, see if its defined in its prototype.
// This definition is not good enough for code generation, but its good enough for a warning. Its pretty unlikely
// that the definition is superceeded at runtime.
if (slot == null && baseType != null && baseType.prototype != null && node.ref.name != null)
{
slot = getSlot(baseType.prototype, cx, node, SET_TOKEN);
}
//TypeValue* dt = node->ref->getType(cx); // this uses use-definition trees, rather than the slot's def. Assume slot's def for warnings
int base_index = node.ref.getScopeIndex(SET_TOKEN);
int slot_index = node.ref.getSlotIndex(SET_TOKEN);
boolean is_globalref = base_index == 0;
boolean is_dotref = base_index == -2;
boolean is_unbound_lexref = base_index == -1;
boolean is_unbound_dotref = is_dotref && slot_index < 0;
boolean is_unbound_globalref = is_globalref && slot_index < 0;
boolean is_unbound_ref = is_unbound_dotref || is_unbound_lexref || is_unbound_globalref;
if (slot != null )
checkDeprecatedSlot(cx, node.expr, node.ref, slot);
// special case to avoid warning on access to a Class's prototype property. This
// property can't be expressed in global.as because you can't both declare a class
// and declare it to be an instance of the Class class.
if (baseType != null && "Class".equals(baseType.name.toString()) && "prototype".equals(node.ref.name))
{
}
else if (slot != null && slot.getType().getTypeValue() == cx.uintType() &&
node.args != null && node.args.items.size() == 1 && node.args.items.get(0) instanceof LiteralNumberNode)
{
LiteralNumberNode ln = (LiteralNumberNode)(node.args.items.get(0));
if (ln.numericValue.doubleValue() < 0)
warning(node.getPosition(), cx.input, kWarning_NegativeUintLiteral);
}
else if ( slot == null && is_unbound_ref)
{
int pos = (node.expr != null ? node.expr.getPosition() : node.getPosition());
boolean unsupported = false;
if (baseType == types[kDateType] || baseType == cx.regExpType() ||
(types[kErrorType] != null && types[kErrorType].includes(cx,baseType))) // these types are dynamic for backwards compatability, so ! doesn't catch this. Its unlikely anyone is adding dynamic props to them
{
warning(node.pos(), cx.input, kWarning_BadES3TypeProp, node.ref.name, baseType.name.name);
unsupported = true;
}
else
{
Map<TypeValue,Integer> search = unsupportedPropsMap.get(node.ref.name);
if (search != null && !search.isEmpty())
{
TypeValue searchType = baseType_context.last(); // todo: why is this more reliable than node->ref->base
// if this is an attempt to access a static member of a class, switch searchType to the class's type
// This is necessary to allow lookup in the unsupportedMethodsMap table.
if (searchType != null && "Class".equals(searchType.name.toString()))
{
ReferenceValue br = baseRef_context.back();
Slot s = (br != null ? br.getSlot(cx,GET_TOKEN) : null);
// c++ variant accesses union member typValue directly, java stores value in objValue
TypeValue t = (s != null && s.getObjectValue() instanceof TypeValue) ? (TypeValue)(s.getObjectValue()) : null;
searchType = (t != null) ? t : searchType;
}
for(TypeValue type : search.keySet())
{
if (type != null && type.includes(cx,searchType))
{
unsupported = true;
warning(pos, cx.input, kWarning_DepricatedPropertyError, node.ref.name,
warningConstantsMap.get(search.get(type)));
}
}
}
}
if (unsupported == false && baseType != null ) // check for unsupported event handlers (StyleSheet.onLoad = new function() ... )
{
Map<TypeValue,Integer> search = unsupportedEventsMap.get(node.ref.name);
if (search != null && ! search.isEmpty()) // it matches a former auto-registered event handler name
{
ObjectValue scope = cx.scope();
if (baseType != null) // !!@todo: check that this dynamic var wasn't seen in an addEventListener call during first_pass
{
TypeValue searchType = baseType_context.last(); // todo: why is this more reliable than node->ref->base
// if this is an attempt to access a static member of a class, switch searchType to the class's type
// This is necessary to allow lookup in the unsupportedMethodsMap table.
if (searchType != null && "Class".equals(searchType.name.toString()))
{
ReferenceValue br = baseRef_context.back();
Slot s = (br != null ? br.getSlot(cx,GET_TOKEN) : null);
// c++ variant accesses union member typValue directly, java stores value in objValue
TypeValue t = (s != null && s.getObjectValue() instanceof TypeValue) ? (TypeValue)(s.getObjectValue()) : null;
searchType = (t != null) ? t : searchType;
}
for(TypeValue type : search.keySet())
{
if (type != null && type.includes(cx,searchType)) // it's defining Type matches one of the warning cases
{
warning(pos, cx.input, kWarning_DepricatedEventHandlerError, warningConstantsMap.get(search.get(type)));
unsupported = true;
}
}
}
}
}
ObjectValue base = node.ref.getBase();
if ((baseType != null && baseType != cx.voidType() && baseType != cx.nullType())
&& ((base != null && base.isFinal() && !base.isDynamic())
|| cx.doubleType().includes(cx, baseType)
|| cx.numberType() == baseType
|| (cx.statics.es4_numerics && cx.decimalType() == baseType)
|| cx.stringType() == baseType
|| cx.booleanType() == baseType
|| types[kMathType] == baseType))
{
warning(node.getPosition(), cx.input, kWarning_ClassIsSealed, getSimpleTypeName(baseType));
}
}
else if (baseType == types[kTextFieldType] && "text".equals(node.ref.name))
{
// look for " member.text += "some text" type syntax. The compiler will have already converted this to
// "member.text = member.text + "some text" by now
Node arg1 = node.args.items.get(0);
if (arg1 instanceof BinaryExpressionNode)
{
MemberExpressionNode membArg = (((BinaryExpressionNode)arg1).lhs instanceof MemberExpressionNode) ?
(MemberExpressionNode)(((BinaryExpressionNode)arg1).lhs) :
null;
if (membArg != null)
{
MemberExpressionNode membArgBase = (membArg.base instanceof MemberExpressionNode) ? (MemberExpressionNode)(membArg.base) : null;
ReferenceValue br = baseRef_context.back();
if (membArgBase != null && membArgBase.ref != null && br != null && br.slot == membArgBase.ref.slot)
warning(node.getPosition(), cx.input, kWarning_SlowTextFieldAddition);
}
}
}
}
else
{
node.expr.evaluate(cx,this);
}
Value result = node.args.evaluate(cx,this);
if (result == cx.nullType() && slot != null)
{
TypeValue t = slot.getType().getTypeValue();
TypeValue rt = (TypeValue)result;
if ((t != null) && (t.isNumeric(cx) || t == cx.booleanType()))
warning(node.args.getPosition(), cx.input, kWarning_BadNullAssignment, t.name.toString());
}
else if (slot != null && slot.getType().getTypeValue() == cx.booleanType() && result instanceof TypeValue &&
result != cx.booleanType() && result != cx.noType() && result != cx.objectType())
{
TypeValue rt = (TypeValue)result;
warning(node.args.getPosition(), cx.input, kWarning_BadBoolAssignment, rt.name.toString());
}
return result;
}
public Value evaluate( Context cx, ThisExpressionNode node )
{
// What 'this' is, depends on where it is:
// o instance method or accessor - this is the second from end of scope chain
// o class method or accessor - no this
// o function - is passed in by the caller, its ct type is object
//return cx.noType().prototype;
int this_context = this_contexts.last();
ObjectValue this_value = null;
switch( this_context )
{
case global_this:
this_value = cx.scope(0);
break;
case instance_this:
{
int scope_depth = cx.getScopes().size()-1;
this_value = cx.scope(scope_depth-1); // If this is an instance method, scope is second from top
}
break;
case error_this:
default:
this_value = null;
break;
}
return (this_value != null) ? this_value.getType(cx).getTypeValue() : null;
}
public Value evaluate( Context cx, LiteralBooleanNode node )
{
return cx.booleanType();
}
public Value evaluate( Context cx, LiteralNumberNode node )
{
return node.type;
}
public Value evaluate( Context cx, LiteralStringNode node )
{
return cx.stringType();
}
public Value evaluate( Context cx, LiteralNullNode node )
{
return cx.nullType();
}
public Value evaluate( Context cx, LiteralRegExpNode node )
{
return cx.regExpType();
}
public Value evaluate( Context cx, LiteralXMLNode node )
{
if( node.list != null )
{
node.list.evaluate(cx,this);
}
return cx.xmlType();
}
public Value evaluate( Context cx, ParenExpressionNode node )
{
assert(false); // "shouldn't be here: evaluate( Context cx, ParenExpressionNode node )" ;
return cx.voidType();
}
public Value evaluate( Context cx, ParenListExpressionNode node )
{
assert(false); // "shouldn't be here: evaluate( Context cx, ParenListExpressionNode node )" ;
return cx.voidType();
}
public Value evaluate( Context cx, LiteralObjectNode node )
{
if( node.fieldlist != null)
{
node.fieldlist.evaluate(cx,this);
}
return cx.noType();
}
public Value evaluate( Context cx, LiteralFieldNode node )
{
node.name.evaluate(cx,this);
node.value.evaluate(cx,this);
return (node.ref != null) ? node.ref.getType(cx).getTypeValue() : null; // getDerivedType(cx) : 0 );
}
public Value evaluate( Context cx, LiteralArrayNode node )
{
if( node.elementlist != null)
node.elementlist.evaluate(cx,this);
return cx.arrayType();
}
public Value evaluate( Context cx, LiteralVectorNode node )
{
node.type.evaluate(cx, this);
if( node.elementlist != null)
node.elementlist.evaluate(cx,this);
return cx.vectorType();
}
public Value evaluate( Context cx, MemberExpressionNode node )
{
if( node.base != null)
{
Value result = node.base.evaluate(cx,this);
TypeValue baseType = (result instanceof TypeValue) ? (TypeValue)result : null;
if (baseType == null || baseType == cx.voidType()) // if base is undefined, we've warned about it. Don't warn that
// we don't know any of the properties of the unknown thing.
{
return cx.voidType();
}
baseType_context.add(baseType); // remember our base's type, sometimes needed for Get/SetExpression handling
// remember the ref to our base, sometimes needed for Get/SetExpression handling
MemberExpressionNode simpleBaseRef = (node.base instanceof MemberExpressionNode) ? (MemberExpressionNode)(node.base) : null;
baseRef_context.add( simpleBaseRef != null ? simpleBaseRef.ref : null);
}
else
{
baseType_context.add(cx.nullType());
//baseType_context.add(cx.voidType());
baseRef_context.push_back(null);
}
Value result = node.selector.evaluate(cx,this);
TypeValue baseType = baseType_context.removeLast();
baseRef_context.removeLast();
// don't trust the type of an xml property. Properties like .name have slots for the name funciton, but could
// evaluate to an xml defined property named "name" at runtime.
return (baseType == cx.xmlType() || baseType == cx.xmlListType()) ? cx.noType() : result;
}
public Value evaluate( Context cx, UnaryExpressionNode node )
{
Value result = node.expr.evaluate(cx,this);
// no need to check expected type. No unary expression expects a Function value
if (result == cx.functionType())
{
if (node.expr instanceof MemberExpressionNode)
{
MemberExpressionNode memb = (MemberExpressionNode)(node.expr);
String funcName = memb.ref.name;
warning(memb.pos(), cx.input, kWarning_UnlikelyFunctionValue, cx.objectType().toString(),
funcName);
}
}
if (node.op == TYPEOF_TOKEN) // typeof can return String or undefined, use object
return cx.noType();
return (node.slot != null) ? node.slot.getType().getTypeValue() : cx.voidType();
}
public Value evaluate( Context cx, IncrementNode node )
{
node.expr.evaluate(cx,this);
if (node.slot != null)
return node.slot.getType().getTypeValue();
else {
TypeValue currentNumberType = cx.doubleType();
if (node.numberUsage != null)
switch (node.numberUsage.get_usage()) {
case NumberUsage.use_int:
currentNumberType = cx.intType();
break;
case NumberUsage.use_uint:
currentNumberType = cx.uintType();
break;
case NumberUsage.use_decimal:
currentNumberType = cx.decimalType();
break;
case NumberUsage.use_double:
case NumberUsage.use_Number:
default:
currentNumberType = cx.doubleType();
}
return currentNumberType;
}
}
public Value evaluate( Context cx, BinaryExpressionNode node )
{
Value lhsType = null;
Value rhsType = null;
if (first_pass)
{
if (node.lhs != null)
node.lhs.evaluate(cx,this);
if (node.rhs != null)
node.rhs.evaluate(cx,this);
return (node.slot != null) ? node.slot.getType().getTypeValue() : cx.voidType();
}
if( node.lhs != null)
lhsType = node.lhs.evaluate(cx,this); // do lhs first, then potentially modify operand, then rhs. Output buffer can't back up
switch( node.op )
{
case LESSTHAN_TOKEN:
case GREATERTHAN_TOKEN:
case LESSTHANOREQUALS_TOKEN:
case GREATERTHANOREQUALS_TOKEN:
case STRICTEQUALS_TOKEN:
case STRICTNOTEQUALS_TOKEN:
case EQUALS_TOKEN:
case NOTEQUALS_TOKEN:
if( node.rhs != null)
rhsType = node.rhs.evaluate(cx,this);
// if lhsType or rhsType is void, it means its the result of an undefined prop access or function call
// Don't warn twice about it.
if ( (lhsType != rhsType) && (lhsType != cx.voidType()) && (rhsType != cx.voidType()) )
{
if ( (lhsType == undefinedLiteral && rhsType != cx.noType()) ||
(lhsType != cx.noType() && rhsType == undefinedLiteral) )
{
String typeName = (lhsType == undefinedLiteral ? getSimpleTypeName((TypeValue )rhsType)
: getSimpleTypeName((TypeValue )lhsType));
warning(node.getPosition(), cx.input, kWarning_BadUndefinedComparision, typeName, typeName);
}
else if ( (lhsType == cx.nullType()) || (rhsType == cx.nullType()) )
{
TypeValue nonVoidType = (lhsType == cx.nullType() ? (TypeValue)rhsType : (TypeValue)lhsType);
switch( nonVoidType.getTypeId() )
{
case TYPE_boolean:
case TYPE_int:
case TYPE_uint:
case TYPE_double:
case TYPE_decimal:
warning(node.getPosition(), cx.input, kWarning_BadNullComparision, getSimpleTypeName(nonVoidType));
break;
}
}
}
if (lhsType == cx.doubleType() || (cx.statics.es4_numerics && (lhsType == cx.decimalType())))
{
MemberExpressionNode mem = (node.lhs instanceof MemberExpressionNode) ? (MemberExpressionNode)(node.lhs) : null;
if (mem != null)
{
GetExpressionNode getter = (mem.selector instanceof GetExpressionNode) ? (GetExpressionNode)(mem.selector) : null;
if (getter != null && "NaN".equals(getter.ref.name))
warning(node.getPosition(), cx.input, kWarning_BadNaNComparision);
}
}
if (rhsType == cx.doubleType() || (cx.statics.es4_numerics && (lhsType == cx.decimalType())))
{
MemberExpressionNode mem = (node.rhs instanceof MemberExpressionNode) ? (MemberExpressionNode)(node.rhs) : null;
if (mem != null)
{
GetExpressionNode getter = (mem.selector instanceof GetExpressionNode) ? (GetExpressionNode)(mem.selector) : null;
if (getter != null && "NaN".equals(getter.ref.name))
warning(node.getPosition(), cx.input, kWarning_BadNaNComparision);
}
}
break;
case INSTANCEOF_TOKEN:
// yes, I do mean position-11; we don't know the position of the word instanceof
// so we start from rhs and go back 11 spaces
warning(node.rhs.getPosition() - 11, cx.input, kWarning_InstanceOfChanges);
if( node.rhs != null )
node.rhs.evaluate(cx,this);
break;
case MULT_TOKEN:
case DIV_TOKEN:
case MODULUS_TOKEN:
case MINUS_TOKEN:
case LEFTSHIFT_TOKEN:
case RIGHTSHIFT_TOKEN:
case UNSIGNEDRIGHTSHIFT_TOKEN:
case BITWISEAND_TOKEN:
case BITWISEXOR_TOKEN:
case BITWISEOR_TOKEN:
case LOGICALAND_TOKEN:
case LOGICALOR_TOKEN:
if( node.rhs != null )
rhsType = node.rhs.evaluate(cx,this);
if (lhsType == cx.functionType())
{
if (node.lhs instanceof MemberExpressionNode)
{
MemberExpressionNode memb = (MemberExpressionNode)(node.lhs);
String funcName = memb.ref.name;
warning(node.lhs.pos(), cx.input, kWarning_UnlikelyFunctionValue, cx.objectType().toString(),
funcName);
}
}
if (rhsType == cx.functionType())
{
if (node.rhs instanceof MemberExpressionNode)
{
MemberExpressionNode memb = (MemberExpressionNode)(node.rhs);
String funcName = memb.ref.name;
warning(node.rhs.pos(), cx.input, kWarning_UnlikelyFunctionValue, cx.objectType().toString(),
funcName);
}
}
break;
case PLUS_TOKEN:
case IN_TOKEN:
default:
if( node.rhs != null )
rhsType = node.rhs.evaluate(cx,this);
break;
}
// Return the result type of the slot
return (node.slot!= null) ? node.slot.getType().getTypeValue() : cx.voidType();
}
public Value evaluate( Context cx, ConditionalExpressionNode node )
{
Value result = null;
Value result2 = null;
if( node.condition != null )
node.condition.evaluate(cx,this);
if( node.thenexpr != null )
result = node.thenexpr.evaluate(cx,this);
if( node.elseexpr != null)
result2 = node.elseexpr.evaluate(cx,this);
if (result == result2)
return result;
else
{
TypeValue type1 = (result instanceof TypeValue) ? (TypeValue) result : null;
TypeValue type2 = (result2 instanceof TypeValue) ? (TypeValue) result2 : null;
if (type1 == null || type2 == null)
return null;
else if (type1.includes(cx,type2))
return type1;
else if (type2.includes(cx,type1))
return type2;
else
return cx.noType(); // incompatable types
}
}
public Value evaluate( Context cx, ArgumentListNode node )
{
Value result = null;
// wrong # of args will have been handled as an error during ConstantEvalation time.
int numDeclaredParams = size(node.decl_styles);
if ( numDeclaredParams != 0 && node.expected_types.at(0).getTypeValue() != cx.voidType()) // void is used for class based method which declares no arguments
{
int param_count = 0;
TypeValue expectedType = null;
for (int i = 0, size = node.items.size(); i < size; i++)
{
Node item = node.items.get(i);
result = item.evaluate(cx, this);
if (param_count < numDeclaredParams && node.decl_styles.at(param_count) != PARAM_Rest && result != null && result instanceof TypeValue)
{
expectedType = node.expected_types.at(param_count).getTypeValue();
if (result != expectedType && result == cx.functionType() && expectedType != cx.objectType() && expectedType != cx.noType())
{
if (item instanceof MemberExpressionNode)
{
MemberExpressionNode memb = (MemberExpressionNode)item;
String funcName = memb.ref.name;
warning(item.pos(), cx.input, kWarning_UnlikelyFunctionValue, expectedType.name.toString(),
funcName);
}
}
else if (expectedType == cx.uintType() && item instanceof LiteralNumberNode && ((LiteralNumberNode)item).numericValue.doubleValue() < 0)
{
warning(item.pos(),cx.input, kWarning_NegativeUintLiteral);
}
else if ( ((expectedType != null) && (expectedType.isNumeric(cx) || expectedType == cx.booleanType()))
&& item instanceof LiteralNullNode )
{
warning(item.getPosition(), cx.input, kWarning_BadNullAssignment, expectedType.name.toString());
}
else if ( expectedType == cx.booleanType() && result instanceof TypeValue &&
result != cx.booleanType() && result != cx.noType() && result != cx.objectType())
{
TypeValue rt = (TypeValue)result;
warning(item.getPosition(), cx.input, kWarning_BadBoolAssignment, rt.name.toString());
}
if (node.decl_styles.at(param_count) != PARAM_Rest)
++param_count;
}
}
}
else
{
for (int i = 0, size = node.items.size(); i < size; i++)
{
Node item = node.items.get(i);
result = item.evaluate(cx, this);
}
}
return result;
}
public Value evaluate( Context cx, ListNode node )
{
Value result = null;
for (int i = 0, size = node.items.size(); i < size; i++)
{
Node item = node.items.get(i);
result = item.evaluate(cx, this);
}
return result;
}
// Statements
public Value evaluate( Context cx, StatementListNode node )
{
Value result = null;
for (int i = 0, size = node.items.size(); i < size; i++)
{
Node item = node.items.get(i);
if (!doing_method)
{
doing_method = true;
}
if (item != null) // cn: probably not necessary, but mimic'ing other evaluators to make sure
{
result = item.evaluate(cx, this);
// look for special case error of calling "func;" where you meant "func();". Note, this does not
// catch a missing () in a list expression ala (var x = 1; trace; trace(x); }, which would
// require examining every item in the (possibly nested) ListNode. Also note that just checking
// the result type for functionType isn't enough because FunctionDefintionNodes,
// SetExpressionNodes which set the value to a function closure, and returnStatments which return a Function
// willl all return type Function. Unfortuneately, it means we have to do this nasty explicit parsetree
// format dependant check here. Its not safe to move this to ListNode processing because the expression
// part of a returnStatement is a ListNode, as would be the argument in a call like
// "funcCall( (var x = function() { return true; }, x) )"
// This only catches a statement like "foo;" where "foo();" was expected, but I think that's likely to be 99+%
// of the cases we are likely to see.
if (result == cx.functionType() && (item instanceof ExpressionStatementNode))
{
ExpressionStatementNode exp = (ExpressionStatementNode)(item);
if (exp.expr instanceof ListNode)
{
ListNode alist = (ListNode)exp.expr;
if (alist.items.size() == 1)
{
Node anItem = alist.items.get(0);
if ( anItem instanceof MemberExpressionNode && ((MemberExpressionNode)(anItem)).selector instanceof GetExpressionNode)
{
warning(item.pos(), cx.input, kWarning_UnlikelyFunctionValue, cx.voidType().name.toString(),
"the function");
}
}
}
}
}
}
return result;
}
public Value evaluate( Context cx, EmptyStatementNode node )
{
Value result = null;
return result;
}
public Value evaluate( Context cx, ExpressionStatementNode node )
{
Value result = null;
if (node.expr != null)
{
result = node.expr.evaluate(cx,this);
}
return result;
}
public Value evaluate( Context cx, LabeledStatementNode node )
{
if( node.statement != null )
node.statement.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, IfStatementNode node )
{
if ( node.condition != null )
{
if (!first_pass)
{
ListNode condList = (node.condition instanceof ListNode) ? (ListNode)(node.condition) : null;
if ( condList != null && condList.items.size() == 1 )
{
Node item = condList.items.get(0);
CoerceNode cn = (item instanceof CoerceNode) ? (CoerceNode)(item) : null;
if (cn != null)
{
MemberExpressionNode mem = (cn.expr instanceof MemberExpressionNode) ? (MemberExpressionNode)(cn.expr) : null;
if (mem != null && (mem.selector instanceof SetExpressionNode))
{
warning(node.getPosition(), cx.input, kWarning_AssignmentWithinConditional);
}
}
}
}
node.condition.evaluate(cx,this);
}
if( node.thenactions != null)
{
if (!first_pass)
{
StatementListNode thenList = (node.thenactions instanceof StatementListNode) ? (StatementListNode)(node.thenactions) : null;
if ( thenList != null && thenList.items.size() == 1 )
{
Node item = thenList.items.get(0);
if (item instanceof EmptyStatementNode)
{
warning(node.getPosition(), cx.input, kWarning_UnexpectedEmptyStatement);
}
}
}
node.thenactions.evaluate(cx,this);
}
if( node.elseactions != null)
node.elseactions.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, SwitchStatementNode node )
{
if( node.expr != null)
node.expr.evaluate(cx,this);
if( node.statements != null)
node.statements.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, CaseLabelNode node )
{
if( node.label != null)
node.label.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, DoStatementNode node )
{
if( node.expr != null)
node.expr.evaluate(cx,this);
if( node.statements != null)
node.statements.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, WhileStatementNode node )
{
if( node.statement != null)
{
if (!first_pass)
{
StatementListNode thenList = (node.statement instanceof StatementListNode) ? (StatementListNode)(node.statement) : null;
if ( thenList != null && thenList.items.size() == 1 )
{
Node item = thenList.items.get(0);
if (item instanceof EmptyStatementNode)
{
warning(node.getPosition(), cx.input, kWarning_UnexpectedEmptyStatement);
}
}
}
node.statement.evaluate(cx,this);
}
if( node.expr != null)
node.expr.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, ForStatementNode node )
{
if (node.is_forin && !first_pass)
{
warning(node.getPosition(), cx.input, kWarning_ForVarInChanges);
}
if( node.initialize != null )
{
node.initialize.evaluate(cx,this);
node.initialize.voidResult();
}
if( node.increment != null )
{
node.increment.evaluate(cx,this);
node.increment.voidResult();
}
if( node.test != null )
{
node.test.evaluate(cx,this);
}
if( node.statement != null )
{
if (!first_pass)
{
StatementListNode thenList = (node.statement instanceof StatementListNode) ? (StatementListNode)(node.statement) : null;
if ( thenList != null && thenList.items.size() == 1 )
{
Node item = thenList.items.get(0);
if (item instanceof EmptyStatementNode)
{
warning(node.getPosition(), cx.input, kWarning_UnexpectedEmptyStatement);
}
}
}
node.statement.evaluate(cx,this);
}
return null;
}
public Value evaluate( Context cx, WithStatementNode node )
{
if( node.expr != null)
node.expr.evaluate(cx,this);
if( node.statement != null)
{
cx.pushScope(node.activation);
boolean saved_in_with = in_with;
in_with = true;
int saveWithDepth = cx.statics.withDepth;
cx.statics.withDepth = cx.getScopes().size()-1;
node.statement.evaluate(cx,this);
cx.statics.withDepth = saveWithDepth;
in_with = saved_in_with;
cx.popScope();
}
return null;
}
public Value evaluate( Context cx, ContinueStatementNode node )
{
if( node.id != null)
node.id.evaluate(cx,this);
return null;
}
public Value evaluate( Context cx, BreakStatementNode node )
{
return null;
}
private void checkReturnStatementType(Context cx, Node item, Value returnType)
{
// look for case where a Function is being returned where something else is expected.
// (i.e. "return func;" where "return func();" was intended). This isn't always a -!
// type error, since all types transparently coerce to Boolean and Object.
if (returnType == cx.functionType())
{
if (item instanceof MemberExpressionNode)
{
MemberExpressionNode memb = (MemberExpressionNode)item;
String funcName = memb.ref.name;
warning(memb.pos(), cx.input, kWarning_UnlikelyFunctionValue, expected_returnType.name.toString(),
funcName);
}
}
else if (expected_returnType == cx.uintType())
{
if (item instanceof LiteralNumberNode && ((LiteralNumberNode)item).numericValue.doubleValue() < 0)
{
warning(item.pos(),cx.input, kWarning_NegativeUintLiteral);
}
}
else if ( expected_returnType == cx.booleanType() && returnType instanceof TypeValue &&
returnType != cx.noType() && returnType != cx.objectType())
{
TypeValue rt = (TypeValue)returnType;
warning(item.getPosition(), cx.input, kWarning_BadBoolAssignment, rt.name.toString());
}
else if ((expected_returnType != null) && (expected_returnType.isNumeric(cx) || expected_returnType == cx.booleanType()))
{
if (item instanceof LiteralNullNode)
{
warning(item.getPosition(), cx.input, kWarning_BadNullAssignment, expected_returnType.name.toString() );
}
}
}
public Value evaluate( Context cx, ReturnStatementNode node )
{
if( node.expr != null)
{
Value returnType = node.expr.evaluate(cx,this);
if (returnType != cx.voidType())
body_has_return = true;
if (!first_pass && expected_returnType != returnType)
{
Node item = node.expr;
// need to handle "return expr" (in which case item is a listNode with one element, expr)
// "return(expr)" (in which case item is a listNode with a one ListNode element. The inner ListNode's one element is expr
// "return(a=0, a+= b, a)" (same as above, but the inner listNode contains three elements, a=0, a+=b, and a)
// "return (a ? b : c)" (same as above, but the inner listNode contains a ConditionalNode as its one element)
// "return (a=0, a+=b, (b=0, b+=c, -1))" you get the idea
while (item instanceof ListNode)
{
ListNode list = (ListNode)item;
item = list.items.get(list.items.size()-1);
}
if (item instanceof ConditionalExpressionNode)
{
ConditionalExpressionNode cen = (ConditionalExpressionNode)item;
if (cen.thenexpr instanceof CoerceNode) // it always will be, but just to make sure
{
CoerceNode cn = (CoerceNode)cen.thenexpr;
checkReturnStatementType(cx, cn.expr, returnType);
}
else
checkReturnStatementType(cx, cen.thenexpr, returnType);
if (cen.elseexpr instanceof CoerceNode) // it always will be, but just to make sure
{
CoerceNode cn = (CoerceNode)cen.elseexpr;
checkReturnStatementType(cx, cn.expr, returnType);
}
else
checkReturnStatementType(cx, cen.elseexpr, returnType);
}
else
{
checkReturnStatementType(cx, item, returnType);
}
}
}
return null;
}
// Definitions
public Value evaluate( Context cx, VariableDefinitionNode node )
{
// C: skip the variable definition. don't lint this branch.
if (node.skip()) return cx.noType();
Context nodeContext = node.getContext();
Context useCx = (nodeContext != null) ? nodeContext : cx;
return node.list.evaluate(useCx,this);
}
public Value evaluate( Context cx, VariableBindingNode node )
{
//TypeValue dt = node.ref.getType(cx); // getDerivedType(cx);
if (!first_pass)
{
Slot s = (node.ref != null) ? node.ref.getSlot(cx,GET_TOKEN) : null;
if (s != null)
{
// warn for duplicate definitions. This is legal due to variable hoisting, but is often not what people expect, especially those
// used to block scoping in c++ or java. It can lean to unexpected code such as this:
// var x = 20;
// if (someCondition())
// {
// for (var x= 0; x < 10; x++)
// doSometing(x);
// }
// print(x); // 10, not 20.
// Look up the slot for the ref. If its the first time we've seen it, mark the slot as "declared". If the slot is already
// marked as declared, this is a duplicate definition.
if (slot_isAlreadyDeclared(s))
{
String origDef = "";
//String origDef = "on line " + cx.input.getLnNum(slot_getOriginalDeclarationPosition(s));
// commented out since the line number is correct for the .as file, but is not correct if we are dealing
// with a .as file generated by flex from an mxml file. Since there is no easy way to map this correctly
// at this point just remove the line number info. We can work on getting the mapping correct in a later release.
warning(node.getPosition(), cx.input, kWarning_DuplicateVariableDef, origDef);
}
else
{
slot_markAsDeclared(s, node.getPosition());
}
}
if (node.initializer == null)
{
if (s != null && s.isConst())
warning(node.getPosition(), cx.input, kWarning_ConstNotInitialized);
}
else if (!doing_method)
{
if( cx.statics.es4_nullability && cx.scope().builder instanceof InstanceBuilder )
{
cx.scope().setInitOnly(true);
}
node.initializer.evaluate(cx,this);
if( cx.statics.es4_nullability && cx.scope().builder instanceof InstanceBuilder )
{
cx.scope().setInitOnly(false);
}
}
if (node.typeref == null && node.ref != null && node.variable.no_anno)
warning(node.getPosition(),cx.input, kWarning_NoTypeDecl, "variable", node.ref.name);
Slot type_slot = null;
if( node.typeref != null && (type_slot = node.typeref.getSlot(cx)) != null )
{
checkDeprecatedSlot(cx, node.variable.type, node.typeref, type_slot);
}
if( node.ref != null )
{
Namespaces open_namespaces = this.open_namespaces.last();
for( int i = 0; i < open_namespaces.size(); ++i )
{
ObjectValue namespace = open_namespaces.at(i);
if( namespace.isPackage() && node.ref.name.equals(namespace.name) )
warning(node.getPosition(), cx.input, kWarning_DefinitionShadowedByPackageName);
}
}
// check for access specifier
int scopes = cx.getScopes().size();
if (scopes == 1 || (cx.scope(scopes-1).builder instanceof ActivationBuilder))
return null; // global scope or local variable, no access modifiers allowed
/*
if (!(cx.scope(scopes-1).builder instanceof ClassBuilder || (cx.scope(scopes-1).builder instanceof InstanceBuilder)))
return null;
*/
if (node.attrs != null)
{
AttributeListNode attrs = node.attrs;
if (attrs.hasUserNamespace() || attrs.hasPrivate || attrs.hasPublic ||
attrs.hasProtected || attrs.hasInternal)
{
return null;
}
}
// Warn about missing access modifier. Get the name of the parent namespace
String ns = cx.scope(scopes-2).getPrintableName() + ":";
warning(node.getPosition(), cx.input, kWarning_MissingNamespaceDecl, "var '" + node.ref.name + "'", ns);
}
return null;
}
public Value evaluate( Context cx, BinaryFunctionDefinitionNode node )
{
return cx.voidType();
}
public Value evaluate( Context unused_cx, FunctionDefinitionNode node )
{
Context cx = node.cx; // switch context to the one used to parse this node, for error reporting
// C: skip the entire branch. don't lint this definition.
if (node.skip()) return cx.functionType();
ObjectValue scope = cx.scope();
boolean is_interface = (scope.type != null && scope.type.isInterface());
if( !first_pass && node.ref != null && !is_interface)
{
// check for access qualifiers
int scopes = cx.getScopes().size();
if (scopes == 1 || (cx.scope(scopes-1).builder instanceof ActivationBuilder)) // no access qualifiers allowed in global scope
return cx.functionType();
if (node.attrs != null)
{
AttributeListNode attrs = node.attrs;
if (attrs.hasUserNamespace() || attrs.hasPrivate || attrs.hasPublic ||
attrs.hasProtected || attrs.hasInternal)
{
return cx.functionType();
}
}
// constructors can only be public. If no access specifier is supplied, it defaults to public. Do not warn about it.
boolean is_constructor = "$construct".equals(node.ref.name);
if (is_constructor)
return cx.functionType();
// Warn about missing qualifier. Get the name of the parent namespace (if any)
String ns = cx.scope(scopes-2).getPrintableName() + ":";
warning(node.getPosition(), cx.input, kWarning_MissingNamespaceDecl, "function '" + node.ref.name + "'", ns);
}
return cx.functionType();
}
public Value evaluate( Context unused_cx, FunctionCommonNode node )
{
Context cx = node.cx; // switch to original context
if( doing_method )
{
return cx.functionType();
}
open_namespaces.push_back(node.used_namespaces);
if (!first_pass && node.ref != null)
{
Map<TypeValue,Integer> search = unsupportedEventsMap.get(node.ref.name);
if (search != null && ! search.isEmpty()) // it matches a former auto-registered event handler name
{
Slot s = node.ref.getSlot(cx,GET_TOKEN);
ObjectValue scope = cx.scope();
TypeValue baseType = (scope != null) ? scope.type.getTypeValue() : null;
if (baseType != null && s != null && !slot_GetRegisteredForEvent(s)) // it wasn't seen in an addEventListener call during first_pass
{
for(TypeValue type : search.keySet())
{
if (type != null && type.includes(cx,baseType)) // it's defining Type matches one of the warning cases
{
int pos = (node.identifier != null) ? node.identifier.getPosition() : node.getPosition();
warning(pos, cx.input, kWarning_DepricatedEventHandlerError, warningConstantsMap.get(search.get(type)));
}
}
}
else if (s != null)
{
s = null;
}
}
}
ObjectValue fun = node.fun;
cx.pushScope(fun.activation);
if( node.isFunctionDefinition() == false ) // if it not a defn then 'this' can be used and is dynamic (i.e. don't warn about undeclared props/methods)
{
this_contexts.add(global_this);
}
for (int i = 0, size = node.fexprs.size(); i < size; i++)
{
FunctionCommonNode item = node.fexprs.get(i);
item.evaluate(cx,this);
}
currentFunctionRef.add(node.ref);
// set local expected_returnType so that we can check it against what's actually returned in ReturnStatementNode
boolean is_constructor = "$construct".equals(node.ref.name);
if (is_constructor)
{
this.expected_returnType = cx.voidType();
}
else
{
this.expected_returnType = node.signature.type!=null?node.signature.type.getTypeValue():cx.noType();
}
body_has_return = false;
if( node.body != null )
{
node.body.evaluate(cx,this);
doing_method = false;
}
this.expected_returnType = cx.noType(); // restore to default
node.signature.evaluate(cx,this);
currentFunctionRef.removeLast();
cx.popScope(); // pop activation. must do this before looking up slot for node.ref, else we can
// bind to the wrong definition in the activation record instead.
Slot s = node.ref.getSlot(cx,GET_TOKEN);
if (first_pass && s != null)
{
if (body_has_return)
slot_setHasReturnValue(s,true);
}
else if (!first_pass)
{
if (node.signature != null && node.signature.parameter != null)
{
HashMap<String,Boolean> paramNames = new HashMap<String,Boolean>(node.signature.parameter.items.size());
for(ParameterNode item : node.signature.parameter.items)
{
Boolean exists = paramNames.get(item.ref.name);
if (exists != null)
{
warning(item.pos(),cx.input,kWarning_DuplicateArgumentNames,item.ref.name);
}
else
{
paramNames.put(item.ref.name,true);
}
}
}
}
open_namespaces.pop_back();
if( node.isFunctionDefinition() == false )
{
this_contexts.removeLast();
}
return cx.functionType();
}
public Value evaluate( Context cx, FunctionNameNode node )
{
assert(false);
return null; // assert(false); // "Should never get here!";
}
public Value evaluate( Context cx, FunctionSignatureNode node )
{
ReferenceValue ref = currentFunctionRef.last();
Slot s = null;
if (ref != null)
{ // must look up the functionRef in the old scope, not the activationObject scope. Don't want to bind
// to like-named arguments!
ObjectValue oldScope = cx.scope();
cx.popScope();
s = ref.getSlot(cx,GET_TOKEN);
cx.pushScope(oldScope);
}
if( node.parameter != null)
{
node.parameter.evaluate(cx,this);
}
if( node.result != null)
{
node.result.evaluate(cx,this);
}
else
{
if (!first_pass && ref != null && ref.name.compareTo("$construct") != 0 && node.no_anno)
{
warning(node.getPosition()-2, cx.input, kWarning_NoTypeDecl, "return value for function", ref.name);
}
}
return null;
}
public Value evaluate( Context cx, ParameterNode node )
{
if (!first_pass && node.ref != null)
{
if (node.type == null && node.no_anno)
warning(node.getPosition(),cx.input, kWarning_NoTypeDecl, "parameter", node.ref.name);
}
return null;
}
public Value evaluate( Context cx, RestParameterNode node )
{
//return evaluate(cx,(ParameterNode*)node);
return null;
}
public Value evaluate(Context cx, RestExpressionNode node)
{
return null;
}
public Value evaluate( Context cx, ParameterListNode node )
{
for(ParameterNode item : node.items)
{
item.evaluate(cx,this);
}
return null;
}
public Value evaluate( Context cx, BinaryProgramNode node )
{
return cx.voidType();
}
public Value evaluate(Context cx, BinaryClassDefNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, BinaryInterfaceDefinitionNode node)
{
return cx.voidType();
}
public Value evaluate( Context unused_cx, ProgramNode node )
{
Context cx = node.cx; // switch contexts so that the original one is used
if ((cx.input == null) || ignorableFile(cx.input))
{
return null;
}
if(initialized == false)
initialize(cx); // initialize tables and maps
// Unlike the other evaluators, we evaluate the ProgramNode twice
// In the first walk (first_pass = true), we store lintData in the slot's opaque
// embeddedData for identifiers we need to remember information for
// (was a handler registered for an event, does it have a return value,
// what is a top level function's argument signature)
// During the second traversal we look for problems to warn against.
for( int numPass = 0; numPass < 2; numPass++)
{
first_pass = numPass == 0;
this_contexts.add(global_this);
doing_class = false;
doing_method = false;
open_namespaces.push_back(node.used_def_namespaces);
if (node.pkgdefs != null)
{
for (PackageDefinitionNode def : node.pkgdefs)
{
def.evaluate(cx, this);
}
}
if (node.clsdefs != null)
{
for (ClassDefinitionNode def : node.clsdefs)
{
def.evaluate(cx, this);
}
}
if (node.fexprs != null)
{
for (FunctionCommonNode def : node.fexprs)
{
def.evaluate(cx, this);
}
}
if( node.statements != null)
{
node.statements.evaluate(cx,this);
}
doing_method = false;
doing_class = false;
this_contexts.removeLast();
open_namespaces.pop_back();
}
return null;
}
public Value evaluate( Context unused_cx, PackageDefinitionNode node )
{
if( !node.in_this_pkg )
{
node.in_this_pkg = true;
open_namespaces.push_back(node.used_namespaces);
}
else
{
node.in_this_pkg = false;
open_namespaces.pop_back();
}
/*
if( doing_package )
{
return null;
}
Context cx = node.cx; // switch to original context
if( node.statements != null )
{
node.statements.evaluate(cx,this);
}
doing_method = false;
*/
return null;
}
public Value evaluate( Context cx, ErrorNode node )
{
return null;
}
public Value evaluate( Context cx, ToObjectNode node )
{
node.expr.evaluate(cx,this);
return cx.noType();
}
public Value evaluate( Context cx, BoxNode node )
{
Value result = node.expr.evaluate(cx,this);
if( node.void_result )
{
result = cx.voidType();
}
else
{
int type_id = node.actual.getTypeId();
switch( type_id )
{
case TYPE_bool:
case TYPE_int:
result = (node.actual);
}
}
return result;
}
public Value evaluate( Context cx, CoerceNode node )
{
// CoerceNodes are introduced by finalantEvaluator, using the type rather than the dtype.
// Because finalantEvaluator uses ObjectType for everything not explicitly typed (and even then sometimes)
// we ignore any cast to ObjectType
Value result = node.expr.evaluate(cx,this);
if (node.expected.getTypeValue() == cx.noType() || result == undefinedLiteral)
return result; // likewise, actual is type not dtype
if (result == cx.functionType() && node.expected.getTypeValue() != cx.functionType())
{
if (node.expr instanceof MemberExpressionNode)
{
MemberExpressionNode memb = (MemberExpressionNode)(node.expr);
String funcName = memb.ref.name;
warning(memb.pos(), cx.input, kWarning_UnlikelyFunctionValue, node.expected.getName(cx).toString(), funcName);
}
}
return result;
}
public Value evaluate( Context cx, LoadRegisterNode node )
{
return (node.type == null ? cx.noType() : node.type);
}
public Value evaluate( Context cx, StoreRegisterNode node )
{
node.expr.evaluate(cx,this);
return (node.type == null ? cx.noType() : node.type);
}
public Value evaluate( Context cx, RegisterNode node )
{
return (node.type == null ? cx.noType() : node.type);
}
public Value evaluate( Context cx, HasNextNode node )
{
return cx.noType();
}
public Value evaluate( Context cx, ThrowStatementNode node )
{
if( node.expr != null )
{
return node.expr.evaluate(cx,this);
}
return null;
}
public Value evaluate( Context cx, TryStatementNode node )
{
if (node.tryblock != null)
{
node.tryblock.evaluate(cx, this);
}
if (node.catchlist != null)
{
node.catchlist.evaluate(cx, this);
}
return null;
}
public Value evaluate( Context cx, CatchClauseNode node )
{
if (node.parameter != null)
{
node.parameter.evaluate(cx,this);
}
if (node.statements != null)
{
node.statements.evaluate(cx, this);
}
return null;
}
public Value evaluate( Context cx, FinallyClauseNode node )
{
return null; // its not implemented in the compiler yet
}
public Value evaluate( Context unused_cx, ClassDefinitionNode node )
{
Context cx = node.cx; // switch to original context
// C: if node.skip() is true, skip the entire branch. don't lint this definition.
if( doing_method || doing_class || node.skip())
{
return node.cframe;
}
open_namespaces.push_back(node.used_namespaces);
doing_class = true;
if( node.attrs != null)
{
node.attrs.evaluate(cx,this);
}
if( node.name != null)
{
node.name.evaluate(cx,this);
}
if( node.baseclass != null)
{
node.baseclass.evaluate(cx,this);
}
if( node.interfaces != null)
{
node.interfaces.evaluate(cx,this);
}
cx.pushStaticClassScopes(node);
this_contexts.add(error_this);
if (node.staticfexprs != null)
{
for(FunctionCommonNode item : node.staticfexprs)
{
doing_method = false;
item.evaluate(cx,this);
}
}
if (node.clsdefs != null)
{
for (Node clsdef : node.clsdefs)
{
clsdef.evaluate(cx, this);
}
}
if( node.statements != null)
{
node.statements.evaluate(cx,this);
}
doing_method = false;
this_contexts.removeLast();
this_contexts.add(instance_this);
cx.pushScope(node.iframe);
// Generate code for the instance property definitions
boolean has_instance_vars = false;
if (node.instanceinits != null)
{
doing_method = true;
for(Node item : node.instanceinits)
{
if( cx.statics.es4_nullability && !item.isDefinition() )
node.iframe.setInitOnly(true);
item.evaluate(cx,this);
if (item instanceof VariableDefinitionNode ) /* cn: only warn if there are instance vars. That's the most likely time there could ever be
a difference in behavior when a class is renamed and its constructor function isn't.
|| item instanceof FunctionDefinitionNode) */
{
has_instance_vars = true;
}
if( cx.statics.es4_nullability && !item.isDefinition() )
node.iframe.setInitOnly(false);
}
doing_method = false;
}
if (node.fexprs != null)
{
boolean found_constructor = false;
boolean has_instance_methods = false;
for(FunctionCommonNode fn : node.fexprs)
{
if (fn.ref.name.equals("$construct"))
{
if (!fn.isSynthetic())
{
found_constructor = true;
body_has_super= false;
}
}
else
{
has_instance_methods = true;
}
fn.evaluate(cx,this);
if (fn.ref.name.equals("$construct") && !node.isInterface() && !fn.isSynthetic() && !body_has_super)
{
warning(fn.pos(), cx.input, kWarning_NoExplicitSuperCallInConstructor, node.ref.name);
body_has_super = false;
}
}
// if we haven't found a constructor AND there is either more than one (i.e. the default constructor) function defined,
// or there are instanceinits (for instance vars), then log warning. We don't want to log a warning for the case where
// a class exists soley to provide static properties/methods.
// todo: Future: perhaps we should only trigger this warning if there are instance variables which do not have a iinit default value.
if ( !found_constructor &&
has_instance_vars &&
!node.isInterface())
warning(node.pos(), cx.input, kWarning_NoConstructor, node.ref.name);
}
doing_method = false;
doing_class = false;
this_contexts.removeLast();
cx.popScope();
cx.popStaticClassScopes(node);
open_namespaces.pop_back();
if( !first_pass && node.ref != null && node.pkgdef != null )
{
// check for access qualifier
int scopes = cx.getScopes().size();
if (node.attrs != null)
{
AttributeListNode attrs = node.attrs;
if (attrs.hasUserNamespace() || attrs.hasPrivate || attrs.hasPublic ||
attrs.hasProtected || attrs.hasInternal)
{
return node.cframe;
}
}
// Warn about missing qualifier. Get the name of the parent namespace (if any)
String ns = node.cframe.name.ns.name;
warning(node.getPosition(), cx.input, kWarning_MissingNamespaceDecl, "class '" + node.ref.name + "'", ns);
}
return node.cframe;
}
public Value evaluate( Context cx, InterfaceDefinitionNode node )
{
if( doing_method || doing_class )
return null;
ObjectValue implobj = cx.scope();
if( !(implobj.builder instanceof InstanceBuilder) )
{
return this.evaluate(cx, (ClassDefinitionNode)node);
}
return null;
}
public Value evaluate( Context cx, ClassNameNode node )
{
if( node.pkgname != null )
{
node.pkgname.evaluate(cx,this);
}
if( node.ident != null )
{
node.ident.evaluate(cx,this);
}
return ObjectValue.undefinedValue;
}
public Value evaluate( Context cx, InheritanceNode node )
{
if( node.baseclass != null)
{
node.baseclass.evaluate(cx,this);
}
if( node.interfaces != null )
{
node.interfaces.evaluate(cx,this);
}
return ObjectValue.undefinedValue;
}
public Value evaluate( Context cx, AttributeListNode node )
{
for(Node item : node.items)
{
item.evaluate(cx,this);
}
return ObjectValue.undefinedValue;
}
public Value evaluate( Context cx, IncludeDirectiveNode node )
{
if( !node.in_this_include )
{
node.in_this_include = true;
node.prev_cx = new Context(cx.statics);
node.prev_cx.switchToContext(cx);
// DANGER: it may not be obvious that we are setting the
// the context of the outer statementlistnode here
cx.switchToContext(node.cx);
}
else
{
node.in_this_include = false;
cx.switchToContext(node.prev_cx); // restore prevailing context
node.prev_cx = null;
}
return null;
}
private ObjectList< Namespaces > open_namespaces = new ObjectList< Namespaces >();
public Value evaluate( Context cx, ImportDirectiveNode node )
{
return null;
// node.name.id.toIdentifierString()
}
public Value evaluate( Context cx, SuperExpressionNode node )
{
TypeValue super_type = cx.noType();
TypeValue this_value = null;
// All error cases handled by flow analyzer
if( node.expr != null )
{
Value result = node.expr.evaluate(cx,this);
this_value = (result instanceof TypeValue) ? (TypeValue) result : null;
}
else
{
int scope_depth = cx.getScopes().size()-1;
Value result = cx.scope(scope_depth-1);
this_value = (result instanceof TypeValue) ? (TypeValue) result : null; // If this is an instance method, scope is second from top
}
if( this_value != null && this_value.baseclass != null )
{
super_type = this_value.baseclass;
}
return super_type;
}
public Value evaluate( Context cx, SuperStatementNode node )
{
body_has_super = true; // remember that constructor called super. Previous compiler error checking gaurantees we must be in a constructor
if( node.call.args != null)
{
node.call.args.evaluate(cx,this);
}
return null;
}
public Value evaluate( Context cx, ConfigNamespaceDefinitionNode node )
{
return null;
}
public Value evaluate( Context cx, NamespaceDefinitionNode node )
{
if( !first_pass && node.ref != null )
{
// check for access qualifier
int scopes = cx.getScopes().size();
/*
if (scopes == 1 || (cx.scope(scopes-1).builder instanceof ActivationBuilder)) // global scope, no access modifiers allowed
return null;
*/
if (scopes != 1 || cx.scope(scopes-1).builder instanceof PackageBuilder )
return null;
if (node.attrs != null)
{
AttributeListNode attrs = node.attrs;
if (attrs.hasUserNamespace() || attrs.hasPrivate || attrs.hasPublic ||
attrs.hasProtected || attrs.hasInternal)
{
return null;
}
}
// Warn about missing qualifier. Get the name of the parent namespace (if any)
String ns = cx.scope(scopes-2).getPrintableName() + ":";
warning(node.getPosition(), cx.input, kWarning_MissingNamespaceDecl, "namespace '" + node.ref.name + "'", ns);
}
return null;
}
public Value evaluate( Context cx, UseDirectiveNode node )
{
return null;
}
public Value evaluate( Context cx, MetaDataNode node )
{
return null;
}
public Value evaluate( Context cx, EmptyElementNode node )
{
// do nothing
return null;
}
// Supporting tables and maps for identifying unsupported AS2 Flash player apis
// -------------------------------------------------------------------
TypeValue[] types = new TypeValue[kNumDefaultTypes]; // table of TypeValues for common types we need to compare against
private class DefaultTypeDesc
{
int code; // TypeCode code;
String nameSpace;
String name;
DefaultTypeDesc(int c, String ns, String n) { code = c; nameSpace = ns; name = n; }
};
private DefaultTypeDesc[] typeDescriptors = {
new DefaultTypeDesc(kVoidType, "", "null"),
new DefaultTypeDesc(kObjectType, "", "Object"),
new DefaultTypeDesc(kFunctionType, "", "Function"),
new DefaultTypeDesc(kStringType, "", "String"),
new DefaultTypeDesc(kNumberType, "", "Number"),
new DefaultTypeDesc(kBooleanType, "", "Boolean"),
new DefaultTypeDesc(kArrayType, "", "Array"),
new DefaultTypeDesc(kDateType, "", "Date"),
new DefaultTypeDesc(kMathType, "", "Math"),
new DefaultTypeDesc(kErrorType, "", "Error"),
new DefaultTypeDesc(kRegExpType, "", "RegExp"),
new DefaultTypeDesc(kDisplayObjectType, "flash.display", "DisplayObject"),
new DefaultTypeDesc(kMovieClipType, "flash.display", "MovieClip"),
new DefaultTypeDesc(kTextFieldType, "flash.text", "TextField"),
new DefaultTypeDesc(kTextFormatType, "flash.text", "TextFormat"),
new DefaultTypeDesc(kMicrophoneType, "flash.media", "Microphone"),
new DefaultTypeDesc(kSimpleButtonType, "flash.display", "SimpleButton"),
new DefaultTypeDesc(kVideoType, "flash.media", "Video"),
new DefaultTypeDesc(kStyleSheetType, "flash.text", "StyleSheet"),
new DefaultTypeDesc(kSelectionType, "flash.obsolete", "SelectionType"), // not found
new DefaultTypeDesc(kColorType, "flash.obsolete", "Color"), // not found
new DefaultTypeDesc(kStageType, "flash.display", "Stage"),
new DefaultTypeDesc(kMouseType, "flash.ui", "Mouse"),
new DefaultTypeDesc(kKeyboardType, "flash.ui", "Keyboard"),
new DefaultTypeDesc(kSoundType, "flash.media", "Sound"),
new DefaultTypeDesc(kSystemType, "flash.system", "System"),
new DefaultTypeDesc(kXMLType, "", "XML"),
new DefaultTypeDesc(kXMLSocketType, "flash.net", "XMLSocket"),
new DefaultTypeDesc(kXMLListType, "", "XMLList"),
new DefaultTypeDesc(kQNameType, "", "QName"),
new DefaultTypeDesc(kLoadVarsType, "flash.net", "LoadVars"),
new DefaultTypeDesc(kCameraType, "flash.media", "Camera"),
new DefaultTypeDesc(kContextMenuType, "flash.ui", "ContextMenu"),
new DefaultTypeDesc(kContextMenuItemType, "flash.ui", "ContextMenuItem"),
new DefaultTypeDesc(kMovieClipLoaderType, "flash.obsolete", "MovieClipLoader"), // not found
new DefaultTypeDesc(kNetStreamType, "flash.net", "NetStream"),
new DefaultTypeDesc(kAccessibilityType, "flash.accessibility", "Accessibility"),
new DefaultTypeDesc(kActivityEventType, "flash.events", "ActivityEvent"),
new DefaultTypeDesc(kByteArrayType, "flash.util", "ByteArray"),
new DefaultTypeDesc(kColorTransformType, "flash.geom", "ColorTransform"),
new DefaultTypeDesc(kDisplayObjectContainerType, "flash.display", "DisplayObjectContainer"),
new DefaultTypeDesc(kCustomActionsType, "macromedia.util", "CustomActions"),
new DefaultTypeDesc(kDataEventType, "flash.events", "DataEvent"),
new DefaultTypeDesc(kExternalInterfaceType, "flash.external", "ExternalInterface"),
new DefaultTypeDesc(kErrorEventType, "flash.events", "ErrorEvent"),
new DefaultTypeDesc(kEventType, "flash.events", "Event"),
new DefaultTypeDesc(kFocusEventType, "flash.events", "FocusEvent"),
new DefaultTypeDesc(kGraphicsType, "flash.display", "Graphics"),
new DefaultTypeDesc(kBitmapFilterType, "flash.filters", "BitmapFilter"),
new DefaultTypeDesc(kInteractiveObjectType, "flash.display", "InteractiveObject"),
new DefaultTypeDesc(kKeyboardEventType, "flash.events", "KeyboardEvent"),
new DefaultTypeDesc(kLoaderType, "flash.display", "Loader"),
new DefaultTypeDesc(kLoaderInfoType, "flash.display", "LoaderInfo"),
new DefaultTypeDesc(kLocalConnectionType, "flash.net", "LocalConnection"),
new DefaultTypeDesc(kContextMenuEventType, "flash.events", "ContextMenuEvent"),
new DefaultTypeDesc(kProductManagerType, "macromedia.util", "ProductManager"),
new DefaultTypeDesc(kPointType, "flash.geom", "Point"),
new DefaultTypeDesc(kProxyType, "flash.util", "Proxy"),
new DefaultTypeDesc(kProfilerType, "flash.profiler", ""),
new DefaultTypeDesc(kProgressEventType, "flash.events", "ProgressEvent"),
new DefaultTypeDesc(kRectangleType, "flash.geom", "Rectangle"),
new DefaultTypeDesc(kSoundTransformType, "flash.media", "SoundTransform"),
new DefaultTypeDesc(kSocketType, "flash.net", "Socket"),
new DefaultTypeDesc(kSharedObjectType, "flash.net", "SharedObject"),
new DefaultTypeDesc(kSpriteType, "flash.display", "Sprite"),
new DefaultTypeDesc(kIMEType, "flash.system", "IME"),
new DefaultTypeDesc(kSWFLoaderInfoType, "flash.display", "SWFLoaderInfo"),
new DefaultTypeDesc(kTextSnapshotType, "flash.text", "TextSnapshot"),
new DefaultTypeDesc(kURLLoaderType, "flash.net", "URLLoader"),
new DefaultTypeDesc(kURLStreamType, "flash.net", "URLStream"),
new DefaultTypeDesc(kURLRequestType, "flash.net", "URLRequest"),
new DefaultTypeDesc(kXMLDocumentType, "flash.xml", "XMLDocument"),
new DefaultTypeDesc(kXMLNodeType, "flash.xml", "XMLNode"),
new DefaultTypeDesc(kNetConnectionType, "flash.net", "NetConnection"),
new DefaultTypeDesc(kSyncEventType, "flash.events", "SyncEvent"),
new DefaultTypeDesc(kBitmapDataType, "flash.display", "BitmapData"),
new DefaultTypeDesc(kXMLUIType, "macromedia.util", "XMLUI"),
new DefaultTypeDesc(kFileReferenceListType, "flash.net", "FileReferenceList"),
new DefaultTypeDesc(kFileReferenceType, "flash.net", "FileReference")
};
// simple versions of the the names for the above types (not including namespace decoration)
static final String[] simpleTypeNames =
{
"null",
"Object",
"Function",
"String",
"Number",
"Boolean",
"Array",
"Date",
"Math",
"Error",
"RegExp",
"DisplayObject",
"MovieClip",
"TextField",
"TextFormat",
"Microphone",
"SimpleButton",
"Video",
"StyleSheet",
"Selection",
"Color",
"Stage",
"Mouse",
"Keyboard",
"Sound",
"System",
"XML",
"XMLSocket",
"XMLList",
"QName",
"LoadVars",
"Camera",
"ContextMenu",
"ContextMenuItem",
"MovieClipLoader",
"NetStream",
"Accessibility",
"ActivityEvent",
"ByteArray",
"ColorTransform",
"DisplayObjectContainer",
"CustomActions",
"DataEvent",
"ExternalInterface",
"ErrorEvent",
"Event",
"FocusEvent",
"Graphics",
"BitmapFilter",
"InteractiveObject",
"KeyboardEvent",
"Loader",
"LoaderInfo",
"LocalConnection",
"ContextMenuEvent",
"ProductManager",
"Point",
"Proxy",
"Profiler",
"ProgressEvent",
"Rectangle",
"SoundTransform",
"Socket",
"SharedObject",
"Sprite",
"IME",
"SWFLoaderInfo",
"TextSnapshot",
"URLLoader",
"URLStream",
"URLRequest",
"XMLDocument",
"XMLNode"
};
private Map<Integer, String> warningConstantsMap = new HashMap<Integer, String>(kNumWarningConstants); // maps WarningCode enum to its warning string
// maps a property name and base type pair to a WarningCode for unsupported properties
private Map<String, Map<TypeValue, Integer>> unsupportedPropsMap = new HashMap<String, Map<TypeValue, Integer>>(kNumPropertyWarnings);
// maps a property name and base type pair to a WarningCode for unsupported methods
private Map<String, Map<TypeValue, Integer>> unsupportedMethodsMap = new HashMap<String, Map<TypeValue, Integer>>(kNumMethodWarnings);
// maps a property name and base type pair to a WarningCode for an event handler which is no longer called automatically by the player
private Map<String, Map<TypeValue, Integer>> unsupportedEventsMap = new HashMap<String, Map<TypeValue, Integer>>(kNumEventWarnings);
/* Hack to ignore warnings for things which should be in playerglobal.as
but aren't yet (usually because there are some problems with its current definition) (like 'Stage') */
private static final Map<String, Boolean> hackIgnoreIdentifierMap = new HashMap<String, Boolean>();
// TODO remove this once playerGlobal.as defines these
private static final String toIgnore[] = {
"_global",
"rest",
"NaN",
"arguments",
"undefined"
};
private Map<Integer, Boolean> enabledMap; // indicates if a warning(Code) is enabled
private String warningEnabledMapFile; // custom location for EnabledWarning.xml
// supporting tables for logging warning instances
// ----------------------------------------------
// Table of warningRecord vectors indexable via WarningCode
private TreeMap<Integer, ObjectList<WarningRecord>> pWarnings = new TreeMap<Integer, ObjectList<WarningRecord>>();
// Table of warningRecord vectors indexed by source file and line
private TreeMap<String, TreeMap<Integer, Set<WarningRecord>>> warningsByLoc = new TreeMap<String, TreeMap<Integer, Set<WarningRecord>>>();
// This is where WarningRecords are logged when encountered,
// and how we dump them out by category at the end of the evaluation
// General data members
// ----------------------
private boolean initialized; // marks that we have initialized the above.
private String scriptName; // name of the source file.
private ObjectValue undefinedLiteral; // we need to be able to identify the literal 'undefined'
private boolean body_has_return; // need to know if a function 'f' has a return value in order to spot "new f()" as a problem.
private boolean body_has_super; // need to know if a constructor function calls super() to initialize its super class
private boolean output_to_file; // defaults to true, whether to create _warnings.txt file
// state trackers, used to avoid evaluating methods/classes twice.
private boolean doing_method;
private boolean doing_class;
private boolean first_pass;
private TypeValue expected_returnType;
// keeps track of the type of the currently evaluating member expressions base
public ObjectList<TypeValue> baseType_context = new ObjectList<TypeValue>();
// keeps track of the referenceValue for the currently evaluating member expression's base
// (if it is a simple MemberExpression/GetExpression of an identifier, its ref is pushed,
// else, NULL is pushed).
public ObjectList<ReferenceValue> baseRef_context = new ObjectList<ReferenceValue>();
// keeps track of what 'this' means in the currently evaluating expression
private IntList this_contexts = new IntList();
//std::vector<int> super_context; //
// Keeps track of the function we are currently evaluating
public ObjectList<ReferenceValue> currentFunctionRef = new ObjectList<ReferenceValue>();
private boolean in_with;
public LintEvaluator( Context cx, String name, String enabledFile )
{
scriptName = name;
// ctx = cx;
initialized = false;
body_has_return = false;
body_has_super = false;
warningEnabledMapFile = enabledFile;
doing_class = false;
doing_method = false;
output_to_file = true;
expected_returnType = cx.noType();
in_with = false;
}
/* This constructor takes in a pre-filled-in warnings map -- you can get it using the
* static method parseEnabledWarningsFile(file). This avoids the overhead of reading
* in the same file hundreds of times in mxmlc. */
public LintEvaluator( Context cx, String name, HashMap<Integer, Boolean> enabledWarningsMap)
{
scriptName = name;
initialized = false;
body_has_return = false;
body_has_super = false;
enabledMap = enabledWarningsMap;
doing_class = false;
doing_method = false;
output_to_file = true;
expected_returnType = cx.noType();
}
public void clear()
{
initialized = false;
unsupportedPropsMap.clear();
unsupportedMethodsMap.clear();
unsupportedEventsMap.clear();
pWarnings.clear();
warningsByLoc.clear();
undefinedLiteral = null;
for (Slot s : slotsToClean)
{
clear_lintData(s);
}
}
/**
* Log warnings with little formatting to the warning or error stream
* FLEX hooks into this method, nothing in ASC uses it.
*/
public int simpleLogWarnings(Context cx, boolean logAsErrors)
{
CompilerHandler handler = cx.getHandler();
if(handler == null) {
handler = cx.statics.handler;
}
int count = 0;
for (TreeMap<Integer, Set<WarningRecord>> locMap : warningsByLoc.values())
{
for(Set<WarningRecord> records : locMap.values())
{
for (WarningRecord record : records)
{
StringBuilder sb = new StringBuilder();
InputBuffer input = record.loc.input;
createErrorMessage(record, sb, record.code);
String source = input.getLineText(record.loc.pos);
if (logAsErrors)
{
handler.error(input.origin, record.lineNum, record.colNum, sb.toString(), source, record.code);
}
else
{
// This is the path the Flex compiler usually takes
handler.warning(input.origin, record.lineNum, record.colNum, sb.toString(), source, record.code);
}
count++;
}
}
}
return count;
}
/**
* Log formatted warnings to scriptname_warnings.txt and warning stream
*/
public void logWarnings(Context cx)
{
if(ContextStatics.useSanityStyleErrors || ContextStatics.useSimpleLogWarnings)
{
simpleLogWarnings(cx, false);
}
else
{
StringBuilder out = new StringBuilder();
out.append(newline).append("Warning Report:").append(newline);
out.append("---------------").append(newline).append(newline);
for (Integer code : pWarnings.keySet())
{
ObjectList<WarningRecord> warnings = pWarnings.get(code);
out.append("[Coach] Warning #").append(code).append(": ").append(warningConstantsMap.get(code)).append(newline);
out.append("-------------------------------------------------------------------------").append(newline);
for( WarningRecord pRec : warnings) {
createWarning(pRec, out, code);
out.append(newline);
}
out.append("-------------------------------------------------------------------------").append(newline).append(newline);
}
if (pWarnings.keySet().size() > 0)
{
// print the message
if(cx.getHandler() != null) {
cx.getHandler().warning("",-1,-1,out.toString(),"");
} else {
System.err.println(out.toString());
}
// Output to a log file
if(output_to_file) {
BufferedOutputStream warningOut = null;
try {
int dotPos = scriptName.indexOf('.');
if (dotPos == -1) dotPos = scriptName.length();
String outName = scriptName.substring(0,dotPos) + "_warnings.txt";
warningOut = new BufferedOutputStream(new FileOutputStream(new File(outName)));
warningOut.write(out.toString().getBytes());
warningOut.flush();
}
catch (IOException ex) { ex.printStackTrace(); }
finally {
if (warningOut != null) {
try { warningOut.close(); }
catch (IOException ex) {}
}
}
}
}
}
}
private void createWarning(WarningRecord pRec, StringBuilder out, Integer code)
{
InputBuffer input = pRec.loc.input;
out.append(" ").append(input.origin).append("(").append(pRec.lineNum).append("): ");
createErrorMessage(pRec, out, code);
out.append(newline);
out.append(" ").append(input.getLineText(pRec.loc.pos)).append(newline);
out.append(" ").append(InputBuffer.getLinePointer(pRec.colNum)).append(newline);
}
private void createErrorMessage(WarningRecord pRec, StringBuilder out, Integer code)
{
// Just the arguments for sanities, no message (since they chang often)
if(ContextStatics.useSanityStyleErrors) {
out.append("code=" + code + "; arg1=" + pRec.errStringArg1 + "; arg2=" + pRec.errStringArg2 + "; arg3=" + pRec.errStringArg3);
}
// else: standard message
else
{
String templateStr = warningConstantsMap.get(code+1);
// Replace all %s with args
int nextLoc = Context.replaceStringArg(out,templateStr,0,pRec.errStringArg1);
nextLoc = Context.replaceStringArg(out,templateStr,nextLoc,pRec.errStringArg2);
nextLoc = Context.replaceStringArg(out,templateStr,nextLoc,pRec.errStringArg3);
// get trailing remainder, if any
if (nextLoc != -1)
{
out.append(templateStr.substring(nextLoc,templateStr.length()));
}
}
}
// similiar to error, but doesn't block compilation
void warning(int pos, InputBuffer input, int code) { warning(pos,input,code,"","",""); }
void warning(int pos, InputBuffer input, int code, String errorArg1) { warning(pos,input,code,errorArg1,"",""); }
void warning(int pos, InputBuffer input, int code, String errorArg1, String errorArg2) { warning(pos,input,code,errorArg1,errorArg2,""); }
void warning(int pos, InputBuffer input, int code, String errorArg1, String errorArg2, String errorArg3)
{
// special case ignore
// things in the pEnabledMap. Some common objects like NaN and undefined are not in playerglobal.as yet
// anything from playerglobal.as. It still needs to be cleaned up
// anything from pos==0. This is synthesized code created by the compiler.
if (first_pass ||
(enabledMap.containsKey(code) && !enabledMap.get(code)) ||
ignorableFile(input) ||
(pos == 0)) return;
CodeLocation loc = new CodeLocation();
loc.pos = pos;
loc.input = input;
if (input != null)
{
//TODO Review this for removal/revising later
// remove this when there is no longer problems with sometimes having a
// large and wrong loc.pos
int colPos = input.getColPos(loc.pos);
if (colPos > 300)
{
colPos = 1;
}
WarningRecord rec = new WarningRecord( loc,
input.getLnNum(loc.pos), colPos, code,
errorArg1, errorArg2, errorArg3);
// add to pWarnings
ObjectList<WarningRecord> warnList = pWarnings.get(code);
if (warnList == null)
warnList = new ObjectList<WarningRecord>();
// Check for duplicates
for(WarningRecord test : warnList) {
if(test.colNum == rec.colNum &&
test.lineNum == rec.lineNum &&
test.errStringArg1.equals(rec.errStringArg1) &&
test.errStringArg2.equals(rec.errStringArg2) &&
test.errStringArg3.equals(rec.errStringArg3) &&
test.loc.input.equals(rec.loc.input)) return;
}
warnList.add(rec);
pWarnings.put(code,warnList);
// add to warningsByLoc
String origin = loc.input.origin;
TreeMap<Integer, Set<WarningRecord>> locMap = warningsByLoc.get(origin);
if (locMap == null)
{
locMap = new TreeMap<Integer, Set<WarningRecord>>();
warningsByLoc.put(origin, locMap);
}
Set<WarningRecord> records = locMap.get(rec.lineNum);
if (records == null)
{
records = new HashSet<WarningRecord>(1);
locMap.put(rec.lineNum, records);
}
records.add(rec);
}
else
assert(false); // "invalid inputId inAS2LintEvaluator::warning";
}
//TODO Review this for removal/revising later
private boolean ignorableFile(InputBuffer input)
{
return (input.origin.endsWith(File.separator + "playerglobal.as")) ||
(input.origin.endsWith(File.separator + "Global.as"));
}
// utility to get a version of a type's name without the namespace attached
String getSimpleTypeName(TypeValue type)
{
return type.name.name;
}
private void initialize(Context cx)
{
if (initialized)
return;
if(warningConstantsEN[0] == null) initWarningConstants();
initialized = true;
ReferenceValue typeRef;
Slot s;
int x;
// create a unique value to identify a literal 'undefined' with. We sometimes
// need to know when something is being compared against (or assigned) undefined.
undefinedLiteral = new ObjectValue("", cx.voidType() );
// lookup TypeValues for built in types we expect
for(x=0; x < kNumDefaultTypes; x++)
{
typeRef = new ReferenceValue(cx, null, typeDescriptors[x].name ,cx.getNamespace(typeDescriptors[x].nameSpace));
// this getSlot is allowed to bind
s = typeRef.getSlot(cx,GET_TOKEN);
// c++ variant accesses union member typValue directly, java stores value in objValue
TypeValue t = (s != null && s.getObjectValue() instanceof TypeValue) ? (TypeValue)(s.getObjectValue()) : null;
types[ typeDescriptors[x].code ] = t;
typeRef = null;
}
types[kVoidType] = cx.nullType();
int lang = cx.statics.languageID;
AscWarning[] warningConsts = allWarningConstants[lang];
for(x = 0; x < kNumWarningConstants; x++)
{
warningConstantsMap.put(warningConsts[x].code, warningConsts[x].pWarning);
}
for(x = 0; x < kNumPropertyWarnings; x++)
{
/* Check to see if there is an old mapping, if so grab it and add to it.
* C++ does this succinctly with operator overloading, Java needs some guidance.
*
* In C++, ((unsupportedPropsMap[key])[subkey]) = value;
* If it does not exist, it simply adds the mapping: key->(subkey->value)
* else, it gets the old TypeValue and overloading makes it look like:
* (unsupportedProperties.get(key))[subkey] = value;
*/
Map<TypeValue,Integer> subMap = unsupportedPropsMap.get(unsupportedProperties[x].name);
if (subMap == null) { subMap = new HashMap<TypeValue,Integer>(2); }
subMap.put(types[unsupportedProperties[x].baseType], unsupportedProperties[x].code);
unsupportedPropsMap.put(unsupportedProperties[x].name, subMap);
}
for(x = 0; x < kNumMethodWarnings; x++)
{
Map<TypeValue,Integer> subMap = unsupportedMethodsMap.get(unsupportedMethods[x].name);
if (subMap == null) { subMap = new HashMap<TypeValue,Integer>(2); }
subMap.put(types[unsupportedMethods[x].baseType], unsupportedMethods[x].code);
unsupportedMethodsMap.put(unsupportedMethods[x].name, subMap);
}
for(x = 0; x < kNumEventWarnings; x++)
{
Map<TypeValue,Integer> subMap = unsupportedEventsMap.get(unsupportedEvents[x].name);
if (subMap == null) { subMap = new HashMap<TypeValue,Integer>(2); }
subMap.put(types[unsupportedEvents[x].baseType], unsupportedEvents[x].code);
unsupportedEventsMap.put(unsupportedEvents[x].name, subMap);
}
/* Hack to ignore warnings for things which should be in playerglobal.as
but aren't yet (usually because there are some problems with its current definition) */
if(hackIgnoreIdentifierMap.size() != toIgnore.length) {
for(x=0; x < toIgnore.length; x++)
hackIgnoreIdentifierMap.put(toIgnore[x], true);
}
baseType_context.add(cx.nullType());
baseRef_context.add(null);
// CN: it would be more elegant to read the xml file using the org.xml.sax.XMLReader,
// but this is quicker (to implement) and matches the c++ implementation. We don't
// really need to read all the data in the xml file, just a boolean code per warning id
if (enabledMap == null)
{
final String filename = (warningEnabledMapFile != null)
? warningEnabledMapFile
: "EnabledWarnings.xml";
try {
enabledMap = parseEnabledWarningsFile(filename);
}
catch (IOException ex)
{
enabledMap = new HashMap<Integer, Boolean>();
}
}
}
public static HashMap<Integer, Boolean> getWarningDefaults()
{
if(warningConstantsEN[0] == null) initWarningConstants();
AscWarning[] warningConsts = allWarningConstants[ContextStatics.LANG_EN]; // language doesn't really matter here
HashMap<Integer, Boolean> enabledMap = new HashMap<Integer, Boolean>();
for(int x = 0; x < kNumWarningConstants; x++)
{
enabledMap.put(warningConsts[x].code,true);
}
return enabledMap;
}
public static HashMap<Integer, Boolean> parseEnabledWarningsFile(String filename) throws IOException {
HashMap<Integer, Boolean> enabledMap = getWarningDefaults();
// only init the warning constants once -- this can be called statically, or internally
File warningsFile = new File(filename);
if (warningsFile.exists() && debug)
{
System.err.println("[coach] Using '" + warningsFile.getName() + "'...");
}
BufferedInputStream warningInput = new BufferedInputStream(new FileInputStream(warningsFile));
BufferedReader reader = new BufferedReader(new InputStreamReader(warningInput, "UTF8"));
String textAsString;
textAsString = reader.readLine();
int textLength = textAsString.length();
int endpos = textAsString.indexOf("</warnings>");
if (endpos == -1)
endpos = textLength;
int pos = 0;
int code;
boolean atLeastOneWarningDisabled = false;
while (endpos == textLength)
{
pos = textAsString.indexOf("<warning id=", pos);
if (pos == -1)
pos = textLength;
if (pos != endpos)
{
code = Integer.parseInt(textAsString.substring(pos+13, pos+17)); // assumes id is 4 digits long
pos = textAsString.indexOf("enabled=",pos);
if (pos == -1) {
pos = textLength;
}
if (pos != endpos && "false".equals(textAsString.substring(pos+9, pos+14))) {
enabledMap.put(code,false);
atLeastOneWarningDisabled = true;
}
pos = textAsString.indexOf("</warning>", pos);
if (pos == -1) {
pos = textLength;
}
else {
pos += 11;
}
}
if (pos >= endpos)
{
textAsString = reader.readLine();
textLength = textAsString.length();
endpos = textAsString.indexOf("</warnings>");
if (endpos == -1)
endpos = textLength;
pos = 0;
}
}
reader.close();
if(debug && atLeastOneWarningDisabled) System.err.println("[coach] Some coach-mode warnings are disabled");
return enabledMap;
}
public Value evaluate(Context cx, DefaultXMLNamespaceNode node)
{
if( node.expr != null )
{
node.expr.evaluate(cx,this);
}
return cx.voidType();
}
public Value evaluate(Context cx, PragmaNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, UsePrecisionNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, UseNumericNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, UseRoundingNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, PragmaExpressionNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, PackageNameNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, PackageIdentifiersNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, TypedIdentifierNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, UntypedVariableBindingNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, DocCommentNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, ImportNode node)
{
return cx.voidType();
}
public Value evaluate(Context cx, QualifiedIdentifierNode node)
{
if (node.qualifier != null)
{
node.qualifier.evaluate(cx, this);
}
return cx.voidType();
}
public Value evaluate(Context cx, QualifiedExpressionNode node)
{
if (node.qualifier != null)
{
node.qualifier.evaluate(cx, this);
}
if (node.expr != null)
{
node.expr.evaluate(cx, this);
}
return cx.voidType();
}
// We sometimes need to store AS2Lint specific data on the slot for an identifier. These
// methods allow us to read and write our custom data to/from a slot without impacting the rest
// of the compiler. Slot supported an opaque data pointer named embeddedData which we
// store our custom LintDataRecord on. The methods which follow simplify accessing
// these custom data fields.
private static class LintDataRecord
{
public boolean has_return_value;
public boolean is_registered_for_event;
public boolean has_been_declared;
int declaration_pos;
public LintDataRecord()
{
has_return_value = false;
has_been_declared = false;
is_registered_for_event = false;
declaration_pos = 0;
}
};
private ObjectList<Slot> slotsToClean = new ObjectList<Slot>(); // store what we allocate so we can clean it up
private LintDataRecord slot_GetComplianceRecord(Slot s)
{
if (s.getEmbeddedData() == null)
{
s.setEmbeddedData(new LintDataRecord());
slotsToClean.add( s );
}
return (LintDataRecord)(s.getEmbeddedData());
}
private void slot_markAsDeclared(Slot s, int pos)
{
slot_GetComplianceRecord(s).has_been_declared = true;
slot_GetComplianceRecord(s).declaration_pos = pos;
}
private void slot_setHasReturnValue(Slot s, boolean hasReturn)
{
slot_GetComplianceRecord(s).has_return_value = hasReturn;
}
private void slot_markAsRegisteredForEvent(Slot s, boolean isRegistered)
{
slot_GetComplianceRecord(s).is_registered_for_event = isRegistered;
}
private boolean slot_isAlreadyDeclared(Slot s)
{
if (s != null && s.getEmbeddedData() != null)
return slot_GetComplianceRecord(s).has_been_declared;
return false;
}
// private int slot_getOriginalDeclarationPosition(Slot s)
// {
// if (s != null && s.getEmbeddedData() != null)
// return slot_GetComplianceRecord(s).declaration_pos;
// return -1;
// }
private boolean slot_GetHasReturnValue(Slot s)
{
if (s != null && s.getEmbeddedData() != null)
{
LintDataRecord r = slot_GetComplianceRecord(s);
return r.has_return_value;
}
return false;
}
private boolean slot_GetRegisteredForEvent(Slot s)
{
if (s != null && s.getEmbeddedData() != null)
return slot_GetComplianceRecord(s).is_registered_for_event;
return false;
}
static void clear_lintData(Slot s)
{
s.setEmbeddedData(null);
}
public void testErrorStrings(Context cx)
{
// todo: add all warning string here
}
public void setOutputToFile(boolean otf) {
output_to_file = otf;
}
public Value evaluate(Context cx, TypeExpressionNode node)
{
return node.expr.evaluate(cx, this);
}
}