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      */
 118     public Kind getKind() {
 119         return kind;
 120     }
 121 
 122     /**
 123      * @param kind the kind to set
 124      */
 125     public void setKind(final Kind kind) {
 126         this.kind = kind;
 127     }
 128 
 129     /**
 130      * @return the name
 131      */
 132     public String getName() {
 133         return name;
 134     }
 135 
 136     /**
 137      * @param name the name to set
 138      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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 
 207     public void setLinkLogicClass(final Type linkLogicClass) {
 208         this.linkLogicClass = linkLogicClass;
 209     }
 210 
 211     /**
 212      * @return the attributes
 213      */
 214     public int getAttributes() {
 215         return attributes;
 216     }
 217 
 218     /**
 219      * @param attributes the attributes to set
 220      */
 221     public void setAttributes(final int attributes) {
 222         this.attributes = attributes;
 223     }
 224 
 225     /**
 226      * @return the javaName
 227      */
 228     public String getJavaName() {
 229         return javaName;
 230     }
 231 
 232     /**
 233      * @param javaName the javaName to set
 234      */
 235     public void setJavaName(final String javaName) {
 236         this.javaName = javaName;
 237     }
 238 
 239     /**
 240      * @return the javaDesc
 241      */
 242     public String getJavaDesc() {
 243         return javaDesc;
 244     }
 245 
 246     void setJavaDesc(final String javaDesc) {
 247         this.javaDesc = javaDesc;
 248     }
 249 
 250     int getJavaAccess() {
 251         return javaAccess;
 252     }
 253 
 254     void setJavaAccess(final int access) {
 255         this.javaAccess = access;
 256     }
 257 
 258     Object getValue() {
 259         return value;
 260     }
 261 
 262     void setValue(final Object value) {
 263         this.value = value;
 264     }
 265 
 266     Where getWhere() {
 267         return where;
 268     }
 269 
 270     void setWhere(final Where where) {
 271         this.where = where;
 272     }
 273 
 274     boolean isFinal() {
 275         return (javaAccess & Opcodes.ACC_FINAL) != 0;
 276     }
 277 
 278     boolean isStatic() {
 279         return (javaAccess & Opcodes.ACC_STATIC) != 0;
 280     }
 281 
 282     boolean isStaticFinal() {
 283         return isStatic() && isFinal();
 284     }
 285 
 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      */
 295     boolean isInstanceSetter() {
 296         return kind == Kind.SETTER && where == Where.INSTANCE;
 297     }
 298 
 299     boolean isInstanceProperty() {
 300         return kind == Kind.PROPERTY && where == Where.INSTANCE;
 301     }
 302 
 303     boolean isInstanceFunction() {
 304         return kind == Kind.FUNCTION && where == Where.INSTANCE;
 305     }
 306 
 307     boolean isPrototypeGetter() {
 308         return kind == Kind.GETTER && where == Where.PROTOTYPE;
 309     }
 310 
 311     boolean isPrototypeSetter() {
 312         return kind == Kind.SETTER && where == Where.PROTOTYPE;
 313     }
 314 
 315     boolean isPrototypeProperty() {
 316         return kind == Kind.PROPERTY && where == Where.PROTOTYPE;
 317     }
 318 
 319     boolean isPrototypeFunction() {
 320         return kind == Kind.FUNCTION && where == Where.PROTOTYPE;
 321     }
 322 
 323     boolean isConstructorGetter() {
 324         return kind == Kind.GETTER && where == Where.CONSTRUCTOR;
 325     }
 326 
 327     boolean isConstructorSetter() {
 328         return kind == Kind.SETTER && where == Where.CONSTRUCTOR;
 329     }
 330 
 331     boolean isConstructorProperty() {
 332         return kind == Kind.PROPERTY && where == Where.CONSTRUCTOR;
 333     }
 334 
 335     boolean isConstructorFunction() {
 336         return kind == Kind.FUNCTION && where == Where.CONSTRUCTOR;
 337     }
 338 
 339     boolean isConstructor() {
 340         return kind == Kind.CONSTRUCTOR;
 341     }
 342 
 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      */
 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 
 516     private static boolean isValidJSType(final Type type) {
 517         return isJSPrimitiveType(type) || isJSObjectType(type);
 518     }
 519 
 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 
 531     private static boolean isJSObjectType(final Type type) {
 532         return isJavaLangObject(type) || isJavaLangString(type) || isScriptObject(type);
 533     }
 534 
 535     private static boolean isJavaLangObject(final Type type) {
 536         return type.getDescriptor().equals(OBJECT_DESC);
 537     }
 538 
 539     private static boolean isJavaLangString(final Type type) {
 540         return type.getDescriptor().equals(STRING_DESC);
 541     }
 542 
 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 
 551     private void error(final String msg) {
 552         throw new RuntimeException(javaName + " of type " + javaDesc + " : " + msg);
 553     }
 554 
 555     /**
 556      * @return the initClass
 557      */
 558     String getInitClass() {
 559         return initClass;
 560     }
 561 
 562     /**
 563      * @param initClass the initClass to set
 564      */
 565     void setInitClass(final String initClass) {
 566         this.initClass = initClass;
 567     }
 568 
 569     @Override
 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      */
 582     int getArity() {
 583         return arity;
 584     }
 585 
 586     /**
 587      * @param arity the arity to set
 588      */
 589     void setArity(final int arity) {
 590         this.arity = arity;
 591     }
 592 
 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 }