| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.dev.jjs.impl; |
| |
| import com.google.gwt.dev.javac.ArtificialRescueChecker.RescueData; |
| import com.google.gwt.dev.javac.JsniCollector; |
| import com.google.gwt.dev.jjs.InternalCompilerException; |
| import com.google.gwt.dev.jjs.JJSOptions; |
| import com.google.gwt.dev.jjs.SourceInfo; |
| import com.google.gwt.dev.jjs.SourceOrigin; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JArrayLength; |
| import com.google.gwt.dev.jjs.ast.JArrayRef; |
| import com.google.gwt.dev.jjs.ast.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JAssertStatement; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JBooleanLiteral; |
| import com.google.gwt.dev.jjs.ast.JBreakStatement; |
| import com.google.gwt.dev.jjs.ast.JCaseStatement; |
| import com.google.gwt.dev.jjs.ast.JCastOperation; |
| import com.google.gwt.dev.jjs.ast.JCharLiteral; |
| import com.google.gwt.dev.jjs.ast.JClassLiteral; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JConditional; |
| import com.google.gwt.dev.jjs.ast.JConstructor; |
| import com.google.gwt.dev.jjs.ast.JContinueStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JDoStatement; |
| import com.google.gwt.dev.jjs.ast.JDoubleLiteral; |
| import com.google.gwt.dev.jjs.ast.JEnumField; |
| import com.google.gwt.dev.jjs.ast.JEnumType; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JExpressionStatement; |
| import com.google.gwt.dev.jjs.ast.JField; |
| import com.google.gwt.dev.jjs.ast.JField.Disposition; |
| import com.google.gwt.dev.jjs.ast.JFieldRef; |
| import com.google.gwt.dev.jjs.ast.JFloatLiteral; |
| import com.google.gwt.dev.jjs.ast.JForStatement; |
| import com.google.gwt.dev.jjs.ast.JIfStatement; |
| import com.google.gwt.dev.jjs.ast.JInstanceOf; |
| import com.google.gwt.dev.jjs.ast.JIntLiteral; |
| import com.google.gwt.dev.jjs.ast.JInterfaceType; |
| import com.google.gwt.dev.jjs.ast.JLabel; |
| import com.google.gwt.dev.jjs.ast.JLabeledStatement; |
| import com.google.gwt.dev.jjs.ast.JLiteral; |
| import com.google.gwt.dev.jjs.ast.JLocal; |
| import com.google.gwt.dev.jjs.ast.JLocalRef; |
| import com.google.gwt.dev.jjs.ast.JLongLiteral; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JNewArray; |
| import com.google.gwt.dev.jjs.ast.JNewInstance; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JNonNullType; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JParameterRef; |
| import com.google.gwt.dev.jjs.ast.JPostfixOperation; |
| import com.google.gwt.dev.jjs.ast.JPrefixOperation; |
| import com.google.gwt.dev.jjs.ast.JPrimitiveType; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JReturnStatement; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JStringLiteral; |
| import com.google.gwt.dev.jjs.ast.JSwitchStatement; |
| import com.google.gwt.dev.jjs.ast.JThisRef; |
| import com.google.gwt.dev.jjs.ast.JThrowStatement; |
| import com.google.gwt.dev.jjs.ast.JTryStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JTypeOracle; |
| import com.google.gwt.dev.jjs.ast.JUnaryOperator; |
| import com.google.gwt.dev.jjs.ast.JVariable; |
| import com.google.gwt.dev.jjs.ast.JVariableRef; |
| import com.google.gwt.dev.jjs.ast.JWhileStatement; |
| import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral; |
| import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodBody; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; |
| import com.google.gwt.dev.js.ast.JsContext; |
| import com.google.gwt.dev.js.ast.JsExpression; |
| import com.google.gwt.dev.js.ast.JsModVisitor; |
| import com.google.gwt.dev.js.ast.JsNameRef; |
| import com.google.gwt.dev.util.JsniRef; |
| import com.google.gwt.dev.util.collect.Maps; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| |
| import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression; |
| import org.eclipse.jdt.internal.compiler.ast.ASTNode; |
| import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; |
| import org.eclipse.jdt.internal.compiler.ast.Annotation; |
| import org.eclipse.jdt.internal.compiler.ast.ArrayAllocationExpression; |
| import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; |
| import org.eclipse.jdt.internal.compiler.ast.ArrayReference; |
| import org.eclipse.jdt.internal.compiler.ast.AssertStatement; |
| import org.eclipse.jdt.internal.compiler.ast.Assignment; |
| import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; |
| import org.eclipse.jdt.internal.compiler.ast.Block; |
| import org.eclipse.jdt.internal.compiler.ast.BreakStatement; |
| import org.eclipse.jdt.internal.compiler.ast.CaseStatement; |
| import org.eclipse.jdt.internal.compiler.ast.CastExpression; |
| import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; |
| import org.eclipse.jdt.internal.compiler.ast.CombinedBinaryExpression; |
| import org.eclipse.jdt.internal.compiler.ast.CompoundAssignment; |
| import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; |
| import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.ContinueStatement; |
| import org.eclipse.jdt.internal.compiler.ast.DoStatement; |
| import org.eclipse.jdt.internal.compiler.ast.EmptyStatement; |
| import org.eclipse.jdt.internal.compiler.ast.EqualExpression; |
| import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; |
| import org.eclipse.jdt.internal.compiler.ast.Expression; |
| import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.FieldReference; |
| import org.eclipse.jdt.internal.compiler.ast.ForStatement; |
| import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; |
| import org.eclipse.jdt.internal.compiler.ast.IfStatement; |
| import org.eclipse.jdt.internal.compiler.ast.Initializer; |
| import org.eclipse.jdt.internal.compiler.ast.InstanceOfExpression; |
| import org.eclipse.jdt.internal.compiler.ast.LabeledStatement; |
| import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.MessageSend; |
| import org.eclipse.jdt.internal.compiler.ast.NullLiteral; |
| import org.eclipse.jdt.internal.compiler.ast.OR_OR_Expression; |
| import org.eclipse.jdt.internal.compiler.ast.OperatorIds; |
| import org.eclipse.jdt.internal.compiler.ast.PostfixExpression; |
| import org.eclipse.jdt.internal.compiler.ast.PrefixExpression; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedAllocationExpression; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedSuperReference; |
| import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference; |
| import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; |
| import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; |
| import org.eclipse.jdt.internal.compiler.ast.Statement; |
| import org.eclipse.jdt.internal.compiler.ast.SuperReference; |
| import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; |
| import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; |
| import org.eclipse.jdt.internal.compiler.ast.ThisReference; |
| import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; |
| import org.eclipse.jdt.internal.compiler.ast.TryStatement; |
| import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; |
| import org.eclipse.jdt.internal.compiler.ast.UnaryExpression; |
| import org.eclipse.jdt.internal.compiler.ast.WhileStatement; |
| import org.eclipse.jdt.internal.compiler.impl.BooleanConstant; |
| import org.eclipse.jdt.internal.compiler.impl.ByteConstant; |
| import org.eclipse.jdt.internal.compiler.impl.CharConstant; |
| import org.eclipse.jdt.internal.compiler.impl.Constant; |
| import org.eclipse.jdt.internal.compiler.impl.DoubleConstant; |
| import org.eclipse.jdt.internal.compiler.impl.FloatConstant; |
| import org.eclipse.jdt.internal.compiler.impl.IntConstant; |
| import org.eclipse.jdt.internal.compiler.impl.LongConstant; |
| import org.eclipse.jdt.internal.compiler.impl.ShortConstant; |
| import org.eclipse.jdt.internal.compiler.impl.StringConstant; |
| import org.eclipse.jdt.internal.compiler.lookup.Binding; |
| import org.eclipse.jdt.internal.compiler.lookup.ClassScope; |
| import org.eclipse.jdt.internal.compiler.lookup.FieldBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.MethodScope; |
| import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SyntheticArgumentBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.SyntheticMethodBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeIds; |
| import org.eclipse.jdt.internal.compiler.lookup.VariableBinding; |
| import org.eclipse.jdt.internal.compiler.util.Util; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * This is the big kahuna where most of the nitty gritty of creating our AST |
| * happens. BuildTypeMap must have already run so we have valid mappings between |
| * JDT nodes and our already-created AST nodes. |
| */ |
| public class GenerateJavaAST { |
| |
| /** |
| * Visit the JDT AST and produce our own AST into the passed-in TypeMap's |
| * JProgram. By the end of this pass, the produced AST should contain every |
| * piece of information we'll ever need about the code. The JDT nodes should |
| * never again be referenced after this. |
| * |
| * This is implemented as a reflective visitor for JDT's AST. The advantage of |
| * doing it reflectively is that if we run into any JDT nodes we can't handle, |
| * we'll automatically throw an exception. If we had subclassed |
| * {@link org.eclipse.jdt.internal.compiler.ast.ASTNode} we'd have to override |
| * every single method and explicitly throw an exception to get the same |
| * behavior. |
| * |
| * NOTE ON JDT FORCED OPTIMIZATIONS - If JDT statically determines that a |
| * section of code in unreachable, it won't fully resolve that section of |
| * code. This invalid-state code causes us major problems. As a result, we |
| * have to optimize out those dead blocks early and never try to translate |
| * them to our AST. |
| */ |
| // Reflective invocation causes unused warnings. |
| @SuppressWarnings("unused") |
| private static class JavaASTGenerationVisitor { |
| |
| /** |
| * Used to cache {@link Method} lookups. |
| */ |
| private static class MethodKey { |
| private final Class<? extends Object> childClass; |
| private final String name; |
| |
| public MethodKey(String name, Class<? extends Object> childClass) { |
| this.name = name; |
| this.childClass = childClass; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof MethodKey) { |
| MethodKey otherKey = (MethodKey) obj; |
| return name.equals(otherKey.name) && childClass.equals(otherKey.childClass); |
| } |
| return super.equals(obj); |
| } |
| |
| @Override |
| public int hashCode() { |
| return name.hashCode() + (101 * childClass.hashCode()); |
| } |
| } |
| |
| /** |
| * Used to cache {@link Method} lookups. |
| */ |
| private static class MethodValue { |
| private final NoSuchMethodException ex; |
| private final Method method; |
| |
| public MethodValue(Method method) { |
| this.method = method; |
| this.ex = null; |
| } |
| |
| public MethodValue(NoSuchMethodException ex) { |
| this.ex = ex; |
| this.method = null; |
| } |
| |
| public Method getMethod() throws NoSuchMethodException { |
| if (this.ex != null) { |
| throw (ex); |
| } |
| return method; |
| } |
| } |
| |
| /** |
| * The literal for the JLS identifier that represents the length field on an |
| * array. |
| */ |
| private static final String ARRAY_LENGTH_FIELD = "length"; |
| |
| private static InternalCompilerException translateException(JNode node, Throwable e) { |
| if (e instanceof VirtualMachineError) { |
| // Always rethrow VM errors (an attempt to wrap may fail). |
| throw (VirtualMachineError) e; |
| } |
| InternalCompilerException ice; |
| if (e instanceof InternalCompilerException) { |
| ice = (InternalCompilerException) e; |
| ice.addNode(node); |
| } else { |
| ice = new InternalCompilerException(node, "Error constructing Java AST", e); |
| } |
| return ice; |
| } |
| |
| private final AutoboxUtils autoboxUtils; |
| |
| private JDeclaredType currentClass; |
| |
| private ClassScope currentClassScope; |
| |
| private String currentFileName; |
| |
| private JMethod currentMethod; |
| |
| private JMethodBody currentMethodBody; |
| |
| private MethodScope currentMethodScope; |
| |
| private Map<JField, JParameter> currentOuterThisRefParams; |
| |
| private int[] currentSeparatorPositions; |
| |
| private final boolean disableClassMetadata; |
| |
| private final boolean enableAsserts; |
| |
| private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap = |
| new HashMap<JsniMethodBody, AbstractMethodDeclaration>(); |
| |
| private final Map<JMethod, Map<String, JLabel>> labelMap = |
| new IdentityHashMap<JMethod, Map<String, JLabel>>(); |
| |
| private final Map<MethodKey, MethodValue> methodCache = new HashMap<MethodKey, MethodValue>(); |
| |
| private final JProgram program; |
| |
| private final TypeMap typeMap; |
| |
| public JavaASTGenerationVisitor(TypeMap typeMap, JProgram program, JJSOptions options) { |
| this.typeMap = typeMap; |
| this.program = program; |
| this.enableAsserts = options.isEnableAssertions(); |
| |
| /* |
| * TODO: Determine if this should be controlled by a compiler flag or a |
| * module property. |
| */ |
| this.disableClassMetadata = options.isClassMetadataDisabled(); |
| autoboxUtils = new AutoboxUtils(program); |
| } |
| |
| /** |
| * <p> |
| * Add a bridge method to <code>clazzBinding</code> for any method it |
| * inherits that implements an interface method but that has a different |
| * erased signature from the interface method. |
| * </p> |
| * |
| * <p> |
| * The need for these bridges was pointed out in issue 3064. The goal is |
| * that virtual method calls through an interface type are translated to |
| * JavaScript that will function correctly. If the interface signature |
| * matches the signature of the implementing method, then nothing special |
| * needs to be done. If they are different, due to the use of generics, then |
| * GenerateJavaScriptAST is careful to do the right thing. There is a |
| * remaining case, though, that GenerateJavaScriptAST is not in a good |
| * position to fix: a method could be inherited from a superclass, used to |
| * implement an interface method that has a different type signature, and |
| * does not have the interface method in its list of overrides. In that |
| * case, a bridge method should be added that overrides the interface method |
| * and then calls the implementation method. |
| * </p> |
| * |
| * <p> |
| * This method should only be called once all regular, non-bridge methods |
| * have been installed on the GWT types. |
| * </p> |
| */ |
| public void addBridgeMethods(SourceTypeBinding clazzBinding) { |
| if (clazzBinding.isInterface()) { |
| // Only add bridges in classes, to simplify matters. |
| return; |
| } |
| |
| JClassType clazz = (JClassType) typeMap.get(clazzBinding); |
| |
| /* |
| * The JDT adds bridge methods in all the places GWT needs them. Look |
| * through the bridge methods the JDT added. |
| */ |
| if (clazzBinding.syntheticMethods() != null) { |
| for (SyntheticMethodBinding synthmeth : clazzBinding.syntheticMethods()) { |
| if (synthmeth.purpose == SyntheticMethodBinding.BridgeMethod && !synthmeth.isStatic()) { |
| JMethod implmeth = (JMethod) typeMap.get(synthmeth.targetMethod); |
| |
| createBridgeMethod(clazz, synthmeth, implmeth); |
| } |
| } |
| } |
| } |
| |
| public void processEnumType(JEnumType type) { |
| // Generate the synthetic values() method. |
| JField valuesField = null; |
| for (JMethod method : type.getMethods()) { |
| currentMethod = method; |
| if ("values".equals(method.getName())) { |
| if (method.getParams().size() != 0) { |
| continue; |
| } |
| currentMethodBody = (JMethodBody) method.getBody(); |
| valuesField = writeEnumValuesMethod(type); |
| } |
| currentMethodBody = null; |
| currentMethod = null; |
| } |
| // Generate the synthetic valueOf() method. |
| if (isScript(program)) { |
| for (JMethod method : type.getMethods()) { |
| currentMethod = method; |
| if ("valueOf".equals(method.getName())) { |
| if (method.getParams().size() != 1) { |
| continue; |
| } |
| if (method.getParams().get(0).getType() != program.getTypeJavaLangString()) { |
| continue; |
| } |
| currentMethodBody = (JMethodBody) method.getBody(); |
| writeEnumValueOfMethod(type, valuesField); |
| } |
| currentMethodBody = null; |
| currentMethod = null; |
| } |
| } |
| } |
| |
| /** |
| * We emulate static initializers and instance initializers as methods. As |
| * in other cases, this gives us: simpler AST, easier to optimize, more like |
| * output JavaScript. |
| */ |
| public void processType(TypeDeclaration x) { |
| currentClass = (JDeclaredType) typeMap.get(x.binding); |
| processArtificialRescues(x.annotations); |
| try { |
| currentClassScope = x.scope; |
| currentSeparatorPositions = x.compilationResult.lineSeparatorPositions; |
| currentFileName = String.valueOf(x.compilationResult.fileName); |
| |
| /* |
| * Make clinits chain to super class (JDT doesn't write code to do |
| * this). Call super class $clinit; $clinit is always in position 0. |
| */ |
| if (currentClass.getSuperClass() != null) { |
| JMethod myClinit = currentClass.getMethods().get(0); |
| JMethod superClinit = currentClass.getSuperClass().getMethods().get(0); |
| JMethodCall superClinitCall = |
| new JMethodCall(myClinit.getSourceInfo(), null, superClinit); |
| JMethodBody body = (JMethodBody) myClinit.getBody(); |
| body.getBlock().addStmt(0, superClinitCall.makeStatement()); |
| } |
| |
| if (x.fields != null) { |
| // Process fields |
| for (int i = 0, n = x.fields.length; i < n; ++i) { |
| FieldDeclaration fieldDeclaration = x.fields[i]; |
| if (fieldDeclaration.isStatic()) { |
| // clinit |
| currentMethod = currentClass.getMethods().get(0); |
| currentMethodBody = (JMethodBody) currentMethod.getBody(); |
| currentMethodScope = x.staticInitializerScope; |
| } else { |
| // init |
| currentMethod = currentClass.getMethods().get(1); |
| currentMethodBody = (JMethodBody) currentMethod.getBody(); |
| currentMethodScope = x.initializerScope; |
| } |
| |
| if (fieldDeclaration instanceof Initializer) { |
| assert (currentClass instanceof JClassType); |
| processInitializer((Initializer) fieldDeclaration); |
| } else { |
| processField(fieldDeclaration); |
| } |
| } |
| } |
| |
| currentMethodScope = null; |
| currentMethod = null; |
| |
| if (x.methods != null) { |
| // Process methods |
| for (int i = 0, n = x.methods.length; i < n; ++i) { |
| if (x.methods[i].isConstructor()) { |
| assert (currentClass instanceof JClassType); |
| processConstructor((ConstructorDeclaration) x.methods[i]); |
| } else if (x.methods[i].isClinit()) { |
| // nothing to do |
| } else { |
| processMethod(x.methods[i]); |
| } |
| } |
| } |
| |
| // Write the body of the getClass() override. |
| if (currentClass instanceof JClassType && currentClass != program.getTypeJavaLangObject()) { |
| JMethod method = currentClass.getMethods().get(2); |
| assert ("getClass".equals(method.getName())); |
| |
| if (program.isJavaScriptObject(currentClass) |
| && currentClass != program.getJavaScriptObject()) { |
| // Just use JavaScriptObject's implementation for all subclasses. |
| currentClass.getMethods().remove(2); |
| } else { |
| tryFindUpRefs(method); |
| SourceInfo info = method.getSourceInfo(); |
| if (isScript(program) && currentClass == program.getIndexedType("Array")) { |
| // Special implementation: return this.arrayClass |
| implementMethod(method, new JFieldRef(info, new JThisRef(info, |
| (JClassType) currentClass), program.getIndexedField("Array.arrayClass"), |
| currentClass)); |
| } else { |
| implementMethod(method, new JClassLiteral(info.makeChild(), currentClass)); |
| } |
| } |
| } |
| |
| // Reimplement GWT.isClient(), GWT.isProdMode(), GWT.isScript(). |
| if (currentClass == program.getIndexedType("GWT")) { |
| JMethod method = program.getIndexedMethod("GWT.isClient"); |
| implementMethod(method, program.getLiteralBoolean(true)); |
| |
| method = program.getIndexedMethod("GWT.isProdMode"); |
| implementMethod(method, program.getLiteralBoolean(true)); |
| |
| method = program.getIndexedMethod("GWT.isScript"); |
| implementMethod(method, program.getLiteralBoolean(isScript(program))); |
| } |
| |
| // Implement various methods on Class |
| if (currentClass == program.getTypeJavaLangClass()) { |
| JMethod method = program.getIndexedMethod("Class.desiredAssertionStatus"); |
| implementMethod(method, program.getLiteralBoolean(enableAsserts)); |
| |
| if (disableClassMetadata) { |
| JMethod isMetadataEnabledMethod = |
| program.getIndexedMethod("Class.isClassMetadataEnabled"); |
| implementMethod(isMetadataEnabledMethod, program.getLiteralBoolean(false)); |
| } |
| } |
| |
| if (currentClass instanceof JEnumType) { |
| processEnumType((JEnumType) currentClass); |
| } |
| |
| currentClassScope = null; |
| currentClass = null; |
| currentSeparatorPositions = null; |
| currentFileName = null; |
| } catch (Throwable e) { |
| throw translateException(currentClass, e); |
| } |
| } |
| |
| /** |
| * This is the guts of the "reflective" part of this visitor. Try to find a |
| * "process" method that exactly matches the run-time type of the argument. |
| */ |
| protected JNode dispatch(String name, Object child) { |
| if (child == null) { |
| return null; |
| } |
| |
| try { |
| Method method = getCachedMethod(name, child.getClass()); |
| return (JNode) method.invoke(this, child); |
| } catch (Throwable e) { |
| if (e instanceof InvocationTargetException) { |
| e = ((InvocationTargetException) e).getTargetException(); |
| } |
| throw translateException(child, e); |
| } |
| } |
| |
| /** |
| * Process an Expression type node reflectively; must return a JExpression. |
| */ |
| protected JExpression dispProcessExpression(Expression x) { |
| /* |
| * Note that we always prefer a JDT-computed constant value to the actual |
| * written expression. (Let's hope JDT is always right.) This means we |
| * don't have to write processExpression methods for the numerous JDT |
| * literal nodes because they ALWAYS have a constant value. |
| */ |
| JExpression result = null; |
| if (x != null && x.constant != null && x.constant != Constant.NotAConstant) { |
| result = (JExpression) dispatch("processConstant", x.constant); |
| } |
| |
| if (result == null) { |
| // The expression was not a constant, so use the general logic. |
| result = (JExpression) dispatch("processExpression", x); |
| } |
| |
| // Check if we need to box the resulting expression. |
| if (x != null) { |
| if ((x.implicitConversion & TypeIds.BOXING) != 0) { |
| result = autoboxUtils.box(result, implicitConversionTargetType(x)); |
| } else if ((x.implicitConversion & TypeIds.UNBOXING) != 0) { |
| // This code can actually leave an unbox operation in |
| // an lvalue position, for example ++(x.intValue()). |
| // Such trees are cleaned up in FixAssignmentToUnbox. |
| JType typeToUnbox = (JType) typeMap.get(x.resolvedType); |
| if (!(typeToUnbox instanceof JClassType)) { |
| throw new InternalCompilerException(result, "Attempt to unbox a non-class type: " |
| + typeToUnbox.getName(), null); |
| } |
| |
| result = unbox(result, (JClassType) typeToUnbox); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Process an Statement type node reflectively; must return a JStatement. |
| */ |
| protected JStatement dispProcessStatement(Statement x) { |
| JStatement stmt; |
| if (x instanceof Expression) { |
| JExpression expr = dispProcessExpression((Expression) x); |
| if (expr == null) { |
| return null; |
| } |
| stmt = expr.makeStatement(); |
| } else { |
| stmt = (JStatement) dispatch("processStatement", x); |
| } |
| return stmt; |
| } |
| |
| Map<JsniMethodBody, AbstractMethodDeclaration> getJsniMethodMap() { |
| return jsniMethodMap; |
| } |
| |
| JBooleanLiteral processConstant(BooleanConstant x) { |
| return program.getLiteralBoolean(x.booleanValue()); |
| } |
| |
| JIntLiteral processConstant(ByteConstant x) { |
| return program.getLiteralInt(x.byteValue()); |
| } |
| |
| JCharLiteral processConstant(CharConstant x) { |
| return program.getLiteralChar(x.charValue()); |
| } |
| |
| JDoubleLiteral processConstant(DoubleConstant x) { |
| return program.getLiteralDouble(x.doubleValue()); |
| } |
| |
| JFloatLiteral processConstant(FloatConstant x) { |
| return program.getLiteralFloat(x.floatValue()); |
| } |
| |
| JIntLiteral processConstant(IntConstant x) { |
| return program.getLiteralInt(x.intValue()); |
| } |
| |
| JLongLiteral processConstant(LongConstant x) { |
| return program.getLiteralLong(x.longValue()); |
| } |
| |
| JIntLiteral processConstant(ShortConstant x) { |
| return program.getLiteralInt(x.shortValue()); |
| } |
| |
| JStringLiteral processConstant(StringConstant x) { |
| // May be processing an annotation |
| SourceInfo info = |
| currentMethod == null ? currentClass.getSourceInfo() : currentMethod.getSourceInfo(); |
| return program.getLiteralString(info.makeChild(), x.stringValue().toCharArray()); |
| } |
| |
| /** |
| * This is slightly different from the normal Java language. Specifically, |
| * we explicitly handle synthetic fields that are part of nested/local |
| * classes. It boils down to the fact that we really HAVE to assign |
| * synthetic arguments to synthetic fields BEFORE calling the superclass |
| * constructor (because it might call you back polymorphically). In straight |
| * Java that glue code is a semantic error, because a this/super call must |
| * be the first statement of your constructor. On the upside, optimizations |
| * work the same on our synthetic fields as with any user fields. |
| * |
| * The order of emulation is: - assign all synthetic fields from synthetic |
| * args - call our super constructor emulation method - call our instance |
| * initializer emulation method - run user code |
| */ |
| void processConstructor(ConstructorDeclaration x) { |
| JConstructor ctor = (JConstructor) typeMap.get(x.binding); |
| try { |
| SourceInfo info = ctor.getSourceInfo(); |
| |
| currentMethod = ctor; |
| currentMethodBody = ctor.getBody(); |
| currentMethodScope = x.scope; |
| |
| /* |
| * Determine if we have an explicit this call. The presence of an |
| * explicit this call indicates we can skip certain initialization steps |
| * (as the callee will perform those steps for us). These skippable |
| * steps are 1) assigning synthetic args to fields and 2) running |
| * initializers. |
| */ |
| boolean hasExplicitThis = (x.constructorCall != null) && !x.constructorCall.isSuperAccess(); |
| |
| JClassType enclosingType = ctor.getEnclosingType(); |
| JBlock block = currentMethodBody.getBlock(); |
| currentOuterThisRefParams = Maps.create(); |
| |
| /* |
| * All synthetic fields must be assigned, unless we have an explicit |
| * this constructor call, in which case the callee will assign them for |
| * us. |
| */ |
| ReferenceBinding declaringClass = x.binding.declaringClass; |
| if (declaringClass instanceof NestedTypeBinding) { |
| Iterator<JParameter> paramIt = currentMethod.getParams().iterator(); |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) declaringClass; |
| if (nestedBinding.enclosingInstances != null) { |
| for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) { |
| JParameter param = paramIt.next(); |
| JField field = (JField) typeMap.get(arg); |
| if (!hasExplicitThis) { |
| block.addStmt(JProgram.createAssignmentStmt(info, createVariableRef(info, field), |
| createVariableRef(info, param))); |
| } |
| currentOuterThisRefParams = Maps.put(currentOuterThisRefParams, field, param); |
| } |
| } |
| |
| if (!hasExplicitThis) { |
| paramIt = getSyntheticLocalsIterator(); |
| if (nestedBinding.outerLocalVariables != null) { |
| for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) { |
| JParameter param = paramIt.next(); |
| JField field = (JField) typeMap.get(arg); |
| block.addStmt(JProgram.createAssignmentStmt(info, createVariableRef(info, field), |
| createVariableRef(info, param))); |
| } |
| } |
| } |
| } |
| |
| // optional this or super constructor call |
| if (x.constructorCall != null) { |
| JMethodCall superOrThisCall = |
| (JMethodCall) dispatch("processExpression", x.constructorCall); |
| // Enums: wire up synthetic name/ordinal params to the super method. |
| if (enclosingType.isEnumOrSubclass() != null) { |
| JVariableRef enumNameRef = |
| createVariableRef(superOrThisCall.getSourceInfo(), ctor.getParams().get(0)); |
| superOrThisCall.addArg(0, enumNameRef); |
| JVariableRef enumOrdinalRef = |
| createVariableRef(superOrThisCall.getSourceInfo(), ctor.getParams().get(1)); |
| superOrThisCall.addArg(1, enumOrdinalRef); |
| } |
| |
| superOrThisCall.setStaticDispatchOnly(); |
| block.addStmt(superOrThisCall.makeStatement()); |
| } |
| |
| JExpression thisRef = createThisRef(info, enclosingType); |
| |
| /* |
| * Call the synthetic instance initializer method, unless we have an |
| * explicit this constructor call, in which case the callee will. |
| */ |
| if (!hasExplicitThis) { |
| // $init is always in position 1 (clinit is in 0) |
| JMethod initMethod = enclosingType.getMethods().get(1); |
| JMethodCall initCall = new JMethodCall(info, thisRef, initMethod); |
| block.addStmt(initCall.makeStatement()); |
| } |
| |
| // user code (finally!) |
| block.addStmts(processStatements(x.statements)); |
| |
| currentOuterThisRefParams = null; |
| currentMethodScope = null; |
| currentMethod = null; |
| } catch (Throwable e) { |
| throw translateException(ctor, e); |
| } |
| } |
| |
| JExpression processExpression(AllocationExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| TypeBinding typeBinding = erasure(x.resolvedType); |
| if (typeBinding.constantPoolName() == null) { |
| /* |
| * Weird case: if JDT determines that this local class is totally |
| * uninstantiable, it won't bother allocating a local name. |
| */ |
| return program.getLiteralNull(); |
| } |
| JClassType newType = (JClassType) typeMap.get(typeBinding); |
| MethodBinding b = x.binding; |
| JConstructor ctor = (JConstructor) typeMap.get(b); |
| JMethodCall call; |
| JClassType javaLangString = program.getTypeJavaLangString(); |
| if (newType == javaLangString && !newType.isExternal()) { |
| /* |
| * MAGIC: java.lang.String is implemented as a JavaScript String |
| * primitive with a modified prototype. This requires funky handling of |
| * constructor calls. We find a method named _String() whose signature |
| * matches the requested constructor |
| */ |
| int ctorArgc = ctor.getParams().size(); |
| JMethod targetMethod = null; |
| outer : for (JMethod method : javaLangString.getMethods()) { |
| if (method.getName().equals("_String") && method.getParams().size() == ctorArgc) { |
| for (int i = 0; i < ctorArgc; ++i) { |
| JParameter mparam = method.getParams().get(i); |
| JParameter cparam = ctor.getParams().get(i); |
| if (mparam.getType() != cparam.getType()) { |
| continue outer; |
| } |
| } |
| targetMethod = method; |
| break; |
| } |
| } |
| if (targetMethod == null) { |
| throw new InternalCompilerException( |
| "String constructor error; no matching implementation."); |
| } |
| call = new JMethodCall(makeSourceInfo(x), null, targetMethod); |
| } else { |
| call = new JNewInstance(info, ctor, currentClass); |
| } |
| |
| // Enums: hidden arguments for the name and id. |
| if (x.enumConstant != null) { |
| call.addArgs(program.getLiteralString(info, x.enumConstant.name), program |
| .getLiteralInt(x.enumConstant.binding.original().id)); |
| } |
| |
| // Synthetic args for inner classes |
| ReferenceBinding targetBinding = b.declaringClass; |
| if (targetBinding.isNestedType() && !targetBinding.isStatic()) { |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding); |
| // Synthetic this args for inner classes |
| if (nestedBinding.enclosingInstances != null) { |
| for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) { |
| JClassType syntheticThisType = (JClassType) typeMap.get(arg.type); |
| call.addArg(createThisRef(info, syntheticThisType)); |
| } |
| } |
| } |
| |
| // Plain old regular user arguments |
| addCallArgs(x.arguments, call, b); |
| |
| // Synthetic args for inner classes |
| if (targetBinding.isNestedType() && !targetBinding.isStatic()) { |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding); |
| // Synthetic locals for local classes |
| if (nestedBinding.outerLocalVariables != null) { |
| for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) { |
| JVariable variable = (JVariable) typeMap.get(arg.actualOuterLocalVariable); |
| call.addArg(createVariableRef(info, variable, arg.actualOuterLocalVariable)); |
| } |
| } |
| } |
| |
| return call; |
| } |
| |
| JExpression processExpression(AND_AND_Expression x) { |
| JType type = (JType) typeMap.get(x.resolvedType); |
| SourceInfo info = makeSourceInfo(x); |
| return processBinaryOperation(info, JBinaryOperator.AND, type, x.left, x.right); |
| } |
| |
| JExpression processExpression(ArrayAllocationExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JArrayType type = (JArrayType) typeMap.get(x.resolvedType); |
| |
| if (x.initializer != null) { |
| List<JExpression> initializers = new ArrayList<JExpression>(); |
| if (x.initializer.expressions != null) { |
| for (Expression expression : x.initializer.expressions) { |
| initializers.add(dispProcessExpression(expression)); |
| } |
| } |
| return JNewArray.createInitializers(info, type, initializers); |
| } else { |
| List<JExpression> dims = new ArrayList<JExpression>(); |
| for (Expression dimension : x.dimensions) { |
| // can be null if index expression was empty |
| if (dimension == null) { |
| dims.add(program.getLiteralAbsentArrayDimension()); |
| } else { |
| dims.add(dispProcessExpression(dimension)); |
| } |
| } |
| return JNewArray.createDims(info, type, dims); |
| } |
| } |
| |
| JExpression processExpression(ArrayInitializer x) { |
| SourceInfo info = makeSourceInfo(x); |
| JArrayType type = (JArrayType) typeMap.get(x.resolvedType); |
| |
| List<JExpression> initializers = new ArrayList<JExpression>(); |
| if (x.expressions != null) { |
| for (Expression expression : x.expressions) { |
| initializers.add(dispProcessExpression(expression)); |
| } |
| } |
| return JNewArray.createInitializers(info, type, initializers); |
| } |
| |
| JExpression processExpression(ArrayReference x) { |
| SourceInfo info = makeSourceInfo(x); |
| JArrayRef arrayRef = |
| new JArrayRef(info, dispProcessExpression(x.receiver), dispProcessExpression(x.position)); |
| return arrayRef; |
| } |
| |
| JExpression processExpression(Assignment x) { |
| JType type = (JType) typeMap.get(x.resolvedType); |
| SourceInfo info = makeSourceInfo(x); |
| return processBinaryOperation(info, JBinaryOperator.ASG, type, x.lhs, x.expression); |
| } |
| |
| JExpression processExpression(BinaryExpression x) { |
| JBinaryOperator op; |
| |
| int binOp = (x.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT; |
| switch (binOp) { |
| case OperatorIds.LEFT_SHIFT: |
| op = JBinaryOperator.SHL; |
| break; |
| case OperatorIds.RIGHT_SHIFT: |
| op = JBinaryOperator.SHR; |
| break; |
| case OperatorIds.UNSIGNED_RIGHT_SHIFT: |
| op = JBinaryOperator.SHRU; |
| break; |
| case OperatorIds.PLUS: |
| if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) { |
| op = JBinaryOperator.CONCAT; |
| } else { |
| op = JBinaryOperator.ADD; |
| } |
| break; |
| case OperatorIds.MINUS: |
| op = JBinaryOperator.SUB; |
| break; |
| case OperatorIds.REMAINDER: |
| op = JBinaryOperator.MOD; |
| break; |
| case OperatorIds.XOR: |
| op = JBinaryOperator.BIT_XOR; |
| break; |
| case OperatorIds.AND: |
| op = JBinaryOperator.BIT_AND; |
| break; |
| case OperatorIds.MULTIPLY: |
| op = JBinaryOperator.MUL; |
| break; |
| case OperatorIds.OR: |
| op = JBinaryOperator.BIT_OR; |
| break; |
| case OperatorIds.DIVIDE: |
| op = JBinaryOperator.DIV; |
| break; |
| case OperatorIds.LESS_EQUAL: |
| op = JBinaryOperator.LTE; |
| break; |
| case OperatorIds.GREATER_EQUAL: |
| op = JBinaryOperator.GTE; |
| break; |
| case OperatorIds.GREATER: |
| op = JBinaryOperator.GT; |
| break; |
| case OperatorIds.LESS: |
| op = JBinaryOperator.LT; |
| break; |
| default: |
| throw new InternalCompilerException("Unexpected operator for BinaryExpression"); |
| } |
| |
| JType type = (JType) typeMap.get(x.resolvedType); |
| SourceInfo info = makeSourceInfo(x); |
| return processBinaryOperation(info, op, type, x.left, x.right); |
| } |
| |
| JExpression processExpression(CastExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JType type = (JType) typeMap.get(x.resolvedType); |
| JCastOperation cast = new JCastOperation(info, type, dispProcessExpression(x.expression)); |
| return cast; |
| } |
| |
| JExpression processExpression(ClassLiteralAccess x) { |
| SourceInfo info = makeSourceInfo(x); |
| JType type = (JType) typeMap.get(x.targetType); |
| return new JClassLiteral(info, type); |
| } |
| |
| JExpression processExpression(CombinedBinaryExpression x) { |
| return processExpression((BinaryExpression) x); |
| } |
| |
| JExpression processExpression(CompoundAssignment x) { |
| JBinaryOperator op; |
| |
| switch (x.operator) { |
| case OperatorIds.PLUS: |
| if (program.isJavaLangString((JType) typeMap.get(x.resolvedType))) { |
| op = JBinaryOperator.ASG_CONCAT; |
| } else { |
| op = JBinaryOperator.ASG_ADD; |
| } |
| break; |
| case OperatorIds.MINUS: |
| op = JBinaryOperator.ASG_SUB; |
| break; |
| case OperatorIds.MULTIPLY: |
| op = JBinaryOperator.ASG_MUL; |
| break; |
| case OperatorIds.DIVIDE: |
| op = JBinaryOperator.ASG_DIV; |
| break; |
| case OperatorIds.AND: |
| op = JBinaryOperator.ASG_BIT_AND; |
| break; |
| case OperatorIds.OR: |
| op = JBinaryOperator.ASG_BIT_OR; |
| break; |
| case OperatorIds.XOR: |
| op = JBinaryOperator.ASG_BIT_XOR; |
| break; |
| case OperatorIds.REMAINDER: |
| op = JBinaryOperator.ASG_MOD; |
| break; |
| case OperatorIds.LEFT_SHIFT: |
| op = JBinaryOperator.ASG_SHL; |
| break; |
| case OperatorIds.RIGHT_SHIFT: |
| op = JBinaryOperator.ASG_SHR; |
| break; |
| case CompoundAssignment.UNSIGNED_RIGHT_SHIFT: |
| op = JBinaryOperator.ASG_SHRU; |
| break; |
| default: |
| throw new InternalCompilerException("Unexpected operator for CompoundAssignment"); |
| } |
| |
| JType type = (JType) typeMap.get(x.resolvedType); |
| SourceInfo info = makeSourceInfo(x); |
| return processBinaryOperation(info, op, type, x.lhs, x.expression); |
| } |
| |
| JExpression processExpression(ConditionalExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JType type = (JType) typeMap.get(x.resolvedType); |
| JExpression ifTest = dispProcessExpression(x.condition); |
| JExpression thenExpr = dispProcessExpression(x.valueIfTrue); |
| JExpression elseExpr = dispProcessExpression(x.valueIfFalse); |
| JConditional conditional = new JConditional(info, type, ifTest, thenExpr, elseExpr); |
| return conditional; |
| } |
| |
| JExpression processExpression(EqualExpression x) { |
| JBinaryOperator op; |
| switch ((x.bits & BinaryExpression.OperatorMASK) >> BinaryExpression.OperatorSHIFT) { |
| case BinaryExpression.EQUAL_EQUAL: |
| op = JBinaryOperator.EQ; |
| break; |
| case BinaryExpression.NOT_EQUAL: |
| op = JBinaryOperator.NEQ; |
| break; |
| default: |
| throw new InternalCompilerException("Unexpected operator for EqualExpression"); |
| } |
| |
| JType type = (JType) typeMap.get(x.resolvedType); |
| SourceInfo info = makeSourceInfo(x); |
| return processBinaryOperation(info, op, type, x.left, x.right); |
| } |
| |
| /** |
| * How we have to treat super calls vs. this calls is so different, they may |
| * as well have been two different JDT nodes. |
| */ |
| JMethodCall processExpression(ExplicitConstructorCall x) { |
| if (x.isSuperAccess()) { |
| return processSuperConstructorCall(x); |
| } else { |
| return processThisConstructorCall(x); |
| } |
| } |
| |
| JExpression processExpression(FieldReference x) { |
| FieldBinding fieldBinding = x.binding; |
| SourceInfo info = makeSourceInfo(x); |
| JExpression instance = dispProcessExpression(x.receiver); |
| JExpression expr; |
| if (fieldBinding.declaringClass == null) { |
| if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) { |
| throw new InternalCompilerException("Expected [array].length."); |
| } |
| expr = new JArrayLength(info, instance); |
| } else { |
| JField field = (JField) typeMap.get(fieldBinding); |
| expr = new JFieldRef(info, instance, field, currentClass); |
| } |
| |
| if (x.genericCast != null) { |
| JType castType = (JType) typeMap.get(x.genericCast); |
| /* |
| * Note, this may result in an invalid AST due to an LHS cast operation. |
| * We fix this up in FixAssignmentToUnbox. |
| */ |
| return maybeCast(castType, expr); |
| } |
| return expr; |
| } |
| |
| JExpression processExpression(InstanceOfExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JExpression expr = dispProcessExpression(x.expression); |
| JReferenceType testType = (JReferenceType) typeMap.get(x.type.resolvedType); |
| return new JInstanceOf(info, testType, expr); |
| } |
| |
| JExpression processExpression(MessageSend x) { |
| SourceInfo info = makeSourceInfo(x); |
| JMethod method = (JMethod) typeMap.get(x.binding); |
| |
| JExpression qualifier; |
| if (x.receiver instanceof ThisReference) { |
| if (method.isStatic()) { |
| // don't bother qualifying it, it's a no-op |
| qualifier = null; |
| } else if (x.receiver instanceof QualifiedThisReference) { |
| // use the supplied qualifier |
| qualifier = dispProcessExpression(x.receiver); |
| } else { |
| /* |
| * In cases where JDT had to synthesize a this ref for us, it could |
| * actually be the wrong type, if the target method is in an enclosing |
| * class. We have to synthesize our own ref of the correct type. |
| */ |
| qualifier = createThisRef(info, method.getEnclosingType()); |
| } |
| } else { |
| qualifier = dispProcessExpression(x.receiver); |
| } |
| |
| JMethodCall call = new JMethodCall(info, qualifier, method); |
| |
| // On a super ref, don't allow polymorphic dispatch. Oddly enough, |
| // QualifiedSuperReference not derived from SuperReference! |
| boolean isSuperRef = |
| x.receiver instanceof SuperReference || x.receiver instanceof QualifiedSuperReference; |
| if (isSuperRef) { |
| call.setStaticDispatchOnly(); |
| } |
| |
| // The arguments come first... |
| addCallArgs(x.arguments, call, x.binding); |
| |
| if (x.valueCast != null) { |
| JType castType = (JType) typeMap.get(x.valueCast); |
| return maybeCast(castType, call); |
| } |
| return call; |
| } |
| |
| JExpression processExpression(NullLiteral x) { |
| return program.getLiteralNull(); |
| } |
| |
| JExpression processExpression(OR_OR_Expression x) { |
| JType type = (JType) typeMap.get(x.resolvedType); |
| SourceInfo info = makeSourceInfo(x); |
| return processBinaryOperation(info, JBinaryOperator.OR, type, x.left, x.right); |
| } |
| |
| JExpression processExpression(PostfixExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JUnaryOperator op; |
| |
| switch (x.operator) { |
| case PostfixExpression.MINUS: |
| op = JUnaryOperator.DEC; |
| break; |
| |
| case PostfixExpression.PLUS: |
| op = JUnaryOperator.INC; |
| break; |
| |
| default: |
| throw new InternalCompilerException("Unexpected postfix operator"); |
| } |
| |
| JPostfixOperation postOp = new JPostfixOperation(info, op, dispProcessExpression(x.lhs)); |
| return postOp; |
| } |
| |
| JExpression processExpression(PrefixExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JUnaryOperator op; |
| |
| switch (x.operator) { |
| case PrefixExpression.MINUS: |
| op = JUnaryOperator.DEC; |
| break; |
| |
| case PrefixExpression.PLUS: |
| op = JUnaryOperator.INC; |
| break; |
| |
| default: |
| throw new InternalCompilerException("Unexpected prefix operator"); |
| } |
| |
| JPrefixOperation preOp = new JPrefixOperation(info, op, dispProcessExpression(x.lhs)); |
| return preOp; |
| } |
| |
| JExpression processExpression(QualifiedAllocationExpression x) { |
| /* |
| * Weird: sometimes JDT will create a QualifiedAllocationExpression with |
| * no qualifier. I guess this is supposed to let us know that we need to |
| * synthesize a synthetic this arg based on our own current "this"? But |
| * plain old regular AllocationExpression also must be treated as if it |
| * might be be implicitly qualified, so I'm not sure what the point is. |
| * Let's just defer to the AllocationExpression logic if there's no |
| * qualifier. |
| */ |
| if (x.enclosingInstance() == null) { |
| return processExpression((AllocationExpression) x); |
| } |
| |
| SourceInfo info = makeSourceInfo(x); |
| MethodBinding b = x.binding; |
| JConstructor ctor = (JConstructor) typeMap.get(b); |
| JNewInstance newInstance = new JNewInstance(info, ctor, currentClass); |
| JExpression qualifier = dispProcessExpression(x.enclosingInstance); |
| List<JExpression> qualList = new ArrayList<JExpression>(); |
| qualList.add(qualifier); |
| |
| /* |
| * Really weird: Sometimes an allocation expression needs both its |
| * explicit qualifier AND its implicit enclosing class! We add this second |
| * because the explicit qualifier takes precedence. |
| */ |
| if (!currentMethod.isStatic()) { |
| JExpression implicitOuter = new JThisRef(info, (JClassType) currentClass); |
| qualList.add(implicitOuter); |
| } |
| |
| // Synthetic this args for inner classes |
| ReferenceBinding targetBinding = b.declaringClass; |
| if (targetBinding.isNestedType() && !targetBinding.isStatic()) { |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding); |
| if (nestedBinding.enclosingInstances != null) { |
| for (SyntheticArgumentBinding arg : nestedBinding.enclosingInstances) { |
| JClassType syntheticThisType = (JClassType) typeMap.get(arg.type); |
| newInstance.addArg(createThisRef(syntheticThisType, qualList)); |
| } |
| } |
| } |
| |
| // Plain old regular arguments |
| addCallArgs(x.arguments, newInstance, b); |
| |
| // Synthetic locals for local classes |
| if (targetBinding.isNestedType() && !targetBinding.isStatic()) { |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(targetBinding); |
| if (nestedBinding.outerLocalVariables != null) { |
| for (SyntheticArgumentBinding arg : nestedBinding.outerLocalVariables) { |
| JVariable variable = (JVariable) typeMap.get(arg.actualOuterLocalVariable); |
| newInstance.addArg(createVariableRef(info, variable, arg.actualOuterLocalVariable)); |
| } |
| } |
| } |
| |
| return newInstance; |
| } |
| |
| JExpression processExpression(QualifiedNameReference x) { |
| SourceInfo info = makeSourceInfo(x); |
| Binding binding = x.binding; |
| JNode node = typeMap.get(binding); |
| if (!(node instanceof JVariable)) { |
| return null; |
| } |
| JVariable variable = (JVariable) node; |
| |
| JExpression curRef = createVariableRef(info, variable, binding); |
| if (x.genericCast != null) { |
| JType castType = (JType) typeMap.get(x.genericCast); |
| curRef = maybeCast(castType, curRef); |
| } |
| |
| /* |
| * Wackiness: JDT represents multiple field access as an array of fields, |
| * each qualified by everything to the left. So each subsequent item in |
| * otherBindings takes the current expression as a qualifier. |
| */ |
| if (x.otherBindings != null) { |
| for (int i = 0; i < x.otherBindings.length; ++i) { |
| FieldBinding fieldBinding = x.otherBindings[i]; |
| if (fieldBinding.declaringClass == null) { |
| // probably array.length |
| if (!ARRAY_LENGTH_FIELD.equals(String.valueOf(fieldBinding.name))) { |
| throw new InternalCompilerException("Expected [array].length."); |
| } |
| curRef = new JArrayLength(info, curRef); |
| } else { |
| JField field = (JField) typeMap.get(fieldBinding); |
| curRef = new JFieldRef(info, curRef, field, currentClass); |
| } |
| if (x.otherGenericCasts != null && x.otherGenericCasts[i] != null) { |
| JType castType = (JType) typeMap.get(x.otherGenericCasts[i]); |
| curRef = maybeCast(castType, curRef); |
| } |
| } |
| } |
| |
| return curRef; |
| } |
| |
| JExpression processExpression(QualifiedSuperReference x) { |
| JClassType refType = (JClassType) typeMap.get(x.resolvedType); |
| JClassType qualType = (JClassType) typeMap.get(x.qualification.resolvedType); |
| assert (refType == qualType.getSuperClass()); |
| // Oddly enough, super refs can be modeled as this refs, because whatever |
| // expression they qualify has already been resolved. |
| return processQualifiedThisOrSuperRef(x, qualType); |
| } |
| |
| JExpression processExpression(QualifiedThisReference x) { |
| JClassType refType = (JClassType) typeMap.get(x.resolvedType); |
| JClassType qualType = (JClassType) typeMap.get(x.qualification.resolvedType); |
| assert (refType == qualType); |
| return processQualifiedThisOrSuperRef(x, qualType); |
| } |
| |
| JExpression processExpression(SingleNameReference x) { |
| SourceInfo info = makeSourceInfo(x); |
| Binding binding = x.binding; |
| Object target = typeMap.get(binding); |
| if (!(target instanceof JVariable)) { |
| return null; |
| } |
| JVariable variable = (JVariable) target; |
| |
| /* |
| * Wackiness: if a field happens to have synthetic accessors (only fields |
| * can have them, apparently), this is a ref to a field in an enclosing |
| * instance. CreateThisRef should compute a "this" access of the |
| * appropriate type, unless the field is static. |
| */ |
| JExpression result = null; |
| if (x.syntheticAccessors != null) { |
| JField field = (JField) variable; |
| if (!field.isStatic()) { |
| JExpression instance = createThisRef(info, field.getEnclosingType()); |
| result = new JFieldRef(info, instance, field, currentClass); |
| } |
| } |
| if (result == null) { |
| result = createVariableRef(info, variable, binding); |
| } |
| if (x.genericCast != null) { |
| JType castType = (JType) typeMap.get(x.genericCast); |
| result = maybeCast(castType, result); |
| } |
| return result; |
| } |
| |
| JExpression processExpression(SuperReference x) { |
| JClassType type = (JClassType) typeMap.get(x.resolvedType); |
| assert (type == currentClass.getSuperClass()); |
| SourceInfo info = makeSourceInfo(x); |
| // Oddly enough, super refs can be modeled as a this refs. |
| JExpression superRef = createThisRef(info, currentClass); |
| return superRef; |
| } |
| |
| JExpression processExpression(ThisReference x) { |
| JClassType type = (JClassType) typeMap.get(x.resolvedType); |
| assert (type == currentClass); |
| SourceInfo info = makeSourceInfo(x); |
| JExpression thisRef = createThisRef(info, currentClass); |
| return thisRef; |
| } |
| |
| JExpression processExpression(UnaryExpression x) { |
| SourceInfo info = makeSourceInfo(x); |
| JUnaryOperator op; |
| int operator = ((x.bits & UnaryExpression.OperatorMASK) >> UnaryExpression.OperatorSHIFT); |
| |
| switch (operator) { |
| case UnaryExpression.MINUS: |
| op = JUnaryOperator.NEG; |
| break; |
| |
| case UnaryExpression.NOT: |
| op = JUnaryOperator.NOT; |
| break; |
| |
| case UnaryExpression.PLUS: |
| // Odd case.. a useless + operator; just return the operand |
| return dispProcessExpression(x.expression); |
| |
| case UnaryExpression.TWIDDLE: |
| op = JUnaryOperator.BIT_NOT; |
| break; |
| |
| default: |
| throw new InternalCompilerException("Unexpected operator for unary expression"); |
| } |
| |
| JPrefixOperation preOp = new JPrefixOperation(info, op, dispProcessExpression(x.expression)); |
| return preOp; |
| } |
| |
| List<JExpressionStatement> processExpressionStatements(Statement[] statements) { |
| List<JExpressionStatement> jstatements = new ArrayList<JExpressionStatement>(); |
| if (statements != null) { |
| for (int i = 0, n = statements.length; i < n; ++i) { |
| JStatement jstmt = dispProcessStatement(statements[i]); |
| if (jstmt != null) { |
| jstatements.add((JExpressionStatement) jstmt); |
| } |
| } |
| } |
| return jstatements; |
| } |
| |
| void processField(FieldDeclaration declaration) { |
| JField field = (JField) typeMap.tryGet(declaration.binding); |
| if (field == null) { |
| /* |
| * When anonymous classes declare constant fields, the field declaration |
| * is not visited by JDT. Just bail since any references to that field |
| * are guaranteed to be replaced with literals. |
| */ |
| return; |
| } |
| try { |
| JExpression initializer = null; |
| if (declaration.initialization != null) { |
| initializer = dispProcessExpression(declaration.initialization); |
| } |
| |
| if (field instanceof JEnumField) { |
| // An enum field must be initialized! |
| assert (initializer instanceof JMethodCall); |
| } |
| |
| if (initializer != null) { |
| SourceInfo info = makeSourceInfo(declaration); |
| // JDeclarationStatement's ctor sets up the field's initializer. |
| JStatement decl = |
| new JDeclarationStatement(info, createVariableRef(info, field), initializer); |
| // will either be init or clinit |
| currentMethodBody.getBlock().addStmt(decl); |
| } |
| } catch (Throwable e) { |
| throw translateException(field, e); |
| } |
| } |
| |
| void processInitializer(Initializer initializer) { |
| JBlock block = (JBlock) dispProcessStatement(initializer.block); |
| try { |
| // will either be init or clinit |
| currentMethodBody.getBlock().addStmt(block); |
| } catch (Throwable e) { |
| throw translateException(initializer, e); |
| } |
| } |
| |
| void processMethod(AbstractMethodDeclaration x) { |
| MethodBinding b = x.binding; |
| JMethod method = (JMethod) typeMap.get(b); |
| try { |
| if (!b.isStatic() && (b.isImplementing() || b.isOverriding())) { |
| tryFindUpRefs(method, b); |
| } |
| |
| if (x.isNative()) { |
| processNativeMethod(x, (JsniMethodBody) method.getBody()); |
| return; |
| } |
| |
| currentMethod = method; |
| currentMethodBody = (JMethodBody) method.getBody(); |
| currentMethodScope = x.scope; |
| |
| if (currentMethodBody != null) { |
| currentMethodBody.getBlock().addStmts(processStatements(x.statements)); |
| } |
| currentMethodScope = null; |
| currentMethodBody = null; |
| currentMethod = null; |
| } catch (Throwable e) { |
| throw translateException(method, e); |
| } |
| } |
| |
| void processNativeMethod(AbstractMethodDeclaration x, JsniMethodBody nativeMethodBody) { |
| // Squirrel away a reference to the JDT node to enable error reporting. |
| jsniMethodMap.put(nativeMethodBody, x); |
| } |
| |
| JStatement processStatement(AssertStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| JExpression expr = dispProcessExpression(x.assertExpression); |
| JExpression arg = dispProcessExpression(x.exceptionArgument); |
| return new JAssertStatement(info, expr, arg); |
| } |
| |
| JBlock processStatement(Block x) { |
| if (x == null) { |
| return null; |
| } |
| |
| SourceInfo info = makeSourceInfo(x); |
| JBlock block = new JBlock(info); |
| block.addStmts(processStatements(x.statements)); |
| return block; |
| } |
| |
| JStatement processStatement(BreakStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| return new JBreakStatement(info, getOrCreateLabel(info, currentMethod, x.label)); |
| } |
| |
| JStatement processStatement(CaseStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| JExpression expression = dispProcessExpression(x.constantExpression); |
| if (expression != null && x.constantExpression.resolvedType.isEnum()) { |
| // TODO: propagate enum information? |
| assert (expression instanceof JFieldRef); |
| JFieldRef fieldRef = (JFieldRef) expression; |
| JEnumField field = (JEnumField) fieldRef.getField(); |
| return new JCaseStatement(info, program.getLiteralInt(field.ordinal())); |
| } else { |
| return new JCaseStatement(info, (JLiteral) expression); |
| } |
| } |
| |
| JStatement processStatement(ContinueStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| return new JContinueStatement(info, getOrCreateLabel(info, currentMethod, x.label)); |
| } |
| |
| JStatement processStatement(DoStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| JExpression loopTest = dispProcessExpression(x.condition); |
| JStatement loopBody = dispProcessStatement(x.action); |
| JDoStatement stmt = new JDoStatement(info, loopTest, loopBody); |
| return stmt; |
| } |
| |
| JStatement processStatement(EmptyStatement x) { |
| return null; |
| } |
| |
| JStatement processStatement(ForeachStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| |
| JBlock body; |
| JStatement action = dispProcessStatement(x.action); |
| if (action instanceof JBlock) { |
| body = (JBlock) action; |
| } else { |
| body = new JBlock(info); |
| if (action != null) { |
| body.addStmt(action); |
| } |
| } |
| |
| JLocal elementVar = (JLocal) typeMap.get(x.elementVariable.binding); |
| String elementVarName = elementVar.getName(); |
| |
| JDeclarationStatement elementDecl = |
| (JDeclarationStatement) processStatement(x.elementVariable); |
| assert (elementDecl.initializer == null); |
| |
| JForStatement result; |
| if (x.collectionVariable != null) { |
| /** |
| * <pre> |
| * for (final T[] i$array = collection, |
| * int i$index = 0, |
| * final int i$max = i$array.length; |
| * i$index < i$max; ++i$index) { |
| * T elementVar = i$array[i$index]; |
| * // user action |
| * } |
| * </pre> |
| */ |
| JLocal arrayVar = |
| JProgram.createLocal(info, elementVarName + "$array", ((JType) typeMap |
| .get(x.collection.resolvedType)), true, currentMethodBody); |
| JLocal indexVar = |
| JProgram.createLocal(info, elementVarName + "$index", program.getTypePrimitiveInt(), |
| false, currentMethodBody); |
| JLocal maxVar = |
| JProgram.createLocal(info, elementVarName + "$max", program.getTypePrimitiveInt(), |
| true, currentMethodBody); |
| |
| List<JStatement> initializers = new ArrayList<JStatement>(3); |
| // T[] i$array = arr |
| initializers.add(createDeclaration(info, arrayVar, dispProcessExpression(x.collection))); |
| // int i$index = 0 |
| initializers.add(createDeclaration(info, indexVar, program.getLiteralInt(0))); |
| // int i$max = i$array.length |
| initializers.add(createDeclaration(info, maxVar, new JArrayLength(info, new JLocalRef(info, |
| arrayVar)))); |
| |
| // i$index < i$max |
| JExpression condition = |
| new JBinaryOperation(info, program.getTypePrimitiveBoolean(), JBinaryOperator.LT, |
| createVariableRef(info, indexVar), createVariableRef(info, maxVar)); |
| |
| // ++i$index |
| List<JExpressionStatement> increments = new ArrayList<JExpressionStatement>(1); |
| increments.add(new JPrefixOperation(info, JUnaryOperator.INC, createVariableRef(info, |
| indexVar)).makeStatement()); |
| |
| // T elementVar = i$array[i$index]; |
| elementDecl.initializer = |
| new JArrayRef(info, createVariableRef(info, arrayVar), |
| createVariableRef(info, indexVar)); |
| body.addStmt(0, elementDecl); |
| |
| result = new JForStatement(info, initializers, condition, increments, body); |
| } else { |
| /** |
| * <pre> |
| * for (Iterator<T> i$iterator = collection.iterator(); i$iterator.hasNext();) { |
| * T elementVar = i$iterator.next(); |
| * // user action |
| * } |
| * </pre> |
| */ |
| JLocal iteratorVar = |
| JProgram.createLocal(info, (elementVarName + "$iterator"), program |
| .getIndexedType("Iterator"), false, currentMethodBody); |
| |
| List<JStatement> initializers = new ArrayList<JStatement>(1); |
| // Iterator<T> i$iterator = collection.iterator() |
| initializers.add(createDeclaration(info, iteratorVar, new JMethodCall(info, |
| dispProcessExpression(x.collection), program.getIndexedMethod("Iterable.iterator")))); |
| |
| // i$iterator.hasNext() |
| JExpression condition = |
| new JMethodCall(info, createVariableRef(info, iteratorVar), program |
| .getIndexedMethod("Iterator.hasNext")); |
| |
| // T elementVar = (T) i$iterator.next(); |
| elementDecl.initializer = |
| new JMethodCall(info, createVariableRef(info, iteratorVar), program |
| .getIndexedMethod("Iterator.next")); |
| |
| // Perform any implicit reference type casts (due to generics). |
| // Note this occurs before potential unboxing. |
| if (elementVar.getType() != program.getTypeJavaLangObject()) { |
| TypeBinding collectionType; |
| try { |
| // TODO: This is slow! Cache lookup. |
| Field privateField = ForeachStatement.class.getDeclaredField("collectionElementType"); |
| privateField.setAccessible(true); |
| collectionType = (TypeBinding) privateField.get(x); |
| } catch (Exception e) { |
| throw new InternalCompilerException(elementDecl, |
| "Failed to retreive collectionElementType through reflection", e); |
| } |
| JType toType = (JType) typeMap.get(collectionType); |
| assert (toType instanceof JReferenceType); |
| elementDecl.initializer = maybeCast(toType, elementDecl.initializer); |
| } |
| |
| body.addStmt(0, elementDecl); |
| |
| result = |
| new JForStatement(info, initializers, condition, Collections |
| .<JExpressionStatement> emptyList(), body); |
| } |
| |
| // May need to box or unbox the element assignment. |
| if (x.elementVariableImplicitWidening != -1) { |
| if ((x.elementVariableImplicitWidening & TypeIds.BOXING) != 0) { |
| /* |
| * Boxing is necessary. In this special case of autoboxing, the boxed |
| * expression cannot be a constant, so the box type must be exactly |
| * that associated with the expression. |
| */ |
| elementDecl.initializer = |
| autoboxUtils.box(elementDecl.initializer, ((JPrimitiveType) elementDecl.initializer |
| .getType())); |
| } else if ((x.elementVariableImplicitWidening & TypeIds.UNBOXING) != 0) { |
| elementDecl.initializer = |
| unbox(elementDecl.initializer, (JClassType) elementDecl.initializer.getType()); |
| } |
| } |
| return result; |
| } |
| |
| JStatement processStatement(ForStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| // SEE NOTE ON JDT FORCED OPTIMIZATIONS |
| // If the condition is false, don't process the body |
| boolean removeBody = isOptimizedFalse(x.condition); |
| |
| List<JStatement> init = processStatements(x.initializations); |
| JExpression expr = dispProcessExpression(x.condition); |
| List<JExpressionStatement> incr = processExpressionStatements(x.increments); |
| JStatement body = removeBody ? null : dispProcessStatement(x.action); |
| return new JForStatement(info, init, expr, incr, body); |
| } |
| |
| JStatement processStatement(IfStatement x) { |
| // SEE NOTE ON JDT FORCED OPTIMIZATIONS |
| // If the condition is false, don't process the then statement |
| // If the condition is false, don't process the else statement |
| boolean removeThen = isOptimizedFalse(x.condition); |
| boolean removeElse = isOptimizedTrue(x.condition); |
| |
| SourceInfo info = makeSourceInfo(x); |
| JExpression expr = dispProcessExpression(x.condition); |
| JStatement thenStmt = removeThen ? null : dispProcessStatement(x.thenStatement); |
| JStatement elseStmt = removeElse ? null : dispProcessStatement(x.elseStatement); |
| JIfStatement ifStmt = new JIfStatement(info, expr, thenStmt, elseStmt); |
| return ifStmt; |
| } |
| |
| JStatement processStatement(LabeledStatement x) { |
| JStatement body = dispProcessStatement(x.statement); |
| if (body == null) { |
| return null; |
| } |
| SourceInfo info = makeSourceInfo(x); |
| return new JLabeledStatement(info, getOrCreateLabel(info, currentMethod, x.label), body); |
| } |
| |
| JStatement processStatement(LocalDeclaration x) { |
| SourceInfo info = makeSourceInfo(x); |
| JLocal local = (JLocal) typeMap.get(x.binding); |
| JLocalRef localRef = new JLocalRef(info, local); |
| JExpression initializer = dispProcessExpression(x.initialization); |
| return new JDeclarationStatement(info, localRef, initializer); |
| } |
| |
| JStatement processStatement(ReturnStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| return new JReturnStatement(info, dispProcessExpression(x.expression)); |
| } |
| |
| JStatement processStatement(SwitchStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| JExpression expression = dispProcessExpression(x.expression); |
| if (isEnumType(expression.getType())) { |
| // Must be an enum; synthesize a call to ordinal(). |
| expression = new JMethodCall(info, expression, program.getIndexedMethod("Enum.ordinal")); |
| } |
| JBlock block = new JBlock(info); |
| // Don't use processStatements here, because it stops at control breaks |
| if (x.statements != null) { |
| for (Statement stmt : x.statements) { |
| JStatement jstmt = dispProcessStatement(stmt); |
| if (jstmt != null) { |
| block.addStmt(jstmt); |
| } |
| } |
| } |
| return new JSwitchStatement(info, expression, block); |
| } |
| |
| JStatement processStatement(SynchronizedStatement x) { |
| JBlock block = (JBlock) dispProcessStatement(x.block); |
| JExpression expr = dispProcessExpression(x.expression); |
| block.addStmt(0, expr.makeStatement()); |
| return block; |
| } |
| |
| JStatement processStatement(ThrowStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| JExpression toThrow = dispProcessExpression(x.exception); |
| return new JThrowStatement(info, toThrow); |
| } |
| |
| JStatement processStatement(TryStatement x) { |
| SourceInfo info = makeSourceInfo(x); |
| JBlock tryBlock = (JBlock) dispProcessStatement(x.tryBlock); |
| List<JLocalRef> catchArgs = new ArrayList<JLocalRef>(); |
| List<JBlock> catchBlocks = new ArrayList<JBlock>(); |
| if (x.catchBlocks != null) { |
| for (int i = 0, c = x.catchArguments.length; i < c; ++i) { |
| JLocal local = (JLocal) typeMap.get(x.catchArguments[i].binding); |
| catchArgs.add((JLocalRef) createVariableRef(info, local)); |
| } |
| for (int i = 0, c = x.catchBlocks.length; i < c; ++i) { |
| catchBlocks.add((JBlock) dispProcessStatement(x.catchBlocks[i])); |
| } |
| } |
| JBlock finallyBlock = (JBlock) dispProcessStatement(x.finallyBlock); |
| return new JTryStatement(info, tryBlock, catchArgs, catchBlocks, finallyBlock); |
| } |
| |
| JStatement processStatement(TypeDeclaration x) { |
| // do nothing -- the local class is treated at the program level |
| return null; |
| } |
| |
| JStatement processStatement(WhileStatement x) { |
| // SEE NOTE ON JDT FORCED OPTIMIZATIONS |
| // If the condition is false, don't process the body |
| boolean removeBody = isOptimizedFalse(x.condition); |
| |
| SourceInfo info = makeSourceInfo(x); |
| JExpression loopTest = dispProcessExpression(x.condition); |
| JStatement loopBody = removeBody ? null : dispProcessStatement(x.action); |
| JWhileStatement stmt = new JWhileStatement(info, loopTest, loopBody); |
| return stmt; |
| } |
| |
| List<JStatement> processStatements(Statement[] statements) { |
| List<JStatement> jstatements = new ArrayList<JStatement>(); |
| if (statements != null) { |
| for (Statement stmt : statements) { |
| JStatement jstmt = dispProcessStatement(stmt); |
| if (jstmt != null) { |
| jstatements.add(jstmt); |
| if (jstmt.unconditionalControlBreak()) { |
| /* |
| * Stop processing statements, because the remaining ones are |
| * unreachable. The JDT compiler might not have fully fleshed out |
| * the unreachable statements. |
| */ |
| break; |
| } |
| } |
| } |
| } |
| |
| return jstatements; |
| } |
| |
| JMethodCall processSuperConstructorCall(ExplicitConstructorCall x) { |
| SourceInfo info = makeSourceInfo(x); |
| JMethod ctor = (JMethod) typeMap.get(x.binding); |
| JExpression trueQualifier = createThisRef(info, currentClass); |
| JMethodCall call = new JMethodCall(info, trueQualifier, ctor); |
| |
| // We have to find and pass through any synthetics our supertype needs |
| ReferenceBinding superClass = x.binding.declaringClass; |
| if (superClass.isNestedType() && !superClass.isStatic()) { |
| ReferenceBinding myBinding = currentClassScope.referenceType().binding; |
| ReferenceBinding superBinding = superClass; |
| |
| // enclosing types |
| if (superBinding.syntheticEnclosingInstanceTypes() != null) { |
| JExpression qualifier = dispProcessExpression(x.qualification); |
| for (ReferenceBinding arg : superBinding.syntheticEnclosingInstanceTypes()) { |
| JClassType classType = (JClassType) typeMap.get(arg); |
| if (qualifier == null) { |
| /* |
| * Got to be one of my params; it would be illegal to use a this |
| * ref at this moment-- we would most likely be passing in a |
| * supertype field that HASN'T BEEN INITIALIZED YET. |
| * |
| * Unfortunately, my params might not work as-is, so we have to |
| * check each one to see if any will make a suitable this ref. |
| */ |
| List<JExpression> workList = new ArrayList<JExpression>(); |
| Iterator<JParameter> paramIt = currentMethod.getParams().iterator(); |
| for (ReferenceBinding b : myBinding.syntheticEnclosingInstanceTypes()) { |
| workList.add(createVariableRef(info, paramIt.next())); |
| } |
| call.addArg(createThisRef(classType, workList)); |
| } else { |
| call.addArg(createThisRef(classType, qualifier)); |
| } |
| } |
| } |
| } |
| |
| addCallArgs(x.arguments, call, x.binding); |
| |
| // We have to find and pass through any synthetics our supertype needs |
| if (superClass.isNestedType() && !superClass.isStatic()) { |
| ReferenceBinding superBinding = superClass; |
| |
| // outer locals |
| if (superBinding.syntheticOuterLocalVariables() != null) { |
| for (SyntheticArgumentBinding arg : superBinding.syntheticOuterLocalVariables()) { |
| // Got to be one of my params |
| JType varType = (JType) typeMap.get(arg.type); |
| String varName = String.valueOf(arg.name); |
| JParameter param = null; |
| for (JParameter paramIt : currentMethod.getParams()) { |
| if (varType == paramIt.getType() && varName.equals(paramIt.getName())) { |
| param = paramIt; |
| } |
| } |
| if (param == null) { |
| throw new InternalCompilerException( |
| "Could not find matching local arg for explicit super ctor call."); |
| } |
| call.addArg(createVariableRef(info, param)); |
| } |
| } |
| } |
| |
| return call; |
| } |
| |
| JMethodCall processThisConstructorCall(ExplicitConstructorCall x) { |
| SourceInfo info = makeSourceInfo(x); |
| JMethod ctor = (JMethod) typeMap.get(x.binding); |
| JExpression trueQualifier = createThisRef(info, currentClass); |
| JMethodCall call = new JMethodCall(info, trueQualifier, ctor); |
| |
| assert (x.qualification == null); |
| |
| // All synthetic this args must be passed through to the target ctor |
| ReferenceBinding declaringClass = x.binding.declaringClass; |
| if (declaringClass.isNestedType() && !declaringClass.isStatic()) { |
| Iterator<JParameter> paramIt = currentMethod.getParams().iterator(); |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(declaringClass); |
| if (nestedBinding.enclosingInstances != null) { |
| for (SyntheticArgumentBinding unused : nestedBinding.enclosingInstances) { |
| call.addArg(createVariableRef(info, paramIt.next())); |
| } |
| } |
| } |
| |
| addCallArgs(x.arguments, call, x.binding); |
| |
| // All synthetic locals must be passed through to the target ctor |
| if (declaringClass.isNestedType() && !declaringClass.isStatic()) { |
| Iterator<JParameter> paramIt = getSyntheticLocalsIterator(); |
| NestedTypeBinding nestedBinding = (NestedTypeBinding) erasure(declaringClass); |
| if (nestedBinding.outerLocalVariables != null) { |
| for (int i = 0; i < nestedBinding.outerLocalVariables.length; ++i) { |
| call.addArg(createVariableRef(info, paramIt.next())); |
| } |
| } |
| } |
| |
| return call; |
| } |
| |
| private void addAllOuterThisRefs(List<? super JVariableRef> list, JExpression expr, |
| JClassType classType) { |
| for (JField field : classType.getFields()) { |
| // This fields are always first. |
| if (!field.isThisRef()) { |
| break; |
| } |
| // In a constructor, use the local param instead of the field. |
| JParameter param = null; |
| if (currentOuterThisRefParams != null && expr instanceof JThisRef) { |
| param = currentOuterThisRefParams.get(field); |
| } |
| if (param != null) { |
| list.add(new JParameterRef(expr.getSourceInfo(), param)); |
| } else { |
| list.add(new JFieldRef(expr.getSourceInfo(), expr, field, currentClass)); |
| } |
| } |
| } |
| |
| private void addAllOuterThisRefsPlusSuperChain(List<? super JVariableRef> workList, |
| JExpression expr, JClassType classType) { |
| for (; classType != null; classType = classType.getSuperClass()) { |
| addAllOuterThisRefs(workList, expr, classType); |
| } |
| } |
| |
| private void addCallArgs(Expression[] jdtArgs, JMethodCall call, MethodBinding binding) { |
| JExpression[] args = new JExpression[jdtArgs == null ? 0 : jdtArgs.length]; |
| for (int i = 0; i < args.length; ++i) { |
| args[i] = dispProcessExpression(jdtArgs[i]); |
| } |
| if (!binding.isVarargs()) { |
| call.addArgs(args); |
| return; |
| } |
| |
| // Handle the odd var-arg case. |
| TypeBinding[] params = binding.parameters; |
| int varArg = params.length - 1; |
| |
| // Everything but the last arg. |
| for (int i = 0; i < varArg; ++i) { |
| call.addArg(args[i]); |
| } |
| |
| // Handle the last arg. |
| |
| // See if there's a single varArg which is already an array. |
| if (args.length == params.length) { |
| if (jdtArgs[varArg].resolvedType.isCompatibleWith(params[varArg])) { |
| // Already the correct array type. |
| call.addArg(args[varArg]); |
| return; |
| } |
| } |
| |
| // Need to synthesize an appropriately-typed array. |
| List<JExpression> initializers = new ArrayList<JExpression>(); |
| for (int i = varArg; i < args.length; ++i) { |
| initializers.add(args[i]); |
| } |
| JArrayType lastParamType = (JArrayType) typeMap.get(params[varArg]); |
| JNewArray newArray = |
| JNewArray.createInitializers(call.getSourceInfo(), lastParamType, initializers); |
| call.addArg(newArray); |
| } |
| |
| private void addThrownExceptions(MethodBinding methodBinding, JMethod method) { |
| for (ReferenceBinding exceptionReference : methodBinding.thrownExceptions) { |
| method.addThrownException((JClassType) typeMap.get(exceptionReference.erasure())); |
| } |
| } |
| |
| /** |
| * Create a bridge method. It calls a same-named method with the same |
| * arguments, but with a different type signature. |
| * |
| * @param clazz The class to put the bridge method in |
| * @param jdtBridgeMethod The corresponding bridge method added in the JDT |
| * @param implmeth The implementation method to bridge to |
| */ |
| private void createBridgeMethod(JClassType clazz, SyntheticMethodBinding jdtBridgeMethod, |
| JMethod implmeth) { |
| SourceInfo info = implmeth.getSourceInfo().makeChild(); |
| // create the method itself |
| JMethod bridgeMethod = |
| program.createMethod(info, String.valueOf(jdtBridgeMethod.selector), clazz, |
| (JType) typeMap.get(jdtBridgeMethod.returnType.erasure()), false, false, implmeth |
| .isFinal(), false, false); |
| bridgeMethod.setSynthetic(); |
| int paramIdx = 0; |
| List<JParameter> implParams = implmeth.getParams(); |
| for (TypeBinding jdtParamType : jdtBridgeMethod.parameters) { |
| JParameter param = implParams.get(paramIdx++); |
| JType paramType = (JType) typeMap.get(jdtParamType.erasure()); |
| JParameter newParam = |
| new JParameter(info.makeChild(param.getSourceInfo().getOrigin()), param.getName(), |
| paramType, true, false, bridgeMethod); |
| bridgeMethod.addParam(newParam); |
| } |
| addThrownExceptions(jdtBridgeMethod, bridgeMethod); |
| bridgeMethod.freezeParamTypes(); |
| info.addCorrelation(info.getCorrelator().by(bridgeMethod)); |
| |
| // create a call |
| JMethodCall call = new JMethodCall(info, new JThisRef(info, clazz), implmeth); |
| |
| for (int i = 0; i < bridgeMethod.getParams().size(); i++) { |
| JParameter param = bridgeMethod.getParams().get(i); |
| JParameterRef paramRef = new JParameterRef(info, param); |
| call.addArg(maybeCast(implParams.get(i).getType(), paramRef)); |
| } |
| |
| // wrap it in a return if necessary |
| JStatement callOrReturn; |
| if (bridgeMethod.getType() == program.getTypeVoid()) { |
| callOrReturn = call.makeStatement(); |
| } else { |
| callOrReturn = new JReturnStatement(info, call); |
| } |
| |
| // create a body that is just that call |
| JMethodBody body = (JMethodBody) bridgeMethod.getBody(); |
| body.getBlock().addStmt(callOrReturn); |
| |
| // Add overrides. |
| List<JMethod> overrides = new ArrayList<JMethod>(); |
| tryFindUpRefs(bridgeMethod, overrides); |
| assert !overrides.isEmpty(); |
| for (JMethod over : overrides) { |
| bridgeMethod.addOverride(over); |
| /* |
| * TODO(scottb): with a diamond-shape inheritance hierarchy, it may be |
| * possible to get dups in this way. Really, method.overrides should |
| * probably just be an IdentitySet to avoid having to check contains in |
| * various places. Left as a todo because I don't think dups is super |
| * harmful. |
| */ |
| bridgeMethod.addOverrides(over.getOverrides()); |
| } |
| } |
| |
| private JDeclarationStatement createDeclaration(SourceInfo info, JLocal local, JExpression value) { |
| return new JDeclarationStatement(info, new JLocalRef(info, local), value); |
| } |
| |
| /** |
| * Helper to create a qualified "this" ref (really a synthetic this field |
| * access) of the appropriate type. Always use this method instead of |
| * creating a naked JThisRef or you won't get the synthetic accesses right. |
| */ |
| private JExpression createQualifiedThisRef(SourceInfo info, JClassType targetType) { |
| assert (currentClass instanceof JClassType); |
| JExpression expr = new JThisRef(info, ((JClassType) currentClass)); |
| List<JExpression> list = new ArrayList<JExpression>(); |
| addAllOuterThisRefsPlusSuperChain(list, expr, (JClassType) currentClass); |
| return createThisRef(targetType, list); |
| } |
| |
| /** |
| * Helper to create an expression of the target type, possibly by accessing |
| * synthetic this fields on the passed-in expression. This is needed by a |
| * QualifiedAllocationExpression, because the qualifier may not be the |
| * correct type, and we may need use one of its fields. |
| */ |
| private JExpression createThisRef(JReferenceType qualType, JExpression expr) { |
| List<JExpression> list = new ArrayList<JExpression>(); |
| list.add(expr); |
| return createThisRef(qualType, list); |
| } |
| |
| /** |
| * Helper to create an expression of the target type, possibly by accessing |
| * synthetic this fields on ANY of several passed-in expressions. Why in the |
| * world would we need to do this? It turns out that when making an |
| * unqualified explicit super constructor call to something that needs a |
| * synthetic outer this arg, the correct value to pass in can be one of |
| * several of the calling constructor's own synthetic args. The catch is, |
| * it's possible none of the args are exactly the right type-- we have to |
| * make one of them the right type by following each of their synthetic this |
| * refs up an arbitrarily big tree of enclosing classes and |
| * supertypes-with-enclosing-classes until we find something that's the |
| * right type. |
| * |
| * We have this implemented as a Breadth-First Search to minimize the number |
| * of derefs required, and this seems to be correct. Note that we explicitly |
| * prefer the current expression as one of its supertypes over a synthetic |
| * this ref rooted off the current expression that happens to be the correct |
| * type. We have observed this to be consistent with how Java handles it. |
| * |
| * TODO(scottb): could we get this info directly from JDT? |
| */ |
| private JExpression createThisRef(JReferenceType qualType, List<JExpression> list) { |
| LinkedList<JExpression> workList = new LinkedList<JExpression>(); |
| workList.addAll(list); |
| while (!workList.isEmpty()) { |
| JExpression expr = workList.removeFirst(); |
| JClassType classType = (JClassType) ((JReferenceType) expr.getType()).getUnderlyingType(); |
| for (; classType != null; classType = classType.getSuperClass()) { |
| // prefer myself or myself-as-supertype over any of my this$ fields |
| // that may have already been added to the work list |
| if (program.typeOracle.canTriviallyCast(classType, qualType)) { |
| return expr; |
| } |
| addAllOuterThisRefs(workList, expr, classType); |
| } |
| } |
| |
| throw new InternalCompilerException("Cannot create a ThisRef of the appropriate type."); |
| } |
| |
| /** |
| * Helper to creates this ref (or maybe a synthetic this field access) of |
| * the appropriate type. Always use this method instead of creating a naked |
| * JThisRef or you won't get the synthetic accesses right. |
| */ |
| private JExpression createThisRef(SourceInfo info, JReferenceType targetType) { |
| assert (currentClass instanceof JClassType); |
| return createThisRef(targetType, new JThisRef(info, ((JClassType) currentClass))); |
| } |
| |
| /** |
| * Creates an appropriate JVariableRef for the polymorphic type of the |
| * requested JVariable. |
| */ |
| private JVariableRef createVariableRef(SourceInfo info, JVariable variable) { |
| if (variable instanceof JLocal) { |
| JLocal local = (JLocal) variable; |
| if (local.getEnclosingMethod() != currentMethod) { |
| throw new InternalCompilerException("LocalRef referencing local in a different method."); |
| } |
| return new JLocalRef(info, local); |
| } else if (variable instanceof JParameter) { |
| JParameter parameter = (JParameter) variable; |
| if (parameter.getEnclosingMethod() != currentMethod) { |
| throw new InternalCompilerException( |
| "ParameterRef referencing param in a different method."); |
| } |
| return new JParameterRef(info, parameter); |
| } else if (variable instanceof JField) { |
| JField field = (JField) variable; |
| JExpression instance = null; |
| if (!field.isStatic()) { |
| JClassType fieldEnclosingType = (JClassType) field.getEnclosingType(); |
| instance = createThisRef(info, fieldEnclosingType); |
| if (!program.typeOracle.canTriviallyCast((JReferenceType) instance.getType(), |
| fieldEnclosingType)) { |
| throw new InternalCompilerException("FieldRef referencing field in a different type."); |
| } |
| } |
| return new JFieldRef(info, instance, field, currentClass); |
| } |
| throw new InternalCompilerException("Unknown JVariable subclass."); |
| } |
| |
| /** |
| * Creates an appropriate JVariableRef for the polymorphic type of the |
| * requested JVariable. |
| */ |
| private JVariableRef createVariableRef(SourceInfo info, JVariable variable, Binding binding) { |
| // Fix up the reference if it's to an outer local/param |
| variable = possiblyReferenceOuterLocal(variable, binding); |
| if (variable == null) { |
| /* |
| * Strange case: in certain circumstances, JDT will fail to provide an |
| * emulation path to an outer local variable. In the case I know of, the |
| * reference is a spurious qualifier to a static method call. Let's just |
| * return null and ditch the expression. |
| */ |
| return null; |
| } |
| return createVariableRef(info, variable); |
| } |
| |
| private TypeBinding erasure(TypeBinding typeBinding) { |
| if (typeBinding instanceof ParameterizedTypeBinding) { |
| typeBinding = typeBinding.erasure(); |
| } |
| return typeBinding; |
| } |
| |
| private Method getCachedMethod(String name, Class<? extends Object> childClass) |
| throws NoSuchMethodException { |
| MethodKey key = new MethodKey(name, childClass); |
| MethodValue value = methodCache.get(key); |
| if (value == null) { |
| try { |
| Method method = getClass().getDeclaredMethod(name, childClass); |
| value = new MethodValue(method); |
| } catch (NoSuchMethodException ex) { |
| value = new MethodValue(ex); |
| } |
| methodCache.put(key, value); |
| } |
| // Might throw an exception here. |
| return value.getMethod(); |
| } |
| |
| private JInterfaceType getOrCreateExternalType(SourceInfo info, char[][] compoundName) { |
| String name = BuildTypeMap.dotify(compoundName); |
| JInterfaceType external = (JInterfaceType) program.getFromTypeMap(name); |
| if (external == null) { |
| external = program.createInterface(info, name); |
| external.setExternal(true); |
| } |
| return external; |
| } |
| |
| /** |
| * Get a new label of a particular name, or create a new one if it doesn't |
| * exist already. |
| */ |
| private JLabel getOrCreateLabel(SourceInfo info, JMethod enclosingMethod, char[] name) { |
| if (name == null) { |
| return null; |
| } |
| String sname = String.valueOf(name); |
| Map<String, JLabel> lblMap = this.labelMap.get(enclosingMethod); |
| if (lblMap == null) { |
| lblMap = new HashMap<String, JLabel>(); |
| this.labelMap.put(enclosingMethod, lblMap); |
| } |
| JLabel jlabel = lblMap.get(sname); |
| if (jlabel == null) { |
| jlabel = new JLabel(info, sname); |
| lblMap.put(sname, jlabel); |
| } |
| return jlabel; |
| } |
| |
| private JPrimitiveType getPrimitiveTypeForWrapperType(JClassType wrapperType) { |
| String wrapperTypeName = wrapperType.getName(); |
| if ("java.lang.Integer".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveInt(); |
| } else if ("java.lang.Boolean".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveBoolean(); |
| } else if ("java.lang.Character".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveChar(); |
| } else if ("java.lang.Long".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveLong(); |
| } else if ("java.lang.Short".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveShort(); |
| } else if ("java.lang.Byte".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveByte(); |
| } else if ("java.lang.Double".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveDouble(); |
| } else if ("java.lang.Float".equals(wrapperTypeName)) { |
| return program.getTypePrimitiveFloat(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Gets a JParameter iterator for a constructor method over its synthetic |
| * local parameters. |
| */ |
| private Iterator<JParameter> getSyntheticLocalsIterator() { |
| return currentMethod.getParams().listIterator(currentMethod.getOriginalParamTypes().size()); |
| } |
| |
| private void implementMethod(JMethod method, JExpression returnValue) { |
| assert method != null; |
| JMethodBody body = (JMethodBody) method.getBody(); |
| JBlock block = body.getBlock(); |
| |
| SourceInfo info; |
| if (block.getStatements().size() > 0) { |
| info = block.getStatements().get(0).getSourceInfo(); |
| } else { |
| info = method.getSourceInfo(); |
| } |
| |
| block.clear(); |
| block.addStmt(new JReturnStatement(info, returnValue)); |
| } |
| |
| /* |
| * Determine the destination type for an implicit conversion of the given |
| * expression. Beware that when autoboxing, the type of the expression is |
| * not necessarily the same as the type of the box to be created. The JDT |
| * figures out what the necessary conversion is, depending on the context |
| * the expression appears in, and stores it in |
| * <code>x.implicitConversion</code>, so extract it from there. |
| */ |
| private JPrimitiveType implicitConversionTargetType(Expression x) |
| throws InternalCompilerException { |
| /* |
| * This algorithm for finding the target type is copied from |
| * org.eclipse.jdt |
| * .internal.compiler.codegen.CodeStream.generateReturnBytecode() . |
| */ |
| switch ((x.implicitConversion & TypeIds.IMPLICIT_CONVERSION_MASK) >> 4) { |
| case TypeIds.T_boolean: |
| return program.getTypePrimitiveBoolean(); |
| case TypeIds.T_byte: |
| return program.getTypePrimitiveByte(); |
| case TypeIds.T_char: |
| return program.getTypePrimitiveChar(); |
| case TypeIds.T_double: |
| return program.getTypePrimitiveDouble(); |
| case TypeIds.T_float: |
| return program.getTypePrimitiveFloat(); |
| case TypeIds.T_int: |
| return program.getTypePrimitiveInt(); |
| case TypeIds.T_long: |
| return program.getTypePrimitiveLong(); |
| case TypeIds.T_short: |
| return program.getTypePrimitiveShort(); |
| default: |
| throw new InternalCompilerException("Could not determine the desired box type"); |
| } |
| } |
| |
| /** |
| * Check whether the specified type is definitely for an enum class. |
| * |
| * @param type The type being tested |
| * @return whether it is certainly an enum |
| */ |
| private boolean isEnumType(JType type) { |
| if (type instanceof JClassType) { |
| return ((JClassType) type).isEnumOrSubclass() != null; |
| } |
| |
| if (type instanceof JNonNullType) { |
| return isEnumType(((JNonNullType) type).getUnderlyingType()); |
| } |
| |
| return false; |
| } |
| |
| private SourceInfo makeSourceInfo(Statement x) { |
| int startLine = |
| Util.getLineNumber(x.sourceStart, currentSeparatorPositions, 0, |
| currentSeparatorPositions.length - 1); |
| SourceOrigin toReturn = |
| SourceOrigin.create(x.sourceStart, x.sourceEnd, startLine, currentFileName); |
| if (currentMethod != null) { |
| return currentMethod.getSourceInfo().makeChild(toReturn); |
| } |
| return toReturn; |
| } |
| |
| private JExpression maybeCast(JType expected, JExpression expression) { |
| if (expected != expression.getType()) { |
| // Must be a generic; insert a cast operation. |
| JReferenceType toType = (JReferenceType) expected; |
| return new JCastOperation(expression.getSourceInfo(), toType, expression); |
| } else { |
| return expression; |
| } |
| } |
| |
| /** |
| * Sometimes a variable reference can be to a local or parameter in an an |
| * enclosing method. This is a tricky situation to detect. There's no |
| * obvious way to tell, but the clue we can get from JDT is that the local's |
| * containing method won't be the same as the method we're currently |
| * processing. |
| * |
| * Once we have this clue, we can use getEmulationPath to compute the |
| * current class's binding for that field. |
| */ |
| private JVariable possiblyReferenceOuterLocal(JVariable variable, Binding binding) { |
| |
| if (variable instanceof JLocal || variable instanceof JParameter) { |
| LocalVariableBinding localBinding = (LocalVariableBinding) binding; |
| if (localBinding.declaringScope.methodScope() != currentMethodScope) { |
| variable = null; |
| VariableBinding[] vars = currentMethodScope.getEmulationPath(localBinding); |
| if (vars == null) { |
| return null; |
| } |
| assert (vars.length == 1); |
| VariableBinding varBinding = vars[0]; |
| |
| // See if there's an available parameter |
| if (varBinding instanceof SyntheticArgumentBinding) { |
| JType type = (JType) typeMap.get(varBinding.type); |
| String name = String.valueOf(varBinding.name); |
| for (int i = 0; i < currentMethod.getParams().size(); ++i) { |
| JParameter param = currentMethod.getParams().get(i); |
| if (type == param.getType() && name.equals(param.getName())) { |
| variable = param; |
| break; |
| } |
| } |
| } |
| |
| // just use the field |
| if (variable == null) { |
| variable = (JField) typeMap.get(varBinding); |
| } |
| |
| // now we have an updated variable that we can create our ref from |
| } |
| } else { |
| assert variable instanceof JField; |
| // In a constructor, prefer the ctor arg rather than the field. |
| if (currentOuterThisRefParams != null) { |
| JParameter ctorArg = currentOuterThisRefParams.get(variable); |
| if (ctorArg != null) { |
| variable = ctorArg; |
| } |
| } |
| } |
| return variable; |
| } |
| |
| /** |
| * Process a {@link ArtificialRescue.Rescue} element. |
| */ |
| private void processArtificialRescue(RescueData rescue) { |
| JReferenceType classType = (JReferenceType) program.getTypeFromJsniRef(rescue.getClassName()); |
| assert classType != null; |
| if (rescue.isInstantiable()) { |
| currentClass.addArtificialRescue(classType); |
| } |
| |
| if (classType instanceof JDeclaredType) { |
| List<String> toRescue = new ArrayList<String>(); |
| Collections.addAll(toRescue, rescue.getFields()); |
| Collections.addAll(toRescue, rescue.getMethods()); |
| |
| for (String name : toRescue) { |
| JsniRef ref = JsniRef.parse("@" + classType.getName() + "::" + name); |
| final String[] errors = {null}; |
| JNode node = |
| JsniRefLookup.findJsniRefTarget(ref, program, new JsniRefLookup.ErrorReporter() { |
| public void reportError(String error) { |
| errors[0] = error; |
| } |
| }); |
| if (errors[0] != null) { |
| // Should have been caught by ArtificialRescueChecker |
| throw new InternalCompilerException("Unable to artificially rescue " + name + ": " |
| + errors[0]); |
| } |
| |
| if (node instanceof JType) { |
| // Already added the type above. |
| } else { |
| currentClass.addArtificialRescue(node); |
| } |
| if (node instanceof JField) { |
| JField field = (JField) node; |
| if (!field.isFinal()) { |
| field.setVolatile(); |
| } |
| } |
| } |
| } |
| } |
| |
| private void processArtificialRescues(Annotation[] annotations) { |
| if (annotations == null) { |
| return; |
| } |
| RescueData[] rescues = RescueData.createFromAnnotations(annotations); |
| for (RescueData rescue : rescues) { |
| processArtificialRescue(rescue); |
| } |
| } |
| |
| /** |
| * Helper for creating all JBinaryOperation. Several different JDT nodes can |
| * result in binary operations: AND_AND_Expression, Assignment, |
| * BinaryExpresion, CompoundAssignment, EqualExpression, and |
| * OR_OR_Expression. Hopefully the specific operators that can result in |
| * each different JDT type won't change between releases, because we only |
| * look for the specific operators that we think should match each JDT node, |
| * and throw an error if there's a mismatch. |
| */ |
| private JExpression processBinaryOperation(SourceInfo info, JBinaryOperator op, JType type, |
| Expression arg1, Expression arg2) { |
| JExpression exprArg1 = dispProcessExpression(arg1); |
| JExpression exprArg2 = dispProcessExpression(arg2); |
| JBinaryOperation binaryOperation = new JBinaryOperation(info, type, op, exprArg1, exprArg2); |
| return binaryOperation; |
| } |
| |
| private JExpression processQualifiedThisOrSuperRef(QualifiedThisReference x, JClassType qualType) { |
| /* |
| * WEIRD: If a thisref or superref is qualified with the EXACT type of the |
| * innermost type (in other words, a needless qualifier), it must refer to |
| * that innermost type, because a class can never be nested inside of |
| * itself. In this case, we must treat it as if it were not qualified. |
| * |
| * In all other cases, the qualified thisref or superref cannot possibly |
| * refer to the innermost type (even if the innermost type could be cast |
| * to a compatible type), so we must create a reference to some outer |
| * type. |
| */ |
| SourceInfo info = makeSourceInfo(x); |
| if (qualType == currentClass) { |
| return createThisRef(info, qualType); |
| } else { |
| return createQualifiedThisRef(info, qualType); |
| } |
| } |
| |
| private InternalCompilerException translateException(Object node, Throwable e) { |
| if (e instanceof VirtualMachineError) { |
| // Always rethrow VM errors (an attempt to wrap may fail). |
| throw (VirtualMachineError) e; |
| } |
| InternalCompilerException ice; |
| if (e instanceof InternalCompilerException) { |
| ice = (InternalCompilerException) e; |
| } else { |
| ice = new InternalCompilerException("Error constructing Java AST", e); |
| } |
| String className = node.getClass().getName(); |
| String description = node.toString(); |
| SourceInfo sourceInfo = null; |
| if (node instanceof Statement) { |
| sourceInfo = makeSourceInfo((Statement) node); |
| } |
| ice.addNode(className, description, sourceInfo); |
| return ice; |
| } |
| |
| /** |
| * For a given method, try to find all methods that it overrides/implements. |
| * This version does not use JDT. |
| */ |
| private void tryFindUpRefs(JMethod method) { |
| List<JMethod> overrides = new ArrayList<JMethod>(); |
| tryFindUpRefs(method, overrides); |
| method.addOverrides(overrides); |
| } |
| |
| private void tryFindUpRefs(JMethod method, List<JMethod> overrides) { |
| if (method.getEnclosingType() != null) { |
| tryFindUpRefsRecursive(method, method.getEnclosingType(), overrides); |
| } |
| } |
| |
| /** |
| * For a given method(and method binding), try to find all methods that it |
| * overrides/implements. |
| */ |
| private void tryFindUpRefs(JMethod method, MethodBinding binding) { |
| // Should never get a parameterized instance here. |
| assert binding == binding.original(); |
| tryFindUpRefsRecursive(method, binding, binding.declaringClass); |
| } |
| |
| /** |
| * For a given method(and method binding), recursively try to find all |
| * methods that it overrides/implements. |
| */ |
| private void tryFindUpRefsRecursive(JMethod method, JDeclaredType searchThisType, |
| List<JMethod> overrides) { |
| |
| // See if this class has any uprefs, unless this class is myself |
| if (method.getEnclosingType() != searchThisType) { |
| for (JMethod upRef : searchThisType.getMethods()) { |
| if (JTypeOracle.methodsDoMatch(method, upRef) && !overrides.contains(upRef)) { |
| overrides.add(upRef); |
| break; |
| } |
| } |
| } |
| |
| // recurse super class |
| if (searchThisType.getSuperClass() != null) { |
| tryFindUpRefsRecursive(method, searchThisType.getSuperClass(), overrides); |
| } |
| |
| // recurse super interfaces |
| for (JInterfaceType intf : searchThisType.getImplements()) { |
| tryFindUpRefsRecursive(method, intf, overrides); |
| } |
| } |
| |
| /** |
| * For a given method(and method binding), recursively try to find all |
| * methods that it overrides/implements. |
| */ |
| private void tryFindUpRefsRecursive(JMethod method, MethodBinding binding, |
| ReferenceBinding searchThisType) { |
| /* |
| * Always look for uprefs in the original, so we can correctly compare |
| * erased signatures. The general design for uprefs is to model what the |
| * JVM does in terms of matching up overrides based on binary match. |
| */ |
| searchThisType = (ReferenceBinding) searchThisType.original(); |
| |
| // See if this class has any uprefs, unless this class is myself |
| if (binding.declaringClass != searchThisType) { |
| for (MethodBinding tryMethod : searchThisType.getMethods(binding.selector)) { |
| if (binding.returnType.erasure() == tryMethod.returnType.erasure() |
| && binding.areParameterErasuresEqual(tryMethod)) { |
| JMethod upRef = (JMethod) typeMap.get(tryMethod); |
| if (!method.getOverrides().contains(upRef)) { |
| method.addOverride(upRef); |
| break; |
| } |
| } |
| } |
| } |
| |
| // recurse super class |
| if (searchThisType.superclass() != null) { |
| tryFindUpRefsRecursive(method, binding, searchThisType.superclass()); |
| } |
| |
| // recurse super interfaces |
| if (searchThisType.superInterfaces() != null) { |
| for (int i = 0; i < searchThisType.superInterfaces().length; i++) { |
| ReferenceBinding intf = searchThisType.superInterfaces()[i]; |
| tryFindUpRefsRecursive(method, binding, intf); |
| } |
| } |
| } |
| |
| private JExpression unbox(JExpression toUnbox, JClassType wrapperType) { |
| JPrimitiveType primitiveType = getPrimitiveTypeForWrapperType(wrapperType); |
| if (primitiveType == null) { |
| throw new InternalCompilerException(toUnbox, "Attempt to unbox unexpected type '" |
| + wrapperType.getName() + "'", null); |
| } |
| |
| String valueMethodName = primitiveType.getName() + "Value"; |
| JMethod valueMethod = null; |
| for (Object element : wrapperType.getMethods()) { |
| JMethod method = (JMethod) element; |
| if (method.getName().equals(valueMethodName)) { |
| if (method.getParams().isEmpty()) { |
| // It's a match! |
| valueMethod = method; |
| break; |
| } |
| } |
| } |
| |
| if (valueMethod == null) { |
| throw new InternalCompilerException(toUnbox, "Expected to find a method on '" |
| + wrapperType.getName() + "' whose signature matches 'public " |
| + primitiveType.getName() + " " + valueMethodName + "()'", null); |
| } |
| |
| JMethodCall unboxCall = new JMethodCall(toUnbox.getSourceInfo(), toUnbox, valueMethod); |
| return unboxCall; |
| } |
| |
| private void writeEnumValueOfMethod(JEnumType type, JField valuesField) { |
| JField mapField; |
| { |
| /* |
| * Make an inner class to hold a lazy-init name-value map. We use a |
| * class to take advantage of its clinit. |
| * |
| * class Map { $MAP = Enum.createValueOfMap($VALUES); } |
| */ |
| SourceInfo typeInfo = type.getSourceInfo().makeChild(); |
| JClassType mapClass = program.createClass(typeInfo, type.getName() + "$Map", false, true); |
| typeInfo.addCorrelation(typeInfo.getCorrelator().by(mapClass)); |
| mapClass.setSuperClass(program.getTypeJavaLangObject()); |
| SourceInfo fieldInfo = typeInfo.makeChild(); |
| mapField = |
| program.createField(fieldInfo, "$MAP", mapClass, program.getJavaScriptObject(), true, |
| Disposition.FINAL); |
| fieldInfo.addCorrelation(fieldInfo.getCorrelator().by(mapField)); |
| |
| SourceInfo methodInfo = typeInfo.makeChild(); |
| JMethodCall call = |
| new JMethodCall(methodInfo, null, program.getIndexedMethod("Enum.createValueOfMap")); |
| call.addArg(new JFieldRef(methodInfo, null, valuesField, type)); |
| JFieldRef mapRef = new JFieldRef(methodInfo, null, mapField, type); |
| JDeclarationStatement declStmt = new JDeclarationStatement(methodInfo, mapRef, call); |
| JMethod clinit = |
| program.createMethod(methodInfo, "$clinit", mapClass, program.getTypeVoid(), false, |
| true, true, true, false); |
| clinit.freezeParamTypes(); |
| methodInfo.addCorrelation(methodInfo.getCorrelator().by(clinit)); |
| JBlock clinitBlock = ((JMethodBody) clinit.getBody()).getBlock(); |
| clinitBlock.addStmt(declStmt); |
| mapField.setInitializer(declStmt); |
| } |
| |
| /* |
| * return Enum.valueOf(Enum$Map.Map.$MAP, name); |
| */ |
| { |
| SourceInfo sourceInfo = currentMethodBody.getSourceInfo(); |
| JFieldRef mapRef = new JFieldRef(sourceInfo, null, mapField, type); |
| JVariableRef nameRef = createVariableRef(sourceInfo, currentMethod.getParams().get(0)); |
| JMethod delegateTo = program.getIndexedMethod("Enum.valueOf"); |
| JMethodCall call = new JMethodCall(sourceInfo, null, delegateTo); |
| call.addArgs(mapRef, nameRef); |
| |
| currentMethodBody.getBlock().addStmt(new JReturnStatement(sourceInfo, call)); |
| } |
| } |
| |
| private JField writeEnumValuesMethod(JEnumType type) { |
| JField valuesField; |
| { |
| // $VALUES = new E[]{A,B,B}; |
| SourceInfo fieldInfo = type.getSourceInfo().makeChild(); |
| JArrayType enumArrayType = program.getTypeArray(type); |
| valuesField = |
| program.createField(fieldInfo, "$VALUES", type, enumArrayType, true, Disposition.FINAL); |
| fieldInfo.addCorrelation(fieldInfo.getCorrelator().by(valuesField)); |
| List<JExpression> initializers = new ArrayList<JExpression>(); |
| for (JEnumField field : type.getEnumList()) { |
| JFieldRef fieldRef = new JFieldRef(fieldInfo, null, field, type); |
| initializers.add(fieldRef); |
| } |
| JNewArray newExpr = JNewArray.createInitializers(fieldInfo, enumArrayType, initializers); |
| JFieldRef valuesRef = new JFieldRef(fieldInfo, null, valuesField, type); |
| JDeclarationStatement declStmt = new JDeclarationStatement(fieldInfo, valuesRef, newExpr); |
| JBlock clinitBlock = ((JMethodBody) type.getMethods().get(0).getBody()).getBlock(); |
| |
| /* |
| * HACKY: the $VALUES array must be initialized immediately after all of |
| * the enum fields, but before any user initialization (which might rely |
| * on $VALUES). The "1 + " is the statement containing the call to |
| * Enum.$clinit(). |
| */ |
| int insertionPoint = 1 + initializers.size(); |
| assert clinitBlock.getStatements().size() >= initializers.size() + 1; |
| clinitBlock.addStmt(insertionPoint, declStmt); |
| valuesField.setInitializer(declStmt); |
| } |
| { |
| // return $VALUES; |
| SourceInfo sourceInfo = currentMethod.getSourceInfo(); |
| JFieldRef valuesRef = new JFieldRef(sourceInfo, null, valuesField, type); |
| currentMethodBody.getBlock().addStmt(new JReturnStatement(sourceInfo, valuesRef)); |
| } |
| return valuesField; |
| } |
| } |
| |
| /** |
| * Resolve JSNI refs; replace with compile-time constants where appropriate. |
| */ |
| private static class JsniRefGenerationVisitor extends JModVisitor { |
| |
| private class JsniRefResolver extends JsModVisitor { |
| private final AbstractMethodDeclaration methodDecl; |
| private final JsniMethodBody nativeMethodBody; |
| |
| private JsniRefResolver(AbstractMethodDeclaration methodDecl, JsniMethodBody nativeMethodBody) { |
| this.methodDecl = methodDecl; |
| this.nativeMethodBody = nativeMethodBody; |
| } |
| |
| @Override |
| public void endVisit(JsNameRef x, JsContext ctx) { |
| String ident = x.getIdent(); |
| if (ident.charAt(0) == '@') { |
| processNameRef(x, ctx); |
| } |
| } |
| |
| private JNode findJsniRefTarget(final SourceInfo info, String ident) { |
| JsniRef parsed = JsniRef.parse(ident); |
| if (parsed == null) { |
| JsniCollector.reportJsniError(info, methodDecl, "Badly formatted native reference '" |
| + ident + "'"); |
| return null; |
| } |
| |
| JProgram prog = program; |
| |
| return JsniRefLookup.findJsniRefTarget(parsed, prog, new JsniRefLookup.ErrorReporter() { |
| public void reportError(String error) { |
| JsniCollector.reportJsniError(info, methodDecl, error); |
| } |
| }); |
| } |
| |
| private void processClassLiteral(JsNameRef nameRef, SourceInfo info, JType type, JsContext ctx) { |
| assert !ctx.isLvalue(); |
| JsniClassLiteral classLiteral = new JsniClassLiteral(info, nameRef.getIdent(), type); |
| nativeMethodBody.addClassRef(classLiteral); |
| } |
| |
| private void processField(JsNameRef nameRef, SourceInfo info, JField field, JsContext ctx) { |
| /* |
| * We must replace any compile-time constants with the constant value of |
| * the field. |
| */ |
| if (field.isCompileTimeConstant()) { |
| assert !ctx.isLvalue(); |
| JLiteral initializer = field.getConstInitializer(); |
| JType type = initializer.getType(); |
| if (type instanceof JPrimitiveType || program.isJavaLangString(type)) { |
| GenerateJavaScriptLiterals generator = new GenerateJavaScriptLiterals(); |
| generator.accept(initializer); |
| JsExpression result = generator.peek(); |
| assert (result != null); |
| ctx.replaceMe(result); |
| return; |
| } |
| } |
| |
| // Normal: create a jsniRef. |
| JsniFieldRef fieldRef = |
| new JsniFieldRef(info, nameRef.getIdent(), field, currentClass, ctx.isLvalue()); |
| nativeMethodBody.addJsniRef(fieldRef); |
| } |
| |
| private void processMethod(JsNameRef nameRef, SourceInfo info, JMethod method, JsContext ctx) { |
| assert !ctx.isLvalue(); |
| JsniMethodRef methodRef = |
| new JsniMethodRef(info, nameRef.getIdent(), method, program.getJavaScriptObject()); |
| nativeMethodBody.addJsniRef(methodRef); |
| } |
| |
| private void processNameRef(JsNameRef nameRef, JsContext ctx) { |
| SourceInfo info = nativeMethodBody.getSourceInfo(); |
| // TODO: make this tighter when we have real source info |
| // JSourceInfo info = translateInfo(nameRef.getInfo()); |
| String ident = nameRef.getIdent(); |
| JNode node = jsniMap.get(ident); |
| if (node == null) { |
| node = findJsniRefTarget(info, ident); |
| if (node == null) { |
| return; // already reported error |
| } |
| jsniMap.put(ident, node); |
| } |
| |
| if (node instanceof JField) { |
| processField(nameRef, info, (JField) node, ctx); |
| } else if (node instanceof JMethod) { |
| processMethod(nameRef, info, (JMethod) node, ctx); |
| } else if (node instanceof JType) { |
| processClassLiteral(nameRef, info, (JType) node, ctx); |
| } else { |
| throw new InternalCompilerException(node, |
| "JSNI reference to something other than a class, field, or method?", null); |
| } |
| } |
| } |
| |
| private JDeclaredType currentClass; |
| |
| private final Map<String, JNode> jsniMap = new HashMap<String, JNode>(); |
| |
| private final Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap; |
| |
| private final JProgram program; |
| |
| public JsniRefGenerationVisitor(JProgram program, |
| Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap) { |
| this.program = program; |
| this.jsniMethodMap = jsniMethodMap; |
| } |
| |
| @Override |
| public void endVisit(JClassType x, Context ctx) { |
| currentClass = null; |
| } |
| |
| @Override |
| public void endVisit(JsniMethodBody x, Context ctx) { |
| new JsniRefResolver(jsniMethodMap.get(x), x).accept(x.getFunc()); |
| } |
| |
| @Override |
| public boolean visit(JClassType x, Context ctx) { |
| currentClass = x; |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMethodBody x, Context ctx) { |
| return false; |
| } |
| } |
| |
| /** |
| * Combines the information from the JDT type nodes and the type map to create |
| * a JProgram structure. |
| */ |
| public static void exec(TypeDeclaration[] types, TypeMap typeMap, JProgram jprogram, |
| JJSOptions options) { |
| Event generateJavaAstEvent = SpeedTracerLogger.start(CompilerEventType.GENERATE_JAVA_AST); |
| // Construct the basic AST. |
| JavaASTGenerationVisitor v = new JavaASTGenerationVisitor(typeMap, jprogram, options); |
| for (TypeDeclaration type : types) { |
| v.processType(type); |
| } |
| for (TypeDeclaration type : types) { |
| v.addBridgeMethods(type.binding); |
| } |
| Collections.sort(jprogram.getDeclaredTypes(), new HasNameSort()); |
| |
| // Process JSNI. |
| Map<JsniMethodBody, AbstractMethodDeclaration> jsniMethodMap = v.getJsniMethodMap(); |
| new JsniRefGenerationVisitor(jprogram, jsniMethodMap).accept(jprogram); |
| generateJavaAstEvent.end(); |
| } |
| |
| /** |
| * Returns <code>true</code> if JDT optimized the condition to |
| * <code>false</code>. |
| */ |
| private static boolean isOptimizedFalse(Expression condition) { |
| if (condition != null) { |
| Constant cst = condition.optimizedBooleanConstant(); |
| if (cst != Constant.NotAConstant) { |
| if (cst.booleanValue() == false) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns <code>true</code> if JDT optimized the condition to |
| * <code>true</code>. |
| */ |
| private static boolean isOptimizedTrue(Expression condition) { |
| if (condition != null) { |
| Constant cst = condition.optimizedBooleanConstant(); |
| if (cst != Constant.NotAConstant) { |
| if (cst.booleanValue()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isScript(JProgram program) { |
| return !program.getTypeJavaLangObject().isExternal(); |
| } |
| } |