001 /* 002 $Id: Verifier.java,v 1.40 2005/06/10 09:55:31 cstein Exp $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 package org.codehaus.groovy.classgen; 047 048 import groovy.lang.Closure; 049 import groovy.lang.GString; 050 import groovy.lang.GroovyObject; 051 import groovy.lang.MetaClass; 052 053 import java.lang.reflect.Modifier; 054 import java.util.ArrayList; 055 import java.util.Iterator; 056 import java.util.List; 057 058 import org.codehaus.groovy.ast.ClassNode; 059 import org.codehaus.groovy.ast.CodeVisitorSupport; 060 import org.codehaus.groovy.ast.ConstructorNode; 061 import org.codehaus.groovy.ast.FieldNode; 062 import org.codehaus.groovy.ast.GroovyClassVisitor; 063 import org.codehaus.groovy.ast.InnerClassNode; 064 import org.codehaus.groovy.ast.MethodNode; 065 import org.codehaus.groovy.ast.Parameter; 066 import org.codehaus.groovy.ast.PropertyNode; 067 import org.codehaus.groovy.ast.expr.ArgumentListExpression; 068 import org.codehaus.groovy.ast.expr.BinaryExpression; 069 import org.codehaus.groovy.ast.expr.BooleanExpression; 070 import org.codehaus.groovy.ast.expr.ClosureExpression; 071 import org.codehaus.groovy.ast.expr.ConstantExpression; 072 import org.codehaus.groovy.ast.expr.Expression; 073 import org.codehaus.groovy.ast.expr.FieldExpression; 074 import org.codehaus.groovy.ast.expr.MethodCallExpression; 075 import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; 076 import org.codehaus.groovy.ast.expr.VariableExpression; 077 import org.codehaus.groovy.ast.stmt.BlockStatement; 078 import org.codehaus.groovy.ast.stmt.EmptyStatement; 079 import org.codehaus.groovy.ast.stmt.ExpressionStatement; 080 import org.codehaus.groovy.ast.stmt.IfStatement; 081 import org.codehaus.groovy.ast.stmt.ReturnStatement; 082 import org.codehaus.groovy.ast.stmt.Statement; 083 import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; 084 import org.codehaus.groovy.syntax.Types; 085 import org.codehaus.groovy.syntax.Token; 086 import org.codehaus.groovy.syntax.RuntimeParserException; 087 import org.objectweb.asm.Opcodes; 088 089 /** 090 * Verifies the AST node and adds any defaulted AST code before 091 * bytecode generation occurs. 092 * 093 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 094 * @version $Revision: 1.40 $ 095 */ 096 public class Verifier implements GroovyClassVisitor, Opcodes { 097 098 public static final String __TIMESTAMP = "__timeStamp"; 099 private ClassNode classNode; 100 private MethodNode methodNode; 101 102 public ClassNode getClassNode() { 103 return classNode; 104 } 105 106 public MethodNode getMethodNode() { 107 return methodNode; 108 } 109 110 /** 111 * add code to implement GroovyObject 112 * @param node 113 */ 114 public void visitClass(ClassNode node) { 115 this.classNode = node; 116 117 if ((classNode.getModifiers() & Opcodes.ACC_INTERFACE) >0) { 118 node.visitContents(this); 119 return; 120 } 121 122 addDefaultParameterMethods(node); 123 124 if (!node.isDerivedFromGroovyObject()) { 125 node.addInterface(GroovyObject.class.getName()); 126 127 // lets add a new field for the metaclass 128 StaticMethodCallExpression initMetaClassCall = 129 new StaticMethodCallExpression( 130 ScriptBytecodeAdapter.class.getName(), 131 "getMetaClass", 132 VariableExpression.THIS_EXPRESSION); 133 134 PropertyNode metaClassProperty = 135 node.addProperty("metaClass", ACC_PUBLIC, MetaClass.class.getName(), initMetaClassCall, null, null); 136 metaClassProperty.setSynthetic(true); 137 FieldNode metaClassField = metaClassProperty.getField(); 138 metaClassField.setModifiers(metaClassField.getModifiers() | ACC_TRANSIENT); 139 140 FieldExpression metaClassVar = new FieldExpression(metaClassField); 141 IfStatement initMetaClassField = 142 new IfStatement( 143 new BooleanExpression( 144 new BinaryExpression(metaClassVar, Token.newSymbol( Types.COMPARE_EQUAL, -1, -1), ConstantExpression.NULL)), 145 new ExpressionStatement(new BinaryExpression(metaClassVar, Token.newSymbol( Types.EQUAL, -1, -1), initMetaClassCall)), 146 EmptyStatement.INSTANCE); 147 148 node.addSyntheticMethod( 149 "getMetaClass", 150 ACC_PUBLIC, 151 MetaClass.class.getName(), 152 Parameter.EMPTY_ARRAY, 153 new BlockStatement(new Statement[] { initMetaClassField, new ReturnStatement(metaClassVar)})); 154 155 // @todo we should check if the base class implements the invokeMethod method 156 157 // lets add the invokeMethod implementation 158 String superClass = node.getSuperClass(); 159 boolean addDelegateObject = 160 (node instanceof InnerClassNode && superClass.equals(Closure.class.getName())) 161 || superClass.equals(GString.class.getName()); 162 163 // don't do anything as the base class implements the invokeMethod 164 if (!addDelegateObject) { 165 node.addSyntheticMethod( 166 "invokeMethod", 167 ACC_PUBLIC, 168 Object.class.getName(), 169 new Parameter[] { 170 new Parameter(String.class.getName(), "method"), 171 new Parameter(Object.class.getName(), "arguments")}, 172 new BlockStatement( 173 new Statement[] { 174 initMetaClassField, 175 new ReturnStatement( 176 new MethodCallExpression( 177 metaClassVar, 178 "invokeMethod", 179 new ArgumentListExpression( 180 new Expression[] { 181 VariableExpression.THIS_EXPRESSION, 182 new VariableExpression("method"), 183 new VariableExpression("arguments")}))) 184 })); 185 186 if (!node.isScript()) { 187 node.addSyntheticMethod( 188 "getProperty", 189 ACC_PUBLIC, 190 Object.class.getName(), 191 new Parameter[] { new Parameter(String.class.getName(), "property")}, 192 new BlockStatement( 193 new Statement[] { 194 initMetaClassField, 195 new ReturnStatement( 196 new MethodCallExpression( 197 metaClassVar, 198 "getProperty", 199 new ArgumentListExpression( 200 new Expression[] { 201 VariableExpression.THIS_EXPRESSION, 202 new VariableExpression("property")}))) 203 })); 204 205 node.addSyntheticMethod( 206 "setProperty", 207 ACC_PUBLIC, 208 "void", 209 new Parameter[] { 210 new Parameter(String.class.getName(), "property"), 211 new Parameter(Object.class.getName(), "value")}, 212 new BlockStatement( 213 new Statement[] { 214 initMetaClassField, 215 new ExpressionStatement( 216 new MethodCallExpression( 217 metaClassVar, 218 "setProperty", 219 new ArgumentListExpression( 220 new Expression[] { 221 VariableExpression.THIS_EXPRESSION, 222 new VariableExpression("property"), 223 new VariableExpression("value")}))) 224 })); 225 } 226 } 227 } 228 229 if (node.getDeclaredConstructors().isEmpty()) { 230 ConstructorNode constructor = new ConstructorNode(ACC_PUBLIC, null); 231 constructor.setSynthetic(true); 232 node.addConstructor(constructor); 233 } 234 235 if (!(node instanceof InnerClassNode)) {// add a static timestamp field to the class 236 FieldNode timeTagField = new FieldNode( 237 Verifier.__TIMESTAMP, 238 Modifier.PUBLIC | Modifier.STATIC, 239 "java.lang.Long", 240 //"", 241 node.getName(), 242 new ConstantExpression(new Long(System.currentTimeMillis()))); 243 // alternatively , FieldNode timeTagField = SourceUnit.createFieldNode("public static final long __timeStamp = " + System.currentTimeMillis() + "L"); 244 timeTagField.setSynthetic(true); 245 node.addField(timeTagField); 246 } 247 248 addFieldInitialization(node); 249 250 node.visitContents(this); 251 } 252 public void visitConstructor(ConstructorNode node) { 253 CodeVisitorSupport checkSuper = new CodeVisitorSupport() { 254 boolean firstMethodCall = true; 255 String type=null; 256 public void visitMethodCallExpression(MethodCallExpression call) { 257 if (!firstMethodCall) return; 258 firstMethodCall = false; 259 String name = call.getMethod(); 260 if (!name.equals("super") && !name.equals("this")) return; 261 type=name; 262 call.getArguments().visit(this); 263 type=null; 264 } 265 public void visitVariableExpression(VariableExpression expression) { 266 if (type==null) return; 267 String name = expression.getVariable(); 268 if (!name.equals("this") && !name.equals("super")) return; 269 throw new RuntimeParserException("cannot reference "+name+" inside of "+type+"(....) before supertype constructor has been called",expression); 270 } 271 }; 272 Statement s = node.getCode(); 273 //todo why can a statement can be null? 274 if (s == null) return; 275 s.visit(checkSuper); 276 } 277 278 public void visitMethod(MethodNode node) { 279 this.methodNode = node; 280 Statement statement = node.getCode(); 281 if (!node.isVoidMethod()) { 282 if (statement instanceof ExpressionStatement) { 283 ExpressionStatement expStmt = (ExpressionStatement) statement; 284 node.setCode(new ReturnStatement(expStmt.getExpression())); 285 } 286 else if (statement instanceof BlockStatement) { 287 BlockStatement block = (BlockStatement) statement; 288 289 // lets copy the list so we create a new block 290 List list = new ArrayList(block.getStatements()); 291 if (!list.isEmpty()) { 292 int idx = list.size() - 1; 293 Statement last = (Statement) list.get(idx); 294 if (last instanceof ExpressionStatement) { 295 ExpressionStatement expStmt = (ExpressionStatement) last; 296 list.set(idx, new ReturnStatement(expStmt.getExpression())); 297 } 298 else if (!(last instanceof ReturnStatement)) { 299 list.add(new ReturnStatement(ConstantExpression.NULL)); 300 } 301 } 302 else { 303 list.add(new ReturnStatement(ConstantExpression.NULL)); 304 } 305 306 node.setCode(new BlockStatement(filterStatements(list))); 307 } 308 } 309 else if (!node.isAbstract()) { 310 BlockStatement newBlock = new BlockStatement(); 311 if (statement instanceof BlockStatement) { 312 newBlock.addStatements(filterStatements(((BlockStatement)statement).getStatements())); 313 } 314 else { 315 newBlock.addStatement(filterStatement(statement)); 316 } 317 newBlock.addStatement(ReturnStatement.RETURN_NULL_OR_VOID); 318 node.setCode(newBlock); 319 } 320 if (node.getName().equals("main") && node.isStatic()) { 321 Parameter[] params = node.getParameters(); 322 if (params.length == 1) { 323 Parameter param = params[0]; 324 if (param.getType() == null || param.getType().equals("java.lang.Object")) { 325 param.setType("java.lang.String[]"); 326 } 327 } 328 } 329 statement = node.getCode(); 330 if (statement!=null) statement.visit(new VerifierCodeVisitor(this)); 331 } 332 333 public void visitField(FieldNode node) { 334 } 335 336 public void visitProperty(PropertyNode node) { 337 String name = node.getName(); 338 FieldNode field = node.getField(); 339 340 341 String getterPrefix = "get"; 342 if ("boolean".equals(node.getType())) { 343 getterPrefix = "is"; 344 } 345 String getterName = getterPrefix + capitalize(name); 346 String setterName = "set" + capitalize(name); 347 348 Statement getterBlock = node.getGetterBlock(); 349 if (getterBlock == null) { 350 if (!node.isPrivate() && classNode.getGetterMethod(getterName) == null) { 351 getterBlock = createGetterBlock(node, field); 352 } 353 } 354 Statement setterBlock = node.getGetterBlock(); 355 if (setterBlock == null) { 356 if (!node.isPrivate() && classNode.getSetterMethod(setterName) == null) { 357 setterBlock = createSetterBlock(node, field); 358 } 359 } 360 361 if (getterBlock != null) { 362 MethodNode getter = 363 new MethodNode(getterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock); 364 getter.setSynthetic(true); 365 classNode.addMethod(getter); 366 visitMethod(getter); 367 368 if ("java.lang.Boolean".equals(node.getType())) { 369 String secondGetterName = "is" + capitalize(name); 370 MethodNode secondGetter = 371 new MethodNode(secondGetterName, node.getModifiers(), node.getType(), Parameter.EMPTY_ARRAY, getterBlock); 372 secondGetter.setSynthetic(true); 373 classNode.addMethod(secondGetter); 374 visitMethod(secondGetter); 375 } 376 } 377 if (setterBlock != null) { 378 Parameter[] setterParameterTypes = { new Parameter(node.getType(), "value")}; 379 MethodNode setter = 380 new MethodNode(setterName, node.getModifiers(), "void", setterParameterTypes, setterBlock); 381 setter.setSynthetic(true); 382 classNode.addMethod(setter); 383 visitMethod(setter); 384 } 385 } 386 387 // Implementation methods 388 //------------------------------------------------------------------------- 389 390 /** 391 * Creates a new helper method for each combination of default parameter expressions 392 */ 393 protected void addDefaultParameterMethods(ClassNode node) { 394 List methods = new ArrayList(node.getMethods()); 395 for (Iterator iter = methods.iterator(); iter.hasNext();) { 396 MethodNode method = (MethodNode) iter.next(); 397 Parameter[] parameters = method.getParameters(); 398 int size = parameters.length; 399 for (int i = 0; i < size; i++) { 400 Parameter parameter = parameters[i]; 401 Expression exp = parameter.getDefaultValue(); 402 if (exp != null) { 403 addDefaultParameterMethod(node, method, parameters, i); 404 } 405 } 406 } 407 } 408 409 /** 410 * Adds a new method which defaults the values for all the parameters starting 411 * from and including the given index 412 * 413 * @param node the class to add the method 414 * @param method the given method to add a helper of 415 * @param parameters the parameters of the method to add a helper for 416 * @param index the index of the first default value expression parameter to use 417 */ 418 protected void addDefaultParameterMethod(ClassNode node, MethodNode method, Parameter[] parameters, int index) { 419 // lets create a method using this expression 420 Parameter[] newParams = new Parameter[index]; 421 System.arraycopy(parameters, 0, newParams, 0, index); 422 423 ArgumentListExpression arguments = new ArgumentListExpression(); 424 int size = parameters.length; 425 for (int i = 0; i < size; i++) { 426 if (i < index) { 427 arguments.addExpression(new VariableExpression(parameters[i].getName())); 428 } 429 else { 430 Expression defaultValue = parameters[i].getDefaultValue(); 431 if (defaultValue == null) { 432 throw new RuntimeParserException( 433 "The " + parameters[i].getName() + " parameter must have a default value", 434 method); 435 } 436 else { 437 arguments.addExpression(defaultValue); 438 } 439 } 440 } 441 442 MethodCallExpression expression = 443 new MethodCallExpression(VariableExpression.THIS_EXPRESSION, method.getName(), arguments); 444 Statement code = null; 445 if (method.isVoidMethod()) { 446 code = new ExpressionStatement(expression); 447 } 448 else { 449 code = new ReturnStatement(expression); 450 } 451 452 node.addMethod(method.getName(), method.getModifiers(), method.getReturnType(), newParams, code); 453 } 454 455 protected void addClosureCode(InnerClassNode node) { 456 // add a new invoke 457 } 458 459 protected void addFieldInitialization(ClassNode node) { 460 for (Iterator iter = node.getDeclaredConstructors().iterator(); iter.hasNext();) { 461 addFieldInitialization(node, (ConstructorNode) iter.next()); 462 } 463 } 464 465 protected void addFieldInitialization(ClassNode node, ConstructorNode constructorNode) { 466 List statements = new ArrayList(); 467 List staticStatements = new ArrayList(); 468 for (Iterator iter = node.getFields().iterator(); iter.hasNext();) { 469 addFieldInitialization(statements, staticStatements, constructorNode, (FieldNode) iter.next()); 470 } 471 if (!statements.isEmpty()) { 472 Statement code = constructorNode.getCode(); 473 List otherStatements = new ArrayList(); 474 if (code instanceof BlockStatement) { 475 BlockStatement block = (BlockStatement) code; 476 otherStatements.addAll(block.getStatements()); 477 } 478 else if (code != null) { 479 otherStatements.add(code); 480 } 481 if (!otherStatements.isEmpty()) { 482 Statement first = (Statement) otherStatements.get(0); 483 if (isSuperMethodCall(first)) { 484 otherStatements.remove(0); 485 statements.add(0, first); 486 } 487 statements.addAll(otherStatements); 488 } 489 constructorNode.setCode(new BlockStatement(statements)); 490 } 491 492 if (!staticStatements.isEmpty()) { 493 node.addStaticInitializerStatements(staticStatements); 494 } 495 } 496 497 protected void addFieldInitialization( 498 List list, 499 List staticList, 500 ConstructorNode constructorNode, 501 FieldNode fieldNode) { 502 Expression expression = fieldNode.getInitialValueExpression(); 503 if (expression != null) { 504 ExpressionStatement statement = 505 new ExpressionStatement( 506 new BinaryExpression( 507 new FieldExpression(fieldNode), 508 Token.newSymbol(Types.EQUAL, fieldNode.getLineNumber(), fieldNode.getColumnNumber()), 509 expression)); 510 if (fieldNode.isStatic()) { 511 staticList.add(statement); 512 } 513 else { 514 list.add(statement); 515 } 516 } 517 } 518 519 protected boolean isSuperMethodCall(Statement first) { 520 if (first instanceof ExpressionStatement) { 521 ExpressionStatement exprStmt = (ExpressionStatement) first; 522 Expression expr = exprStmt.getExpression(); 523 if (expr instanceof MethodCallExpression) { 524 return MethodCallExpression.isSuperMethodCall((MethodCallExpression) expr); 525 } 526 } 527 return false; 528 } 529 530 /** 531 * Capitalizes the start of the given bean property name 532 */ 533 public static String capitalize(String name) { 534 return name.substring(0, 1).toUpperCase() + name.substring(1, name.length()); 535 } 536 537 protected Statement createGetterBlock(PropertyNode propertyNode, FieldNode field) { 538 Expression expression = new FieldExpression(field); 539 return new ReturnStatement(expression); 540 } 541 542 protected Statement createSetterBlock(PropertyNode propertyNode, FieldNode field) { 543 Expression expression = new FieldExpression(field); 544 return new ExpressionStatement( 545 new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("value"))); 546 } 547 548 /** 549 * Filters the given statements 550 */ 551 protected List filterStatements(List list) { 552 List answer = new ArrayList(list.size()); 553 for (Iterator iter = list.iterator(); iter.hasNext();) { 554 answer.add(filterStatement((Statement) iter.next())); 555 } 556 return answer; 557 } 558 559 protected Statement filterStatement(Statement statement) { 560 if (statement instanceof ExpressionStatement) { 561 ExpressionStatement expStmt = (ExpressionStatement) statement; 562 Expression expression = expStmt.getExpression(); 563 if (expression instanceof ClosureExpression) { 564 ClosureExpression closureExp = (ClosureExpression) expression; 565 if (!closureExp.isParameterSpecified()) { 566 return closureExp.getCode(); 567 } 568 } 569 } 570 return statement; 571 } 572 }