/* * 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.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JArrayRef; import com.google.gwt.dev.jjs.ast.JArrayType; import com.google.gwt.dev.jjs.ast.JBinaryOperation; import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JCastMap; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JIntLiteral; import com.google.gwt.dev.jjs.ast.JLiteral; import com.google.gwt.dev.jjs.ast.JMethod; 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.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.RuntimeConstants; import com.google.gwt.dev.jjs.ast.js.JsonArray; import java.util.Collections; import java.util.List; /** * Replace array accesses and instantiations with calls to the Array class. * Depends on {@link CompoundAssignmentNormalizer} and {@link ImplementCastsAndTypeChecks} * having already run. */ public class ArrayNormalizer { private class ArrayVisitor extends JModVisitor { @Override public void endVisit(JBinaryOperation x, Context ctx) { JArrayRef arrayRef = needsSetCheck(x); if (arrayRef == null) { return; } // replace this assignment with a call to setCheck() JMethodCall call = new JMethodCall(x.getSourceInfo(), null, setCheckMethod); call.addArgs(arrayRef.getInstance(), arrayRef.getIndexExpr(), x.getRhs()); ctx.replaceMe(call); } @Override public void endVisit(JNewArray x, Context ctx) { JArrayType type = x.getArrayType(); List<JExpression> initializers = x.getInitializers(); if (initializers != null) { JsonArray initializerArray = getInitializerArray(x); if (program.isUntypedArrayType(type)) { ctx.replaceMe(initializerArray); return; } ctx.replaceMe(createArrayFromInitializers(x, type)); return; } if (program.isUntypedArrayType(type) && type.getDims() == 1) { // Create a plain array. ctx.replaceMe(new JMethodCall(x.getSourceInfo(), null, program.getIndexedMethod(RuntimeConstants.ARRAY_NEW_ARRAY), x.getDimensionExpressions().get(0))); return; } int suppliedDimensions = x.getDimensionExpressions().size(); assert (suppliedDimensions >= 1); if (suppliedDimensions == 1) { ctx.replaceMe(initializeUnidimensionalArray(x, type)); return; } ctx.replaceMe(initializeMultidimensionalArray(x, type)); } private JRuntimeTypeReference getElementRuntimeTypeReference(SourceInfo sourceInfo, JArrayType arrayType) { JType elementType = arrayType.getElementType(); if (!(elementType instanceof JReferenceType)) { // elementType is a primitive type, store check will be performed statically. elementType = JReferenceType.NULL_TYPE; } if (program.typeOracle.isEffectivelyJavaScriptObject(elementType)) { /* * treat types that are effectively JSO's as JSO's, for the purpose of * castability checking */ elementType = program.getJavaScriptObject(); } else { elementType = elementType.getUnderlyingType(); } elementType = program.normalizeJsoType(elementType); return new JRuntimeTypeReference(sourceInfo, program.getTypeJavaLangObject(), (JReferenceType) elementType); } private JExpression getOrCreateCastMap(SourceInfo sourceInfo, JArrayType arrayType) { JCastMap castableTypeMap = program.getCastMap(arrayType); if (castableTypeMap == null) { return new JCastMap(sourceInfo, program.getTypeJavaLangObject(), Collections.<JReferenceType>emptyList()); } return castableTypeMap; } private JExpression initializeUnidimensionalArray(JNewArray x, JArrayType arrayType) { // override the type of the called method with the array's type SourceInfo sourceInfo = x.getSourceInfo(); JLiteral classLit = x.getLeafTypeClassLiteral(); JExpression castableTypeMap = getOrCreateCastMap(sourceInfo, arrayType); JRuntimeTypeReference arrayElementRuntimeTypeReference = getElementRuntimeTypeReference(sourceInfo, arrayType); JType elementType = arrayType.getElementType(); JIntLiteral elementTypeCategory = getTypeCategoryLiteral(elementType); JExpression dim = x.getDimensionExpressions().get(0); JMethodCall call = new JMethodCall(sourceInfo, null, initializeUnidimensionalArrayMethod); call.overrideReturnType(arrayType); call.addArgs(classLit, castableTypeMap, arrayElementRuntimeTypeReference, dim, elementTypeCategory, program.getLiteralInt(arrayType.getDims())); return call; } private JExpression initializeMultidimensionalArray(JNewArray x, JArrayType arrayType) { // override the type of the called method with the array's type SourceInfo sourceInfo = x.getSourceInfo(); JsonArray castableTypeMaps = new JsonArray(sourceInfo, program.getJavaScriptObject()); JsonArray elementTypeReferences = new JsonArray(sourceInfo, program.getJavaScriptObject()); JsonArray dimList = new JsonArray(sourceInfo, program.getJavaScriptObject()); JType currentElementType = arrayType; JLiteral classLit = x.getLeafTypeClassLiteral(); for (int i = 0; i < x.getDimensionExpressions().size(); ++i) { // Walk down each type from most dims to least. JArrayType curArrayType = (JArrayType) currentElementType; JExpression castableTypeMap = getOrCreateCastMap(sourceInfo, curArrayType); castableTypeMaps.getExpressions().add(castableTypeMap); JRuntimeTypeReference elementTypeIdLit = getElementRuntimeTypeReference(sourceInfo, curArrayType); elementTypeReferences.getExpressions().add(elementTypeIdLit); dimList.getExpressions().add(x.getDimensionExpressions().get(i)); currentElementType = curArrayType.getElementType(); } JType leafElementType = currentElementType; JIntLiteral leafElementTypeCategory = getTypeCategoryLiteral(leafElementType); JMethodCall call = new JMethodCall(sourceInfo, null, initializeMultidimensionalArrayMethod); call.overrideReturnType(arrayType); call.addArgs(classLit, castableTypeMaps, elementTypeReferences, leafElementTypeCategory, dimList, program.getLiteralInt(x.getDimensionExpressions().size())); return call; } private JExpression createArrayFromInitializers(JNewArray x, JArrayType arrayType) { // override the type of the called method with the array's type SourceInfo sourceInfo = x.getSourceInfo(); JExpression classLitExpression = program.createArrayClassLiteralExpression(x.getSourceInfo(), x.getLeafTypeClassLiteral(), arrayType.getDims()); JExpression castableTypeMap = getOrCreateCastMap(sourceInfo, arrayType); JRuntimeTypeReference elementTypeIds = getElementRuntimeTypeReference(sourceInfo, arrayType); JsonArray initializers = new JsonArray(sourceInfo, program.getJavaScriptObject(), x.getInitializers()); JIntLiteral leafElementTypeCategory = getTypeCategoryLiteral(arrayType.getElementType()); JMethodCall call = new JMethodCall(sourceInfo, null, stampJavaTypeInfoMethod); call.overrideReturnType(arrayType); call.addArgs(classLitExpression, castableTypeMap, elementTypeIds, leafElementTypeCategory, initializers); return call; } /** * Returns a literal that represent the type category for a type. */ private JIntLiteral getTypeCategoryLiteral(JType type) { return JIntLiteral.get(TypeCategory.typeCategoryForType(type, program).ordinal()); } } private JArrayRef needsSetCheck(JBinaryOperation x) { if (x.getOp() != JBinaryOperator.ASG || !(x.getLhs() instanceof JArrayRef)) { return null; } JArrayRef arrayRef = (JArrayRef) x.getLhs(); JType elementType = arrayRef.getType(); JExpression arrayInstance = arrayRef.getInstance(); if (elementType.isNullType()) { // JNullType will generate a null pointer exception instead, return null; } else if (!(elementType instanceof JReferenceType)) { // Primitive array types are statically correct, no need to set check. return null; } else if (!arrayInstance.getType().canBeSubclass() && program.typeOracle.castSucceedsTrivially((JReferenceType) x.getRhs().getType(), (JReferenceType) elementType)) { // There is no need to check as the static check already proved the cast is correct. return null; } // TODO(rluble): native[] setCheck can also be omitted. return arrayRef; } public static JsonArray getInitializerArray(JNewArray x) { return new JsonArray(x.getSourceInfo(), x.getType(), x.getInitializers()); } public static void exec(JProgram program) { new ArrayNormalizer(program).execImpl(); } private final JMethod initializeUnidimensionalArrayMethod; private final JMethod initializeMultidimensionalArrayMethod; private final JMethod stampJavaTypeInfoMethod; private final JMethod setCheckMethod; private final JProgram program; private ArrayNormalizer(JProgram program) { this.program = program; setCheckMethod = program.getIndexedMethod(RuntimeConstants.ARRAY_SET_CHECK); initializeUnidimensionalArrayMethod = program.getIndexedMethod( RuntimeConstants.ARRAY_INITIALIZE_UNIDIMENSIONAL_ARRAY); initializeMultidimensionalArrayMethod = program.getIndexedMethod( RuntimeConstants.ARRAY_INITIALIZE_MULTIDIMENSIONAL_ARRAY); stampJavaTypeInfoMethod = program.getIndexedMethod(RuntimeConstants.ARRAY_STAMP_JAVA_TYPE_INFO); } private void execImpl() { new ArrayVisitor().accept(program); } }