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.JBinaryOperation;
021: import com.google.gwt.dev.jjs.ast.JCastOperation;
022: import com.google.gwt.dev.jjs.ast.JConditional;
023: import com.google.gwt.dev.jjs.ast.JExpression;
024: import com.google.gwt.dev.jjs.ast.JLocalDeclarationStatement;
025: import com.google.gwt.dev.jjs.ast.JMethod;
026: import com.google.gwt.dev.jjs.ast.JMethodCall;
027: import com.google.gwt.dev.jjs.ast.JModVisitor;
028: import com.google.gwt.dev.jjs.ast.JNewArray;
029: import com.google.gwt.dev.jjs.ast.JParameter;
030: import com.google.gwt.dev.jjs.ast.JProgram;
031: import com.google.gwt.dev.jjs.ast.JReferenceType;
032: import com.google.gwt.dev.jjs.ast.JReturnStatement;
033: import com.google.gwt.dev.jjs.ast.JType;
034:
035: import java.util.ArrayList;
036: import java.util.List;
037:
038: /**
039: * Synthesize casts for JavaScriptObject types in cases where dynamic type
040: * information may be necessary.
041: */
042: public class JavaScriptObjectCaster {
043:
044: /**
045: * Synthesize casts from JavaScriptObjects to trigger wrapping.
046: */
047: private class AssignmentVisitor extends JModVisitor {
048:
049: private JMethod currentMethod;
050:
051: @Override
052: public void endVisit(JBinaryOperation x, Context ctx) {
053: if (x.isAssignment()) {
054: JType lhsType = x.getLhs().getType();
055: JExpression newRhs = checkAndReplaceJso(x.getRhs(),
056: lhsType);
057: if (newRhs == x.getRhs()) {
058: // There's another case to check: if we have an array store that may
059: // trigger a type check, we need to wrap the rhs.
060: if (x.getLhs() instanceof JArrayRef) {
061: newRhs = checkAndReplaceJsoArrayStore(newRhs,
062: lhsType);
063: }
064: }
065: if (newRhs != x.getRhs()) {
066: JBinaryOperation asg = new JBinaryOperation(
067: program, x.getSourceInfo(), lhsType, x
068: .getOp(), x.getLhs(), newRhs);
069: ctx.replaceMe(asg);
070: }
071: }
072: }
073:
074: @Override
075: public void endVisit(JConditional x, Context ctx) {
076: JExpression newThen = checkAndReplaceJso(x.getThenExpr(), x
077: .getType());
078: JExpression newElse = checkAndReplaceJso(x.getElseExpr(), x
079: .getType());
080: if (newThen != x.getThenExpr()
081: || newElse != x.getElseExpr()) {
082: JConditional newCond = new JConditional(program, x
083: .getSourceInfo(), x.getType(), x.getIfTest(),
084: newThen, newElse);
085: ctx.replaceMe(newCond);
086: }
087: }
088:
089: @Override
090: public void endVisit(JLocalDeclarationStatement x, Context ctx) {
091: JExpression newInst = x.getInitializer();
092: if (newInst != null) {
093: newInst = checkAndReplaceJso(newInst, x.getLocalRef()
094: .getType());
095: if (newInst != x.getInitializer()) {
096: JLocalDeclarationStatement newStmt = new JLocalDeclarationStatement(
097: program, x.getSourceInfo(),
098: x.getLocalRef(), newInst);
099: ctx.replaceMe(newStmt);
100: }
101: }
102: }
103:
104: @Override
105: public void endVisit(JMethod x, Context ctx) {
106: currentMethod = null;
107: }
108:
109: @Override
110: public void endVisit(JMethodCall x, Context ctx) {
111: // Check implicit assignments from argument and instance passing.
112:
113: ArrayList<JExpression> args = x.getArgs();
114: JMethod target = x.getTarget();
115:
116: for (int i = 0; i < target.params.size(); ++i) {
117: JParameter param = target.params.get(i);
118: JExpression arg = args.get(i);
119: JExpression newArg = checkAndReplaceJso(arg, param
120: .getType(), target.isNative());
121: this .didChange |= (newArg != arg);
122: args.set(i, newArg);
123: }
124:
125: /*
126: * Virtual calls *require* wrapping to dispatch correctly. This should be
127: * a rare case since most call should get statically resolved already.
128: */
129: if (!target.isStatic()) {
130: JExpression newInst = checkAndReplaceJso(x
131: .getInstance(), program.getTypeJavaLangObject());
132: if (newInst != x.getInstance()) {
133: JMethodCall newCall = new JMethodCall(program, x
134: .getSourceInfo(), newInst, target, x
135: .isStaticDispatchOnly());
136: newCall.getArgs().addAll(args);
137: ctx.replaceMe(newCall);
138: }
139: }
140: }
141:
142: @Override
143: public void endVisit(JNewArray x, Context ctx) {
144: List<JExpression> initializers = x.initializers;
145: if (initializers != null) {
146: for (int i = 0; i < initializers.size(); ++i) {
147: JExpression intializer = initializers.get(i);
148: JExpression newInitializer = checkAndReplaceJsoArrayStore(
149: intializer, x.getArrayType().getLeafType());
150: if (intializer != newInitializer) {
151: initializers.set(i, newInitializer);
152: this .didChange = true;
153: }
154: }
155: }
156: }
157:
158: @Override
159: public void endVisit(JReturnStatement x, Context ctx) {
160: if (x.getExpr() != null) {
161: JExpression newExpr = checkAndReplaceJso(x.getExpr(),
162: currentMethod.getType());
163: if (newExpr != x.getExpr()) {
164: JReturnStatement newStmt = new JReturnStatement(
165: program, x.getSourceInfo(), newExpr);
166: ctx.replaceMe(newStmt);
167: }
168: }
169: }
170:
171: @Override
172: public boolean visit(JMethod x, Context ctx) {
173: currentMethod = x;
174: return true;
175: }
176:
177: private JExpression checkAndReplaceJso(JExpression arg,
178: JType targetType) {
179: return checkAndReplaceJso(arg, targetType, false);
180: }
181:
182: /**
183: * Wraps a JSO-typed argument if the target type is a different type.
184: */
185: private JExpression checkAndReplaceJso(JExpression arg,
186: JType targetType, boolean nowrapJso) {
187: JType argType = arg.getType();
188: if (argType == targetType) {
189: return arg;
190: }
191: if (!(targetType instanceof JReferenceType)) {
192: return arg;
193: }
194: if (!program.isJavaScriptObject(argType)) {
195: return arg;
196: }
197: /*
198: * Special case: when calling a native method, only force a wrapping if
199: * the type is explicitly Object. As long as the target type is a JSO
200: * subclass, don't bother wrapping since we're losing type information
201: * anyway.
202: */
203: if (nowrapJso && program.isJavaScriptObject(targetType)) {
204: return arg;
205: }
206: // Synthesize a cast to the arg type to force a wrap
207: JCastOperation cast = new JCastOperation(program, arg
208: .getSourceInfo(), argType, arg);
209: return cast;
210: }
211:
212: /**
213: * Wraps a JSO-typed argument.
214: *
215: * TODO: We could eliminate casts cases where the array instance was never
216: * cast to a weaker type.
217: */
218: private JExpression checkAndReplaceJsoArrayStore(
219: JExpression arg, JType targetType) {
220: if (!(targetType instanceof JReferenceType)) {
221: return arg;
222: }
223: if (!program.isJavaScriptObject(arg.getType())) {
224: return arg;
225: }
226: // Synthesize a cast to the target type
227: JCastOperation cast = new JCastOperation(program, arg
228: .getSourceInfo(), targetType, arg);
229: return cast;
230: }
231: }
232:
233: public static void exec(JProgram program) {
234: new JavaScriptObjectCaster(program).execImpl();
235: }
236:
237: private final JProgram program;
238:
239: private JavaScriptObjectCaster(JProgram program) {
240: this .program = program;
241: }
242:
243: private void execImpl() {
244: AssignmentVisitor visitor = new AssignmentVisitor();
245: visitor.accept(program);
246: }
247:
248: }
|