Subject: Rhino: execMethod/methodArity cleanup.

Date: Mon, 07 May 2001 14:25:34 +0200
From: Igor Bukanov <igor.bukanov@windriver.com>
Organization: Wind River
To: Norris Boyd <nboyd@atg.com>

The current code that implements execMethod/methodArity for IdFunction
support returns an arbitrary value for id that is not known. This is not
very good behavior because it may hide bugs in the id support and it
also does not allow to distinguish ids that are used for function  from
ids used for properties like String.length.

To fix this I changed semantic of the methodArity method to return -1
for an unknown/non-method id and added code to throw an exception for
bad ids. This change requires to adjust all NativeSomething objects that
use IdScriptabl and after a release all such interface changes would be
no go, but is not a release yet, right?

I also eliminated the "IdFunction f" argument from
IdFunction.Master.methodArity and the tagId field from IdFunction. When
I wrote  the initial code for IdFunction.java, I added that just to be
able to use same id number in a class that implements IdFunction.Master
and its descendants via checking idTag. But that does not work in
general because IdScriptable can use id for non-function fields as well
so to avoid id clashes another way should be used. For example, if
someone would like to extend NativeMath to support more functionality,
he can use:

class Math2: extends NativeMath {
     private static idBase;

     {
         if (idBase == 0) idBase = super.getMaximumId();
     }

      public int methodArity(int methodId) {
          switch (methodId - idBase) {
             case Id_foo: return 2;
             case Id_bar: return 3;
         }
         return super.methodArity(methodId);
     }

      public Object execMethod
          (int methodId, IdFunction f,
           Context cx, Scriptable scope, Scriptable thisObj, Object[] args)
          throws JavaScriptException
      {
          switch (methodId - idBase) {
             case Id_foo: return ...;
             case Id_bar: return ...;
         }
          return super.execMethod(methodId, f, cx, scope, thisObj, args);
     }

      protected int getMaximumId() { return idBase + MAX_ID; }

      protected String getIdName(int id) {
          switch (id - idBase) {
             case Id_foo: return "for";
             case Id_bar: return "bar";
         }
         return super.getIdName(id);
     }
     ...
     private static final int
         Id_foo = 1,
         Id_bar = 2,
         MAX_ID = 2;

etc.


Note that a simpler solution to make MAX_ID field public in NativeMath
and write in Math2:
     private static final int
         Id_foo = NativeMath.MAX_ID + 1,
         Id_bar = NativeMath.MAX_ID + 2,
         MAX_ID = NativeMath.MAX_ID + 2;

does not work because in this way adding any new id to NativeMath would
break binary compatibility with Math2.
This commit is contained in:
nboyd%atg.com 2001-05-08 13:36:16 +00:00
parent c1af829899
commit bd685b66b2
11 changed files with 216 additions and 125 deletions

View File

@ -49,6 +49,9 @@ public class IdFunction extends ScriptableObject implements Function
public static final int FUNCTION_AND_CONSTRUCTOR = 2;
/** Master for id-based functions that knows their properties and how to
** execute them
*/
public static interface Master {
/** 'thisObj' will be null if invoked as constructor, in which case
** instance of Scriptable should be returned */
@ -57,7 +60,10 @@ public class IdFunction extends ScriptableObject implements Function
Scriptable thisObj, Object[] args)
throws JavaScriptException;
public int methodArity(int methodId, IdFunction function);
/** Get arity or defined argument count for method with given id.
** Should return -1 if methodId is not known or can not be used
** with execMethod call */
public int methodArity(int methodId);
public Scriptable getParentScope();
}
@ -68,18 +74,6 @@ public class IdFunction extends ScriptableObject implements Function
this.methodId = id;
}
/**
** Primary goal of idTag is to allow to use the same method id in class
** and its descendants.
*/
public final Object idTag() {
return idTag;
}
public void setIdTag(Object tag) {
idTag = tag;
}
public final int functionType() {
return functionType;
}
@ -219,8 +213,20 @@ public class IdFunction extends ScriptableObject implements Function
}
}
static RuntimeException onBadMethodId(Master master, int id) {
// It is program error to call id-like methods for unknown or
// non-function id
return new RuntimeException("BAD FUNCTION ID="+id+" MASTER="+master);
}
private int getArity() {
return master.methodArity(methodId, this);
int arity = master.methodArity(methodId);
if (arity < 0) {
throw onBadMethodId(master, methodId);
}
return arity;
}
// Copied from NativeFunction.construct
@ -278,8 +284,6 @@ public class IdFunction extends ScriptableObject implements Function
protected String methodName;
protected Object idTag;
protected int functionType = FUNCTION_ONLY;
// If != NOT_FOUND, represent script-immune prototype property

View File

@ -286,15 +286,22 @@ public abstract class IdScriptable extends ScriptableObject
}
/** 'thisObj' will be null if invoked as constructor, in which case
** instance of Scriptable should be returned.
*/
public abstract Object execMethod(int methodId, IdFunction function,
** instance of Scriptable should be returned. */
public Object execMethod(int methodId, IdFunction function,
Context cx, Scriptable scope,
Scriptable thisObj, Object[] args)
throws JavaScriptException;
public abstract int methodArity(int methodId, IdFunction function);
throws JavaScriptException
{
throw IdFunction.onBadMethodId(this, methodId);
}
/** Get arity or defined argument count for method with given id.
** Should return -1 if methodId is not known or can not be used
** with execMethod call. */
public int methodArity(int methodId) {
return -1;
}
/** Do scope initialization.
** Default implementation calls activateIdMap() and then if
** mapNameToId("constructor") returns positive id, defines EcmaScript

View File

@ -118,11 +118,23 @@ public class NativeArray extends IdScriptable {
return this;
}
public int methodArity(int methodId, IdFunction function) {
if (methodId == Id_reverse || methodId == Id_toString) {
return 0;
public int methodArity(int methodId) {
switch (methodId) {
case Id_constructor: return 1;
case Id_toString: return 0;
case Id_toLocaleString: return 1;
case Id_join: return 1;
case Id_reverse: return 0;
case Id_sort: return 1;
case Id_push: return 1;
case Id_pop: return 1;
case Id_shift: return 1;
case Id_unshift: return 1;
case Id_splice: return 1;
case Id_concat: return 1;
case Id_slice: return 1;
}
return 1;
return super.methodArity(methodId);
}
public Object execMethod
@ -171,7 +183,7 @@ public class NativeArray extends IdScriptable {
return jsFunction_slice(cx, thisObj, args);
}
return Scriptable.NOT_FOUND;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
public Object get(int index, Scriptable start) {

View File

@ -66,11 +66,11 @@ public class NativeBoolean extends IdScriptable {
return super.getDefaultValue(typeHint);
}
public int methodArity(int methodId, IdFunction function) {
if (methodId == Id_constructor) {
return 1;
}
return 0;
public int methodArity(int methodId) {
if (methodId == Id_constructor) return 1;
if (methodId == Id_toString) return 0;
if (methodId == Id_valueOf) return 0;
return super.methodArity(methodId);
}
public Object execMethod
@ -89,7 +89,7 @@ public class NativeBoolean extends IdScriptable {
return wrap_boolean(realThis(thisObj, f).jsFunction_valueOf());
}
return null; // Unreachable
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private NativeBoolean realThis(Scriptable thisObj, IdFunction f) {

View File

@ -83,36 +83,57 @@ public class NativeDate extends IdScriptable {
super.fillConstructorProperties(cx, ctor, sealed);
}
public int methodArity(int methodId, IdFunction function) {
public int methodArity(int methodId) {
switch (methodId) {
case ConstructorId_UTC:
case ConstructorId_parse:
case Id_constructor:
case Id_setTime:
case Id_setMilliseconds:
case Id_setUTCMilliseconds:
case Id_setDate:
case Id_setUTCDate:
case Id_setYear:
return 1;
case Id_setSeconds:
case Id_setUTCSeconds:
case Id_setMonth:
case Id_setUTCMonth:
return 2;
case Id_setMinutes:
case Id_setUTCMinutes:
case Id_setFullYear:
case Id_setUTCFullYear:
return 3;
case Id_setHours:
case Id_setUTCHours:
return 4;
case ConstructorId_UTC: return 1;
case ConstructorId_parse: return 1;
case Id_constructor: return 1;
case Id_toString: return 0;
case Id_toTimeString: return 0;
case Id_toDateString: return 0;
case Id_toLocaleString: return 0;
case Id_toLocaleTimeString: return 0;
case Id_toLocaleDateString: return 0;
case Id_toGMTString: return 0;
case Id_toUTCString: return 0;
case Id_valueOf: return 0;
case Id_getTime: return 0;
case Id_getYear: return 0;
case Id_getFullYear: return 0;
case Id_getUTCFullYear: return 0;
case Id_getMonth: return 0;
case Id_getUTCMonth: return 0;
case Id_getDate: return 0;
case Id_getUTCDate: return 0;
case Id_getDay: return 0;
case Id_getUTCDay: return 0;
case Id_getHours: return 0;
case Id_getUTCHours: return 0;
case Id_getMinutes: return 0;
case Id_getUTCMinutes: return 0;
case Id_getSeconds: return 0;
case Id_getUTCSeconds: return 0;
case Id_getMilliseconds: return 0;
case Id_getUTCMilliseconds: return 0;
case Id_getTimezoneOffset: return 0;
case Id_setTime: return 1;
case Id_setMilliseconds: return 1;
case Id_setUTCMilliseconds: return 1;
case Id_setSeconds: return 2;
case Id_setUTCSeconds: return 2;
case Id_setMinutes: return 3;
case Id_setUTCMinutes: return 3;
case Id_setHours: return 4;
case Id_setUTCHours: return 4;
case Id_setDate: return 1;
case Id_setUTCDate: return 1;
case Id_setMonth: return 2;
case Id_setUTCMonth: return 2;
case Id_setFullYear: return 3;
case Id_setUTCFullYear: return 3;
case Id_setYear: return 1;
}
return 0;
return super.methodArity(methodId);
}
public Object execMethod
@ -278,7 +299,7 @@ public class NativeDate extends IdScriptable {
jsFunction_setYear(ScriptRuntime.toNumber(args, 0)));
}
return Scriptable.NOT_FOUND;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private NativeDate realThis(Scriptable thisObj, IdFunction f,

View File

@ -51,11 +51,10 @@ public class NativeError extends IdScriptable {
super.scopeInit(cx, scope, sealed);
}
public int methodArity(int methodId, IdFunction function) {
if (methodId == Id_constructor) {
return 1;
}
return 0;
public int methodArity(int methodId) {
if (methodId == Id_constructor) return 1;
if (methodId == Id_toString) return 0;
return super.methodArity(methodId);
}
public Object execMethod
@ -71,7 +70,7 @@ public class NativeError extends IdScriptable {
return realThis(thisObj, f).jsFunction_toString();
}
return Scriptable.NOT_FOUND;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private NativeError realThis(Scriptable thisObj, IdFunction f) {

View File

@ -54,7 +54,7 @@ public class NativeGlobal implements ScopeInitializer, IdFunction.Master {
public void scopeInit(Context cx, Scriptable scope, boolean sealed) {
for (int id = 1; id <= LAST_METHOD_ID; ++id) {
for (int id = 1; id <= MAX_ID; ++id) {
String name = getMethodName(id);
IdFunction f = new IdFunction(this, name, id);
f.setParentScope(scope);
@ -129,12 +129,26 @@ public class NativeGlobal implements ScopeInitializer, IdFunction.Master {
case Id_new_CommonError:
return new_CommonError(function, cx, scope, args);
}
return null;
throw IdFunction.onBadMethodId(this, methodId);
}
public int methodArity(int methodId, IdFunction function) {
if (methodId == Id_parseInt) { return 2; }
return 1;
public int methodArity(int methodId) {
switch (methodId) {
case Id_new_CommonError: return 1;
case Id_decodeURI: return 1;
case Id_decodeURIComponent: return 1;
case Id_encodeURI: return 1;
case Id_encodeURIComponent: return 1;
case Id_escape: return 1;
case Id_eval: return 1;
case Id_isFinite: return 1;
case Id_isNaN: return 1;
case Id_parseFloat: return 1;
case Id_parseInt: return 2;
case Id_unescape: return 1;
}
return -1;
}
public Scriptable getParentScope() { return null; }
@ -773,7 +787,14 @@ public class NativeGlobal implements ScopeInitializer, IdFunction.Master {
return ucs4Char;
}
protected int getMinimumId() { return MIN_ID; }
protected int getMaximumId() { return MAX_ID; }
private static final int
MIN_ID = -1,
Id_new_CommonError = -1,
Id_decodeURI = 1,
Id_decodeURIComponent = 2,
Id_encodeURI = 3,
@ -786,7 +807,6 @@ public class NativeGlobal implements ScopeInitializer, IdFunction.Master {
Id_parseInt = 10,
Id_unescape = 11,
LAST_METHOD_ID = 11,
MAX_ID = 11;
Id_new_CommonError = 12;
}

View File

@ -85,21 +85,32 @@ public class NativeMath extends IdScriptable
return null;
}
public int methodArity(int methodId, IdFunction function) {
public int methodArity(int methodId) {
switch (methodId) {
case Id_atan2:
case Id_max:
case Id_min:
case Id_pow:
return 2;
case Id_random:
return 0;
case Id_abs: return 1;
case Id_acos: return 1;
case Id_asin: return 1;
case Id_atan: return 1;
case Id_atan2: return 2;
case Id_ceil: return 1;
case Id_cos: return 1;
case Id_exp: return 1;
case Id_floor: return 1;
case Id_log: return 1;
case Id_max: return 2;
case Id_min: return 2;
case Id_pow: return 2;
case Id_random: return 0;
case Id_round: return 1;
case Id_sin: return 1;
case Id_sqrt: return 1;
case Id_tan: return 1;
}
return 1;
return super.methodArity(methodId);
}
public Object execMethod
(int methodId, IdFunction function,
(int methodId, IdFunction f,
Context cx, Scriptable scope, Scriptable thisObj, Object[] args)
throws JavaScriptException
{
@ -160,7 +171,7 @@ public class NativeMath extends IdScriptable
case Id_tan: return wrap_double
(js_tan(ScriptRuntime.toNumber(args, 0)));
}
return null;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private double js_abs(double x) {

View File

@ -82,17 +82,17 @@ public class NativeNumber extends IdScriptable {
super.fillConstructorProperties(cx, ctor, sealed);
}
public int methodArity(int methodId, IdFunction function) {
public int methodArity(int methodId) {
switch (methodId) {
case Id_constructor:
case Id_toString:
case Id_toLocaleString:
case Id_toFixed:
case Id_toExponential:
case Id_toPrecision:
return 1;
case Id_constructor: return 1;
case Id_toString: return 1;
case Id_valueOf: return 0;
case Id_toLocaleString: return 1;
case Id_toFixed: return 1;
case Id_toExponential: return 1;
case Id_toPrecision: return 1;
}
return 0;
return super.methodArity(methodId);
}
public Object execMethod
@ -125,7 +125,7 @@ public class NativeNumber extends IdScriptable {
}
return Scriptable.NOT_FOUND;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private NativeNumber realThis(Scriptable thisObj, IdFunction f) {

View File

@ -49,15 +49,17 @@ public class NativeObject extends IdScriptable {
return "Object";
}
public int methodArity(int methodId, IdFunction function) {
public int methodArity(int methodId) {
switch (methodId) {
case Id_constructor:
case Id_hasOwnProperty:
case Id_propertyIsEnumerable:
case Id_isPrototypeOf:
return 1;
case Id_constructor: return 1;
case Id_toString: return 0;
case Id_toLocaleString: return 0;
case Id_valueOf: return 0;
case Id_hasOwnProperty: return 1;
case Id_propertyIsEnumerable: return 1;
case Id_isPrototypeOf: return 1;
}
return 0;
return super.methodArity(methodId);
}
public Object execMethod
@ -81,7 +83,7 @@ public class NativeObject extends IdScriptable {
case Id_isPrototypeOf:
return jsFunction_isPrototypeOf(cx, thisObj, args);
}
return Scriptable.NOT_FOUND;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private static Object jsConstructor(Context cx, Object[] args,

View File

@ -97,29 +97,44 @@ public class NativeString extends IdScriptable {
return this;
}
public int methodArity(int methodId, IdFunction function) {
public int methodArity(int methodId) {
switch (methodId) {
case ConstructorId_fromCharCode:
case Id_constructor:
case Id_charAt:
case Id_charCodeAt:
case Id_concat:
case Id_equals:
case Id_equalsIgnoreCase:
case Id_match:
case Id_search:
case Id_split:
case Id_replace:
return 1;
case ConstructorId_fromCharCode: return 1;
case Id_indexOf:
case Id_lastIndexOf:
case Id_substring:
case Id_substr:
case Id_slice:
return 2;
case Id_constructor: return 1;
case Id_toString: return 0;
case Id_valueOf: return 0;
case Id_charAt: return 1;
case Id_charCodeAt: return 1;
case Id_indexOf: return 2;
case Id_lastIndexOf: return 2;
case Id_split: return 1;
case Id_substring: return 2;
case Id_toLowerCase: return 0;
case Id_toUpperCase: return 0;
case Id_substr: return 2;
case Id_concat: return 1;
case Id_slice: return 2;
case Id_bold: return 0;
case Id_italics: return 0;
case Id_fixed: return 0;
case Id_strike: return 0;
case Id_small: return 0;
case Id_big: return 0;
case Id_blink: return 0;
case Id_sup: return 0;
case Id_sub: return 0;
case Id_fontsize: return 0;
case Id_fontcolor: return 0;
case Id_link: return 0;
case Id_anchor: return 0;
case Id_equals: return 1;
case Id_equalsIgnoreCase: return 1;
case Id_match: return 1;
case Id_search: return 1;
case Id_replace: return 1;
}
return 0;
return super.methodArity(methodId);
}
public Object execMethod
@ -232,7 +247,7 @@ public class NativeString extends IdScriptable {
}
return Scriptable.NOT_FOUND;
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
private NativeString realThis(Scriptable thisObj, IdFunction f) {