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 }