001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.jjs.impl;
017:
018: import com.google.gwt.dev.jjs.ast.Context;
019: import com.google.gwt.dev.jjs.ast.JArrayRef;
020: import com.google.gwt.dev.jjs.ast.JArrayType;
021: import com.google.gwt.dev.jjs.ast.JBinaryOperation;
022: import com.google.gwt.dev.jjs.ast.JBinaryOperator;
023: import com.google.gwt.dev.jjs.ast.JCastOperation;
024: import com.google.gwt.dev.jjs.ast.JClassType;
025: import com.google.gwt.dev.jjs.ast.JExpression;
026: import com.google.gwt.dev.jjs.ast.JInstanceOf;
027: import com.google.gwt.dev.jjs.ast.JIntLiteral;
028: import com.google.gwt.dev.jjs.ast.JMethod;
029: import com.google.gwt.dev.jjs.ast.JMethodCall;
030: import com.google.gwt.dev.jjs.ast.JModVisitor;
031: import com.google.gwt.dev.jjs.ast.JNullLiteral;
032: import com.google.gwt.dev.jjs.ast.JNullType;
033: import com.google.gwt.dev.jjs.ast.JPrimitiveType;
034: import com.google.gwt.dev.jjs.ast.JProgram;
035: import com.google.gwt.dev.jjs.ast.JReferenceType;
036: import com.google.gwt.dev.jjs.ast.JType;
037: import com.google.gwt.dev.jjs.ast.JTypeOracle;
038: import com.google.gwt.dev.jjs.ast.JVisitor;
039: import com.google.gwt.dev.jjs.ast.js.JClassSeed;
040: import com.google.gwt.dev.jjs.ast.js.JsonObject;
041: import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
042:
043: import java.util.ArrayList;
044: import java.util.HashSet;
045: import java.util.IdentityHashMap;
046: import java.util.List;
047: import java.util.Map;
048: import java.util.Set;
049:
050: /**
051: * Replace cast and instanceof operations with calls to the Cast class. Depends
052: * on {@link com.google.gwt.dev.jjs.impl.CatchBlockNormalizer},
053: * {@link com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer}, and
054: * {@link com.google.gwt.dev.jjs.impl.JavaScriptObjectCaster} having already
055: * run.
056: */
057: public class CastNormalizer {
058:
059: private class AssignTypeIdsVisitor extends JVisitor {
060:
061: Set<JClassType> alreadyRan = new HashSet<JClassType>();
062: private Map<JReferenceType, Set<JReferenceType>> queriedTypes = new IdentityHashMap<JReferenceType, Set<JReferenceType>>();
063: private int nextQueryId = 1; // 0 is reserved
064: private final List<JArrayType> instantiatedArrayTypes = new ArrayList<JArrayType>();
065: private List<JClassType> classes = new ArrayList<JClassType>();
066: private List<JsonObject> jsonObjects = new ArrayList<JsonObject>();
067:
068: {
069: JTypeOracle typeOracle = program.typeOracle;
070: for (JArrayType arrayType : program.getAllArrayTypes()) {
071: if (typeOracle.isInstantiatedType(arrayType)) {
072: instantiatedArrayTypes.add(arrayType);
073: }
074: }
075:
076: // Reserve query id 1 for java.lang.String to facilitate the mashup case.
077: // Multiple GWT modules need to modify String's prototype the same way.
078: recordCastInternal(program.getTypeJavaLangString(), program
079: .getTypeJavaLangObject());
080: }
081:
082: public void computeTypeIds() {
083:
084: // the 0th entry is the "always false" entry
085: classes.add(null);
086: jsonObjects.add(new JsonObject(program));
087:
088: /*
089: * Do String first to reserve typeIds 1 and 2 for Object and String,
090: * respectively. This ensures consistent modification of String's
091: * prototype.
092: */
093: computeSourceClass(program.getTypeJavaLangString());
094: assert (classes.size() == 3);
095:
096: /*
097: * Compute the list of classes than can successfully satisfy cast
098: * requests, along with the set of types they can be successfully cast to.
099: * Do it in super type order.
100: */
101: for (JReferenceType type : program.getDeclaredTypes()) {
102: if (type instanceof JClassType) {
103: computeSourceClass((JClassType) type);
104: }
105: }
106:
107: for (JArrayType type : program.getAllArrayTypes()) {
108: computeSourceClass(type);
109: }
110:
111: // pass our info to JProgram
112: program.initTypeInfo(classes, jsonObjects);
113: program.recordQueryIds(queryIds);
114: }
115:
116: /*
117: * If this expression could possibly generate an ArrayStoreException, we
118: * must record a query on the element type being assigned to.
119: */
120: @Override
121: public void endVisit(JBinaryOperation x, Context ctx) {
122: if (x.getOp() == JBinaryOperator.ASG
123: && x.getLhs() instanceof JArrayRef) {
124:
125: // first, calculate the transitive closure of all possible runtime types
126: // the lhs could be
127: JExpression instance = ((JArrayRef) x.getLhs())
128: .getInstance();
129: if (instance.getType() instanceof JNullType) {
130: // will generate a null pointer exception instead
131: return;
132: }
133: JArrayType lhsArrayType = (JArrayType) instance
134: .getType();
135: JType elementType = lhsArrayType.getElementType();
136:
137: // primitives are statically correct
138: if (!(elementType instanceof JReferenceType)) {
139: return;
140: }
141:
142: // element type being final means the assignment is statically correct
143: if (((JReferenceType) elementType).isFinal()) {
144: return;
145: }
146:
147: /*
148: * For every instantiated array type that could -in theory- be the
149: * runtime type of the lhs, we must record a cast from the rhs to the
150: * prospective element type of the lhs.
151: */
152: JTypeOracle typeOracle = program.typeOracle;
153: JType rhsType = x.getRhs().getType();
154: assert (rhsType instanceof JReferenceType);
155: JReferenceType refRhsType = (JReferenceType) rhsType;
156:
157: for (JArrayType arrayType : instantiatedArrayTypes) {
158: if (typeOracle.canTheoreticallyCast(arrayType,
159: lhsArrayType)) {
160: JType itElementType = arrayType
161: .getElementType();
162: if (itElementType instanceof JReferenceType) {
163: recordCastInternal(
164: (JReferenceType) itElementType,
165: refRhsType);
166: }
167: }
168: }
169: }
170: }
171:
172: @Override
173: public void endVisit(JCastOperation x, Context ctx) {
174: if (x.getCastType() != program.getTypeNull()) {
175: recordCast(x.getCastType(), x.getExpr());
176: }
177: }
178:
179: @Override
180: public void endVisit(JInstanceOf x, Context ctx) {
181: assert (x.getTestType() != program.getTypeNull());
182: recordCast(x.getTestType(), x.getExpr());
183: }
184:
185: /**
186: * Create the data for JSON table to capture the mapping from a class to its
187: * query types.
188: */
189: private void computeSourceClass(JClassType type) {
190: if (type == null || alreadyRan.contains(type)) {
191: return;
192: }
193:
194: alreadyRan.add(type);
195:
196: /*
197: * IMPORTANT: Visit my supertype first. The implementation of
198: * com.google.gwt.lang.Cast.wrapJSO() depends on all superclasses having
199: * typeIds that are less than all their subclasses. This allows the same
200: * JSO to be wrapped stronger but not weaker.
201: */
202: computeSourceClass(type.extnds);
203:
204: if (!program.typeOracle.isInstantiatedType(type)) {
205: return;
206: }
207:
208: // Find all possible query types which I can satisfy
209: Set<JReferenceType> yesSet = null;
210:
211: // NOTE: non-deterministic iteration over HashSet and HashMap. This is
212: // okay here because we're just adding things to another HashSet.
213: for (JReferenceType qType : queriedTypes.keySet()) {
214:
215: Set<JReferenceType> querySet = queriedTypes.get(qType);
216: if (program.typeOracle.canTriviallyCast(type, qType)) {
217:
218: for (JReferenceType argType : querySet) {
219:
220: if (program.typeOracle.canTriviallyCast(type,
221: argType)) {
222: if (yesSet == null) {
223: yesSet = new HashSet<JReferenceType>();
224: }
225: yesSet.add(qType);
226: break;
227: }
228: }
229: }
230: }
231:
232: /*
233: * Weird: JavaScriptObjects MUST have a typeId, the implementation of
234: * Cast.wrapJSO depends on it. Object must also have a typeId, to force
235: * String to have an id of 2.
236: */
237: if (yesSet == null && !program.isJavaScriptObject(type)
238: && (type != program.getTypeJavaLangObject())) {
239: return; // won't satisfy anything
240: }
241:
242: // use an array to sort my yes set
243: JReferenceType[] yesArray = new JReferenceType[nextQueryId];
244: if (yesSet != null) {
245: for (JReferenceType yesType : yesSet) {
246: Integer boxedInt = queryIds.get(yesType);
247: yesArray[boxedInt.intValue()] = yesType;
248: }
249: }
250:
251: // create a sparse lookup object
252: JsonObject jsonObject = new JsonObject(program);
253: for (int i = 0; i < nextQueryId; ++i) {
254: if (yesArray[i] != null) {
255: JIntLiteral labelExpr = program.getLiteralInt(i);
256: JIntLiteral valueExpr = program.getLiteralInt(1);
257: jsonObject.propInits.add(new JsonPropInit(program,
258: labelExpr, valueExpr));
259: }
260: }
261:
262: // add an entry for me
263: classes.add(type);
264: jsonObjects.add(jsonObject);
265: }
266:
267: private void recordCast(JType targetType, JExpression rhs) {
268: if (targetType instanceof JReferenceType) {
269: // unconditional cast b/c it would've been a semantic error earlier
270: JReferenceType rhsType = (JReferenceType) rhs.getType();
271: // don't record a type for trivial casts that won't generate code
272: if (rhsType instanceof JClassType) {
273: if (program.typeOracle.canTriviallyCast(rhsType,
274: (JReferenceType) targetType)) {
275: return;
276: }
277: }
278:
279: recordCastInternal((JReferenceType) targetType, rhsType);
280: }
281: }
282:
283: private void recordCastInternal(JReferenceType targetType,
284: JReferenceType rhsType) {
285: JReferenceType toType = targetType;
286: Set<JReferenceType> querySet = queriedTypes.get(toType);
287: if (querySet == null) {
288: queryIds.put(toType, new Integer(nextQueryId++));
289: querySet = new HashSet<JReferenceType>();
290: queriedTypes.put(toType, querySet);
291: }
292: querySet.add(rhsType);
293: }
294: }
295:
296: /**
297: * Explicitly convert any char-typed expressions within a concat operation
298: * into strings.
299: */
300: private class ConcatVisitor extends JModVisitor {
301:
302: private JMethod stringValueOfChar = null;
303:
304: @Override
305: public void endVisit(JBinaryOperation x, Context ctx) {
306: if (x.getType() != program.getTypeJavaLangString()) {
307: return;
308: }
309:
310: if (x.getOp() == JBinaryOperator.ADD) {
311: JExpression newLhs = convertCharString(x.getLhs());
312: JExpression newRhs = convertCharString(x.getRhs());
313: if (newLhs != x.getLhs() || newRhs != x.getRhs()) {
314: JBinaryOperation newExpr = new JBinaryOperation(
315: program, x.getSourceInfo(), program
316: .getTypeJavaLangString(),
317: JBinaryOperator.ADD, newLhs, newRhs);
318: ctx.replaceMe(newExpr);
319: }
320: } else if (x.getOp() == JBinaryOperator.ASG_ADD) {
321: JExpression newRhs = convertCharString(x.getRhs());
322: if (newRhs != x.getRhs()) {
323: JBinaryOperation newExpr = new JBinaryOperation(
324: program, x.getSourceInfo(), program
325: .getTypeJavaLangString(),
326: JBinaryOperator.ASG_ADD, x.getLhs(), newRhs);
327: ctx.replaceMe(newExpr);
328: }
329: }
330: }
331:
332: private JExpression convertCharString(JExpression expr) {
333: JPrimitiveType charType = program.getTypePrimitiveChar();
334: if (expr.getType() == charType) {
335: // Replace the character with a call to Cast.charToString()
336: if (stringValueOfChar == null) {
337: stringValueOfChar = program
338: .getIndexedMethod("Cast.charToString");
339: assert (stringValueOfChar != null);
340: }
341: JMethodCall call = new JMethodCall(program, expr
342: .getSourceInfo(), null, stringValueOfChar);
343: call.getArgs().add(expr);
344: return call;
345: }
346: return expr;
347: }
348: }
349:
350: /**
351: * Explicitly cast all integral divide operations to trigger replacements with
352: * narrowing calls in the next pass.
353: */
354: private class DivVisitor extends JModVisitor {
355:
356: @Override
357: public void endVisit(JBinaryOperation x, Context ctx) {
358: JType type = x.getType();
359: if (x.getOp() == JBinaryOperator.DIV
360: && type != program.getTypePrimitiveFloat()
361: && type != program.getTypePrimitiveDouble()) {
362: x.setType(program.getTypePrimitiveDouble());
363: JCastOperation cast = new JCastOperation(program, x
364: .getSourceInfo(), type, x);
365: ctx.replaceMe(cast);
366: }
367: }
368: }
369:
370: /**
371: * Replaces all casts and instanceof operations with calls to implementation
372: * methods.
373: */
374: private class ReplaceTypeChecksVisitor extends JModVisitor {
375:
376: @Override
377: public void endVisit(JCastOperation x, Context ctx) {
378: JExpression replaceExpr;
379: JType toType = x.getCastType();
380: if (toType instanceof JNullType) {
381: /*
382: * Magic: a null type cast means the user tried a cast that couldn't
383: * possibly work. Typically this means either the statically resolvable
384: * arg type is incompatible with the target type, or the target type was
385: * globally uninstantiable. We handle this cast by throwing a
386: * ClassCastException, unless the argument is null.
387: */
388: JMethod method = program
389: .getIndexedMethod("Cast.throwClassCastExceptionUnlessNull");
390: /*
391: * Override the type of the magic method with the null type.
392: */
393: JMethodCall call = new JMethodCall(program, x
394: .getSourceInfo(), null, method, program
395: .getTypeNull());
396: call.getArgs().add(x.getExpr());
397: replaceExpr = call;
398: } else if (toType instanceof JReferenceType) {
399: JExpression curExpr = x.getExpr();
400: JReferenceType refType = (JReferenceType) toType;
401: JType argType = x.getExpr().getType();
402: if (program.isJavaScriptObject(argType)) {
403: /*
404: * A JSO-derived class that is about to be cast must be "wrapped"
405: * first. Since a JSO was never constructed, it may not have an
406: * accessible prototype. Instead we copy fields from the seed
407: * function's prototype directly onto the target object as expandos.
408: * See com.google.gwt.lang.Cast.wrapJSO().
409: */
410: JMethod wrap = program
411: .getIndexedMethod("Cast.wrapJSO");
412: // override the type of the called method with the JSO's type
413: JMethodCall call = new JMethodCall(program, x
414: .getSourceInfo(), null, wrap, argType);
415: JClassSeed seed = program
416: .getLiteralClassSeed((JClassType) argType);
417: call.getArgs().add(curExpr);
418: call.getArgs().add(seed);
419: curExpr = call;
420: }
421: if (argType instanceof JClassType
422: && program.typeOracle.canTriviallyCast(
423: (JClassType) argType, refType)) {
424: // TODO(???): why is this only for JClassType?
425: // just remove the cast
426: replaceExpr = curExpr;
427: } else {
428: JMethod method = program
429: .getIndexedMethod("Cast.dynamicCast");
430: // override the type of the called method with the target cast type
431: JMethodCall call = new JMethodCall(program, x
432: .getSourceInfo(), null, method, toType);
433: Integer boxedInt = queryIds.get(refType);
434: JIntLiteral qId = program.getLiteralInt(boxedInt
435: .intValue());
436: call.getArgs().add(curExpr);
437: call.getArgs().add(qId);
438: replaceExpr = call;
439: }
440: } else {
441: /*
442: * See JLS 5.1.3: if a cast narrows from one type to another, we must
443: * call a narrowing conversion function. EXCEPTION: we currently have no
444: * way to narrow double to float, so don't bother.
445: */
446: boolean narrow = false, round = false;
447: JPrimitiveType tByte = program.getTypePrimitiveByte();
448: JPrimitiveType tChar = program.getTypePrimitiveChar();
449: JPrimitiveType tShort = program.getTypePrimitiveShort();
450: JPrimitiveType tInt = program.getTypePrimitiveInt();
451: JPrimitiveType tLong = program.getTypePrimitiveLong();
452: JPrimitiveType tFloat = program.getTypePrimitiveFloat();
453: JPrimitiveType tDouble = program
454: .getTypePrimitiveDouble();
455: JType fromType = x.getExpr().getType();
456: if (tByte == fromType) {
457: if (tChar == toType) {
458: narrow = true;
459: }
460: } else if (tShort == fromType) {
461: if (tByte == toType || tChar == toType) {
462: narrow = true;
463: }
464: } else if (tChar == fromType) {
465: if (tByte == toType || tShort == toType) {
466: narrow = true;
467: }
468: } else if (tInt == fromType) {
469: if (tByte == toType || tShort == toType
470: || tChar == toType) {
471: narrow = true;
472: }
473: } else if (tLong == fromType) {
474: if (tByte == toType || tShort == toType
475: || tChar == toType || tInt == toType) {
476: narrow = true;
477: }
478: } else if (tFloat == fromType || tDouble == fromType) {
479: if (tByte == toType || tShort == toType
480: || tChar == toType || tInt == toType
481: || tLong == toType) {
482: round = true;
483: }
484: }
485:
486: if (narrow || round) {
487: // Replace the expression with a call to the narrow or round method
488: String methodName = "Cast."
489: + (narrow ? "narrow_" : "round_")
490: + toType.getName();
491: JMethod castMethod = program
492: .getIndexedMethod(methodName);
493: JMethodCall call = new JMethodCall(program, x
494: .getSourceInfo(), null, castMethod);
495: call.getArgs().add(x.getExpr());
496: replaceExpr = call;
497: } else {
498: // Just remove the cast
499: replaceExpr = x.getExpr();
500: }
501: }
502: ctx.replaceMe(replaceExpr);
503: }
504:
505: @Override
506: public void endVisit(JInstanceOf x, Context ctx) {
507: JType argType = x.getExpr().getType();
508: if (argType instanceof JClassType
509: && program.typeOracle.canTriviallyCast(
510: (JClassType) argType, x.getTestType())) {
511: // trivially true if non-null; replace with a null test
512: JNullLiteral nullLit = program.getLiteralNull();
513: JBinaryOperation eq = new JBinaryOperation(program, x
514: .getSourceInfo(), program
515: .getTypePrimitiveBoolean(),
516: JBinaryOperator.NEQ, x.getExpr(), nullLit);
517: ctx.replaceMe(eq);
518: } else {
519: JMethod method = program
520: .getIndexedMethod("Cast.instanceOf");
521: JMethodCall call = new JMethodCall(program, x
522: .getSourceInfo(), null, method);
523: Integer boxedInt = queryIds.get(x.getTestType());
524: JIntLiteral qId = program.getLiteralInt(boxedInt
525: .intValue());
526: call.getArgs().add(x.getExpr());
527: call.getArgs().add(qId);
528: ctx.replaceMe(call);
529: }
530: }
531: }
532:
533: public static void exec(JProgram program) {
534: new CastNormalizer(program).execImpl();
535: }
536:
537: private Map<JReferenceType, Integer> queryIds = new IdentityHashMap<JReferenceType, Integer>();
538:
539: private final JProgram program;
540:
541: private CastNormalizer(JProgram program) {
542: this .program = program;
543: }
544:
545: private void execImpl() {
546: {
547: ConcatVisitor visitor = new ConcatVisitor();
548: visitor.accept(program);
549: }
550: {
551: DivVisitor visitor = new DivVisitor();
552: visitor.accept(program);
553: }
554: {
555: AssignTypeIdsVisitor assigner = new AssignTypeIdsVisitor();
556: assigner.accept(program);
557: assigner.computeTypeIds();
558: }
559: {
560: ReplaceTypeChecksVisitor replacer = new ReplaceTypeChecksVisitor();
561: replacer.accept(program);
562: }
563: }
564:
565: }
|