1 /*
2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package jdk.nashorn.internal.tools.nasgen;
26 
27 import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJECT_ARRAY_DESC;
28 import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJECT_DESC;
29 import static jdk.nashorn.internal.tools.nasgen.StringConstants.OBJ_PKG;
30 import static jdk.nashorn.internal.tools.nasgen.StringConstants.RUNTIME_PKG;
31 import static jdk.nashorn.internal.tools.nasgen.StringConstants.SCRIPTS_PKG;
32 import static jdk.nashorn.internal.tools.nasgen.StringConstants.STRING_DESC;
33 import static jdk.nashorn.internal.tools.nasgen.StringConstants.TYPE_SYMBOL;
34 
35 import jdk.internal.org.objectweb.asm.Opcodes;
36 import jdk.internal.org.objectweb.asm.Type;
37 
38 /**
39  * Details about a Java method or field annotated with any of the field/method
40  * annotations from the jdk.nashorn.internal.objects.annotations package.
41  */
42 public final class MemberInfo implements Cloneable {
43     // class loader of this class
44     private static final ClassLoader MY_LOADER = MemberInfo.class.getClassLoader();
45 
46     /**
47      * The different kinds of available class annotations
48      */
49     public static enum Kind {
50 
51         /**
52          * This is a script class
53          */
54         SCRIPT_CLASS,
55         /**
56          * This is a constructor
57          */
58         CONSTRUCTOR,
59         /**
60          * This is a function
61          */
62         FUNCTION,
63         /**
64          * This is a getter
65          */
66         GETTER,
67         /**
68          * This is a setter
69          */
70         SETTER,
71         /**
72          * This is a property
73          */
74         PROPERTY,
75         /**
76          * This is a specialized version of a function
77          */
78         SPECIALIZED_FUNCTION,
79     }
80 
81     // keep in sync with jdk.nashorn.internal.objects.annotations.Attribute
82     static final int DEFAULT_ATTRIBUTES = 0x0;
83 
84     static final int DEFAULT_ARITY = -2;
85 
86     // the kind of the script annotation - one of the above constants
87     private MemberInfo.Kind kind;
88     // script property name
89     private String name;
90     // script property attributes
91     private int attributes;
92     // name of the java member
93     private String javaName;
94     // type descriptor of the java member
95     private String javaDesc;
96     // access bits of the Java field or method
97     private int javaAccess;
98     // initial value for static @Property fields
99     private Object value;
100     // class whose object is created to fill property value
101     private String initClass;
102     // arity of the Function or Constructor
103     private int arity;
104 
105     private Where where;
106 
107     private Type linkLogicClass;
108 
109     private boolean isSpecializedConstructor;
110 
111     private boolean isOptimistic;
112 
113     private boolean convertsNumericArgs;
114 
115     /**
116      * @return the kind
117      */
getKind()118     public Kind getKind() {
119         return kind;
120     }
121 
122     /**
123      * @param kind the kind to set
124      */
setKind(final Kind kind)125     public void setKind(final Kind kind) {
126         this.kind = kind;
127     }
128 
129     /**
130      * @return the name
131      */
getName()132     public String getName() {
133         return name;
134     }
135 
136     /**
137      * @param name the name to set
138      */
setName(final String name)139     public void setName(final String name) {
140         this.name = name;
141     }
142 
143     /**
144      * Tag something as specialized constructor or not
145      * @param isSpecializedConstructor boolean, true if specialized constructor
146      */
setIsSpecializedConstructor(final boolean isSpecializedConstructor)147     public void setIsSpecializedConstructor(final boolean isSpecializedConstructor) {
148         this.isSpecializedConstructor = isSpecializedConstructor;
149     }
150 
151     /**
152      * Check if something is a specialized constructor
153      * @return true if specialized constructor
154      */
isSpecializedConstructor()155     public boolean isSpecializedConstructor() {
156         return isSpecializedConstructor;
157     }
158 
159     /**
160      * Check if this is an optimistic builtin function
161      * @return true if optimistic builtin
162      */
isOptimistic()163     public boolean isOptimistic() {
164         return isOptimistic;
165     }
166 
167     /**
168      * Tag something as optimistic builtin or not
169      * @param isOptimistic boolean, true if builtin constructor
170      */
setIsOptimistic(final boolean isOptimistic)171     public void setIsOptimistic(final boolean isOptimistic) {
172         this.isOptimistic = isOptimistic;
173     }
174 
175     /**
176      * Check if this function converts arguments for numeric parameters to numbers
177      * so it's safe to pass booleans as 0 and 1
178      * @return true if it is safe to convert arguments to numbers
179      */
convertsNumericArgs()180     public boolean convertsNumericArgs() {
181         return convertsNumericArgs;
182     }
183 
184     /**
185      * Tag this as a function that converts arguments for numeric params to numbers
186      * @param convertsNumericArgs if true args can be safely converted to numbers
187      */
setConvertsNumericArgs(final boolean convertsNumericArgs)188     public void setConvertsNumericArgs(final boolean convertsNumericArgs) {
189         this.convertsNumericArgs = convertsNumericArgs;
190     }
191 
192     /**
193      * Get the SpecializedFunction guard for specializations, i.e. optimistic
194      * builtins
195      * @return specialization, null if none
196      */
getLinkLogicClass()197     public Type getLinkLogicClass() {
198         return linkLogicClass;
199     }
200 
201     /**
202      * Set the SpecializedFunction link logic class for specializations, i.e. optimistic
203      * builtins
204      * @param linkLogicClass link logic class
205      */
206 
setLinkLogicClass(final Type linkLogicClass)207     public void setLinkLogicClass(final Type linkLogicClass) {
208         this.linkLogicClass = linkLogicClass;
209     }
210 
211     /**
212      * @return the attributes
213      */
getAttributes()214     public int getAttributes() {
215         return attributes;
216     }
217 
218     /**
219      * @param attributes the attributes to set
220      */
setAttributes(final int attributes)221     public void setAttributes(final int attributes) {
222         this.attributes = attributes;
223     }
224 
225     /**
226      * @return the javaName
227      */
getJavaName()228     public String getJavaName() {
229         return javaName;
230     }
231 
232     /**
233      * @param javaName the javaName to set
234      */
setJavaName(final String javaName)235     public void setJavaName(final String javaName) {
236         this.javaName = javaName;
237     }
238 
239     /**
240      * @return the javaDesc
241      */
getJavaDesc()242     public String getJavaDesc() {
243         return javaDesc;
244     }
245 
setJavaDesc(final String javaDesc)246     void setJavaDesc(final String javaDesc) {
247         this.javaDesc = javaDesc;
248     }
249 
getJavaAccess()250     int getJavaAccess() {
251         return javaAccess;
252     }
253 
setJavaAccess(final int access)254     void setJavaAccess(final int access) {
255         this.javaAccess = access;
256     }
257 
getValue()258     Object getValue() {
259         return value;
260     }
261 
setValue(final Object value)262     void setValue(final Object value) {
263         this.value = value;
264     }
265 
getWhere()266     Where getWhere() {
267         return where;
268     }
269 
setWhere(final Where where)270     void setWhere(final Where where) {
271         this.where = where;
272     }
273 
isFinal()274     boolean isFinal() {
275         return (javaAccess & Opcodes.ACC_FINAL) != 0;
276     }
277 
isStatic()278     boolean isStatic() {
279         return (javaAccess & Opcodes.ACC_STATIC) != 0;
280     }
281 
isStaticFinal()282     boolean isStaticFinal() {
283         return isStatic() && isFinal();
284     }
285 
isInstanceGetter()286     boolean isInstanceGetter() {
287         return kind == Kind.GETTER && where == Where.INSTANCE;
288     }
289 
290     /**
291      * Check whether this MemberInfo is a getter that resides in the instance
292      *
293      * @return true if instance setter
294      */
isInstanceSetter()295     boolean isInstanceSetter() {
296         return kind == Kind.SETTER && where == Where.INSTANCE;
297     }
298 
isInstanceProperty()299     boolean isInstanceProperty() {
300         return kind == Kind.PROPERTY && where == Where.INSTANCE;
301     }
302 
isInstanceFunction()303     boolean isInstanceFunction() {
304         return kind == Kind.FUNCTION && where == Where.INSTANCE;
305     }
306 
isPrototypeGetter()307     boolean isPrototypeGetter() {
308         return kind == Kind.GETTER && where == Where.PROTOTYPE;
309     }
310 
isPrototypeSetter()311     boolean isPrototypeSetter() {
312         return kind == Kind.SETTER && where == Where.PROTOTYPE;
313     }
314 
isPrototypeProperty()315     boolean isPrototypeProperty() {
316         return kind == Kind.PROPERTY && where == Where.PROTOTYPE;
317     }
318 
isPrototypeFunction()319     boolean isPrototypeFunction() {
320         return kind == Kind.FUNCTION && where == Where.PROTOTYPE;
321     }
322 
isConstructorGetter()323     boolean isConstructorGetter() {
324         return kind == Kind.GETTER && where == Where.CONSTRUCTOR;
325     }
326 
isConstructorSetter()327     boolean isConstructorSetter() {
328         return kind == Kind.SETTER && where == Where.CONSTRUCTOR;
329     }
330 
isConstructorProperty()331     boolean isConstructorProperty() {
332         return kind == Kind.PROPERTY && where == Where.CONSTRUCTOR;
333     }
334 
isConstructorFunction()335     boolean isConstructorFunction() {
336         return kind == Kind.FUNCTION && where == Where.CONSTRUCTOR;
337     }
338 
isConstructor()339     boolean isConstructor() {
340         return kind == Kind.CONSTRUCTOR;
341     }
342 
verify()343     void verify() {
344         switch (kind) {
345             case CONSTRUCTOR: {
346                 final Type returnType = Type.getReturnType(javaDesc);
347                 if (!isJSObjectType(returnType)) {
348                     error("return value of a @Constructor method should be of Object type, found " + returnType);
349                 }
350                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
351                 if (argTypes.length < 2) {
352                     error("@Constructor methods should have at least 2 args");
353                 }
354                 if (!argTypes[0].equals(Type.BOOLEAN_TYPE)) {
355                     error("first argument of a @Constructor method should be of boolean type, found " + argTypes[0]);
356                 }
357                 if (!isJavaLangObject(argTypes[1])) {
358                     error("second argument of a @Constructor method should be of Object type, found " + argTypes[0]);
359                 }
360 
361                 if (argTypes.length > 2) {
362                     for (int i = 2; i < argTypes.length - 1; i++) {
363                         if (!isJavaLangObject(argTypes[i])) {
364                             error(i + "'th argument of a @Constructor method should be of Object type, found " + argTypes[i]);
365                         }
366                     }
367 
368                     final String lastArgTypeDesc = argTypes[argTypes.length - 1].getDescriptor();
369                     final boolean isVarArg = lastArgTypeDesc.equals(OBJECT_ARRAY_DESC);
370                     if (!lastArgTypeDesc.equals(OBJECT_DESC) && !isVarArg) {
371                         error("last argument of a @Constructor method is neither Object nor Object[] type: " + lastArgTypeDesc);
372                     }
373 
374                     if (isVarArg && argTypes.length > 3) {
375                         error("vararg of a @Constructor method has more than 3 arguments");
376                     }
377                 }
378             }
379             break;
380             case FUNCTION: {
381                 final Type returnType = Type.getReturnType(javaDesc);
382                 if (!(isValidJSType(returnType) || Type.VOID_TYPE == returnType)) {
383                     error("return value of a @Function method should be a valid JS type, found " + returnType);
384                 }
385                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
386                 if (argTypes.length < 1) {
387                     error("@Function methods should have at least 1 arg");
388                 }
389                 if (!isJavaLangObject(argTypes[0])) {
390                     error("first argument of a @Function method should be of Object type, found " + argTypes[0]);
391                 }
392 
393                 if (argTypes.length > 1) {
394                     for (int i = 1; i < argTypes.length - 1; i++) {
395                         if (!isJavaLangObject(argTypes[i])) {
396                             error(i + "'th argument of a @Function method should be of Object type, found " + argTypes[i]);
397                         }
398                     }
399 
400                     final String lastArgTypeDesc = argTypes[argTypes.length - 1].getDescriptor();
401                     final boolean isVarArg = lastArgTypeDesc.equals(OBJECT_ARRAY_DESC);
402                     if (!lastArgTypeDesc.equals(OBJECT_DESC) && !isVarArg) {
403                         error("last argument of a @Function method is neither Object nor Object[] type: " + lastArgTypeDesc);
404                     }
405 
406                     if (isVarArg && argTypes.length > 2) {
407                         error("vararg @Function method has more than 2 arguments");
408                     }
409                 }
410             }
411             break;
412             case SPECIALIZED_FUNCTION: {
413                 final Type returnType = Type.getReturnType(javaDesc);
414                 if (!(isValidJSType(returnType) || (isSpecializedConstructor() && Type.VOID_TYPE == returnType))) {
415                     error("return value of a @SpecializedFunction method should be a valid JS type, found " + returnType);
416                 }
417                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
418                 for (int i = 0; i < argTypes.length; i++) {
419                     if (!isValidJSType(argTypes[i])) {
420                         error(i + "'th argument of a @SpecializedFunction method is not valid JS type, found " + argTypes[i]);
421                     }
422                 }
423             }
424             break;
425             case GETTER: {
426                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
427                 if (argTypes.length != 1) {
428                     error("@Getter methods should have one argument");
429                 }
430                 if (!isJavaLangObject(argTypes[0])) {
431                     error("first argument of a @Getter method should be of Object type, found: " + argTypes[0]);
432                 }
433 
434                 if (Type.getReturnType(javaDesc).equals(Type.VOID_TYPE)) {
435                     error("return type of getter should not be void");
436                 }
437             }
438             break;
439             case SETTER: {
440                 final Type[] argTypes = Type.getArgumentTypes(javaDesc);
441                 if (argTypes.length != 2) {
442                     error("@Setter methods should have two arguments");
443                 }
444                 if (!isJavaLangObject(argTypes[0])) {
445                     error("first argument of a @Setter method should be of Object type, found: " + argTypes[0]);
446                 }
447                 if (!Type.getReturnType(javaDesc).toString().equals("V")) {
448                     error("return type of of a @Setter method should be void, found: " + Type.getReturnType(javaDesc));
449                 }
450             }
451             break;
452             case PROPERTY: {
453                 if (where == Where.CONSTRUCTOR) {
454                     if (isStatic()) {
455                         if (!isFinal()) {
456                             error("static Where.CONSTRUCTOR @Property should be final");
457                         }
458 
459                         if (!isJSPrimitiveType(Type.getType(javaDesc))) {
460                             error("static Where.CONSTRUCTOR @Property should be a JS primitive");
461                         }
462                     }
463                 } else if (where == Where.PROTOTYPE) {
464                     if (isStatic()) {
465                         if (!isFinal()) {
466                             error("static Where.PROTOTYPE @Property should be final");
467                         }
468 
469                         if (!isJSPrimitiveType(Type.getType(javaDesc))) {
470                             error("static Where.PROTOTYPE @Property should be a JS primitive");
471                         }
472                     }
473                 }
474             }
475             break;
476 
477             default:
478             break;
479         }
480     }
481 
482     /**
483      * Returns if the given (internal) name of a class represents a ScriptObject subtype.
484      */
isScriptObject(final String name)485     public static boolean isScriptObject(final String name) {
486         // very crude check for ScriptObject subtype!
487         if (name.startsWith(OBJ_PKG + "Native") ||
488             name.equals(OBJ_PKG + "Global") ||
489             name.equals(OBJ_PKG + "ArrayBufferView")) {
490             return true;
491         }
492 
493         if (name.startsWith(RUNTIME_PKG)) {
494             final String simpleName = name.substring(name.lastIndexOf('/') + 1);
495             switch (simpleName) {
496                 case "ScriptObject":
497                 case "ScriptFunction":
498                 case "NativeJavaPackage":
499                 case "Scope":
500                     return true;
501             }
502         }
503 
504         if (name.startsWith(SCRIPTS_PKG)) {
505             final String simpleName = name.substring(name.lastIndexOf('/') + 1);
506             switch (simpleName) {
507                 case "JD":
508                 case "JO":
509                     return true;
510             }
511         }
512 
513         return false;
514     }
515 
isValidJSType(final Type type)516     private static boolean isValidJSType(final Type type) {
517         return isJSPrimitiveType(type) || isJSObjectType(type);
518     }
519 
isJSPrimitiveType(final Type type)520     private static boolean isJSPrimitiveType(final Type type) {
521         switch (type.getSort()) {
522             case Type.BOOLEAN:
523             case Type.INT:
524             case Type.DOUBLE:
525                 return true;
526             default:
527                 return type != TYPE_SYMBOL;
528         }
529     }
530 
isJSObjectType(final Type type)531     private static boolean isJSObjectType(final Type type) {
532         return isJavaLangObject(type) || isJavaLangString(type) || isScriptObject(type);
533     }
534 
isJavaLangObject(final Type type)535     private static boolean isJavaLangObject(final Type type) {
536         return type.getDescriptor().equals(OBJECT_DESC);
537     }
538 
isJavaLangString(final Type type)539     private static boolean isJavaLangString(final Type type) {
540         return type.getDescriptor().equals(STRING_DESC);
541     }
542 
isScriptObject(final Type type)543     private static boolean isScriptObject(final Type type) {
544         if (type.getSort() != Type.OBJECT) {
545             return false;
546         }
547 
548         return isScriptObject(type.getInternalName());
549     }
550 
error(final String msg)551     private void error(final String msg) {
552         throw new RuntimeException(javaName + " of type " + javaDesc + " : " + msg);
553     }
554 
555     /**
556      * @return the initClass
557      */
getInitClass()558     String getInitClass() {
559         return initClass;
560     }
561 
562     /**
563      * @param initClass the initClass to set
564      */
setInitClass(final String initClass)565     void setInitClass(final String initClass) {
566         this.initClass = initClass;
567     }
568 
569     @Override
clone()570     protected Object clone() {
571         try {
572             return super.clone();
573         } catch (final CloneNotSupportedException e) {
574             assert false : "clone not supported " + e;
575             return null;
576         }
577     }
578 
579     /**
580      * @return the arity
581      */
getArity()582     int getArity() {
583         return arity;
584     }
585 
586     /**
587      * @param arity the arity to set
588      */
setArity(final int arity)589     void setArity(final int arity) {
590         this.arity = arity;
591     }
592 
getDocumentationKey(final String objName)593     String getDocumentationKey(final String objName) {
594         if (kind == Kind.FUNCTION) {
595             final StringBuilder buf = new StringBuilder(objName);
596             switch (where) {
597                 case CONSTRUCTOR:
598                     break;
599                 case PROTOTYPE:
600                     buf.append(".prototype");
601                     break;
602                 case INSTANCE:
603                     buf.append(".this");
604                     break;
605             }
606             buf.append('.');
607             buf.append(name);
608             return buf.toString();
609         }
610 
611         return null;
612     }
613 }
614