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.CanBeStatic;
019: import com.google.gwt.dev.jjs.ast.Context;
020: import com.google.gwt.dev.jjs.ast.JAbsentArrayDimension;
021: import com.google.gwt.dev.jjs.ast.JArrayType;
022: import com.google.gwt.dev.jjs.ast.JBinaryOperation;
023: import com.google.gwt.dev.jjs.ast.JBinaryOperator;
024: import com.google.gwt.dev.jjs.ast.JBlock;
025: import com.google.gwt.dev.jjs.ast.JClassType;
026: import com.google.gwt.dev.jjs.ast.JExpression;
027: import com.google.gwt.dev.jjs.ast.JField;
028: import com.google.gwt.dev.jjs.ast.JFieldRef;
029: import com.google.gwt.dev.jjs.ast.JInterfaceType;
030: import com.google.gwt.dev.jjs.ast.JLocal;
031: import com.google.gwt.dev.jjs.ast.JLocalDeclarationStatement;
032: import com.google.gwt.dev.jjs.ast.JLocalRef;
033: import com.google.gwt.dev.jjs.ast.JMethod;
034: import com.google.gwt.dev.jjs.ast.JMethodBody;
035: import com.google.gwt.dev.jjs.ast.JMethodCall;
036: import com.google.gwt.dev.jjs.ast.JModVisitor;
037: import com.google.gwt.dev.jjs.ast.JNewArray;
038: import com.google.gwt.dev.jjs.ast.JNewInstance;
039: import com.google.gwt.dev.jjs.ast.JNode;
040: import com.google.gwt.dev.jjs.ast.JParameter;
041: import com.google.gwt.dev.jjs.ast.JParameterRef;
042: import com.google.gwt.dev.jjs.ast.JPrimitiveType;
043: import com.google.gwt.dev.jjs.ast.JProgram;
044: import com.google.gwt.dev.jjs.ast.JReferenceType;
045: import com.google.gwt.dev.jjs.ast.JStringLiteral;
046: import com.google.gwt.dev.jjs.ast.JThisRef;
047: import com.google.gwt.dev.jjs.ast.JType;
048: import com.google.gwt.dev.jjs.ast.JVariable;
049: import com.google.gwt.dev.jjs.ast.JVariableRef;
050: import com.google.gwt.dev.jjs.ast.JVisitor;
051: import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
052: import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
053: import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
054: import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
055: import com.google.gwt.dev.js.ast.JsContext;
056: import com.google.gwt.dev.js.ast.JsExpression;
057: import com.google.gwt.dev.js.ast.JsFunction;
058: import com.google.gwt.dev.js.ast.JsName;
059: import com.google.gwt.dev.js.ast.JsNameRef;
060: import com.google.gwt.dev.js.ast.JsVisitor;
061:
062: import java.util.ArrayList;
063: import java.util.HashMap;
064: import java.util.HashSet;
065: import java.util.Iterator;
066: import java.util.List;
067: import java.util.Map;
068: import java.util.Set;
069:
070: /**
071: * Remove globally unreferenced classes, interfaces, methods, parameters, and
072: * fields from the AST. This algorithm is based on having known "entry points"
073: * into the application which serve as the root(s) from which reachability is
074: * determined and everything else is rescued. Pruner determines reachability at
075: * a global level based on method calls and new operations; it does not perform
076: * any local code flow analysis. But, a local code flow optimization pass that
077: * can eliminate method calls would allow Pruner to prune additional nodes.
078: *
079: * Note: references to pruned types may still exist in the tree after this pass
080: * runs, however, it should only be in contexts that do not rely on any code
081: * generation for the pruned type. For example, it's legal to have a variable of
082: * a pruned type, or to try to cast to a pruned type. These will cause natural
083: * failures at run time; or later optimizations might be able to hard-code
084: * failures at compile time.
085: *
086: * Note: this class is limited to pruning parameters of static methods only.
087: */
088: public class Pruner {
089:
090: /**
091: * Remove assignments to pruned fields, locals and params. Also nullify the
092: * return type of methods declared to return a globally uninstantiable type.
093: */
094: private class CleanupRefsVisitor extends JModVisitor {
095:
096: @Override
097: public void endVisit(JBinaryOperation x, Context ctx) {
098: // The LHS of assignments may have been pruned.
099: if (x.getOp() == JBinaryOperator.ASG) {
100: JExpression lhs = x.getLhs();
101: if (lhs.hasSideEffects()) {
102: return;
103: }
104: if (lhs instanceof JFieldRef
105: || lhs instanceof JLocalRef
106: || lhs instanceof JParameterRef) {
107: JVariable var = ((JVariableRef) lhs).getTarget();
108: if (!referencedNonTypes.contains(var)) {
109: // Just replace with my RHS
110: ctx.replaceMe(x.getRhs());
111: }
112: }
113: }
114: }
115:
116: @Override
117: public void endVisit(JMethod x, Context ctx) {
118: JType type = x.getType();
119: if (type instanceof JReferenceType) {
120: if (!program.typeOracle
121: .isInstantiatedType((JReferenceType) type)) {
122: x.setType(program.getTypeNull());
123: }
124: }
125: }
126:
127: public void endVisit(JLocalDeclarationStatement x, Context ctx) {
128: // The variable may have been pruned.
129: if (!referencedNonTypes.contains(x.getLocalRef()
130: .getTarget())) {
131: // If there is an initializer, just replace with that.
132: if (x.getInitializer() != null) {
133: ctx.replaceMe(x.getInitializer().makeStatement());
134: } else {
135: // No initializer, prune this entirely.
136: if (ctx.canRemove()) {
137: // Just remove it if we can.
138: ctx.removeMe();
139: } else {
140: // Replace with empty block.
141: ctx.replaceMe(new JBlock(program, x
142: .getSourceInfo()));
143: }
144: }
145: }
146: }
147:
148: @Override
149: public void endVisit(JMethodCall x, Context ctx) {
150: JMethod method = x.getTarget();
151:
152: // Did we prune the parameters of the method we're calling?
153: if (methodToOriginalParamsMap.containsKey(method)) {
154: // This must be a static method
155: assert method.isStatic();
156:
157: JMethodCall newCall = new JMethodCall(program, x
158: .getSourceInfo(), x.getInstance(), method);
159:
160: ArrayList<JExpression> args = x.getArgs();
161: ArrayList<JParameter> originalParams = methodToOriginalParamsMap
162: .get(method);
163:
164: JMultiExpression currentMulti = null;
165: for (int i = 0, c = args.size(); i < c; ++i) {
166: JExpression arg = args.get(i);
167: JParameter param = null;
168: if (i < originalParams.size()) {
169: param = originalParams.get(i);
170: }
171:
172: if (param != null
173: && referencedNonTypes.contains(param)) {
174: // If there is an existing multi, terminate it.
175: if (currentMulti != null) {
176: currentMulti.exprs.add(arg);
177: newCall.getArgs().add(currentMulti);
178: currentMulti = null;
179: } else {
180: newCall.getArgs().add(arg);
181: }
182: } else if (arg.hasSideEffects()) {
183: // The argument is only needed for side effects, add it to a multi.
184: if (currentMulti == null) {
185: currentMulti = new JMultiExpression(
186: program, x.getSourceInfo());
187: }
188: currentMulti.exprs.add(arg);
189: }
190: }
191:
192: // Add any orphaned parameters on the end. Extra params are OK.
193: if (currentMulti != null) {
194: newCall.getArgs().add(currentMulti);
195: }
196:
197: ctx.replaceMe(newCall);
198: }
199: }
200: }
201:
202: /**
203: * Remove any unreferenced classes and interfaces from JProgram. Remove any
204: * unreferenced methods and fields from their containing classes.
205: */
206: private class PruneVisitor extends JVisitor {
207:
208: private boolean didChange = false;
209:
210: @Override
211: public boolean didChange() {
212: return didChange;
213: }
214:
215: @Override
216: public boolean visit(JClassType type, Context ctx) {
217:
218: assert (referencedTypes.contains(type));
219: boolean isInstantiated = program.typeOracle
220: .isInstantiatedType(type);
221:
222: for (Iterator<JField> it = type.fields.iterator(); it
223: .hasNext();) {
224: JField field = it.next();
225: if (!referencedNonTypes.contains(field)
226: || pruneViaNoninstantiability(isInstantiated,
227: field)) {
228: it.remove();
229: didChange = true;
230: }
231: }
232:
233: for (Iterator<JMethod> it = type.methods.iterator(); it
234: .hasNext();) {
235: JMethod method = it.next();
236: if (!methodIsReferenced(method)
237: || pruneViaNoninstantiability(isInstantiated,
238: method)) {
239: it.remove();
240: didChange = true;
241: } else {
242: accept(method);
243: }
244: }
245:
246: return false;
247: }
248:
249: @Override
250: public boolean visit(JInterfaceType type, Context ctx) {
251: boolean isReferenced = referencedTypes.contains(type);
252: boolean isInstantiated = program.typeOracle
253: .isInstantiatedType(type);
254:
255: for (Iterator<JField> it = type.fields.iterator(); it
256: .hasNext();) {
257: JField field = it.next();
258: // all interface fields are static and final
259: if (!isReferenced
260: || !referencedNonTypes.contains(field)) {
261: it.remove();
262: didChange = true;
263: }
264: }
265:
266: Iterator<JMethod> it = type.methods.iterator();
267: if (it.hasNext()) {
268: // start at index 1; never prune clinit directly out of the interface
269: it.next();
270: }
271: while (it.hasNext()) {
272: JMethod method = it.next();
273: // all other interface methods are instance and abstract
274: if (!isInstantiated || !methodIsReferenced(method)) {
275: it.remove();
276: didChange = true;
277: }
278: }
279:
280: return false;
281: }
282:
283: @Override
284: public boolean visit(JMethod x, Context ctx) {
285: if (x.isStatic()) {
286: /*
287: * Don't prune parameters on unreferenced methods. The methods might not
288: * be reachable through the current method traversal routines, but might
289: * be used or checked elsewhere.
290: *
291: * Basically, if we never actually checked if the method parameters were
292: * used or not, don't prune them. Doing so would leave a number of
293: * dangling JParameterRefs that blow up in later optimizations.
294: */
295: if (!referencedNonTypes.contains(x)) {
296: return true;
297: }
298:
299: /*
300: * We cannot prune parameters from staticImpls that still have a live
301: * instance method, because doing so would screw up any subsequent
302: * devirtualizations. If the instance method has been pruned, then it's
303: * okay. Also, it's okay on the final pass since no more
304: * devirtualizations will occur.
305: */
306: JMethod staticImplFor = program.staticImplFor(x);
307: // Unless the instance method has already been pruned, of course.
308: if (saveCodeGenTypes
309: && staticImplFor != null
310: && staticImplFor.getEnclosingType().methods
311: .contains(staticImplFor)) {
312: // instance method is still live
313: return true;
314: }
315:
316: JsFunction func = x.isNative() ? ((JsniMethodBody) x
317: .getBody()).getFunc() : null;
318:
319: ArrayList<JParameter> originalParams = new ArrayList<JParameter>(
320: x.params);
321:
322: for (int i = 0; i < x.params.size(); ++i) {
323: JParameter param = x.params.get(i);
324: if (!referencedNonTypes.contains(param)) {
325: x.params.remove(i);
326: didChange = true;
327: // Remove the associated JSNI parameter
328: if (func != null) {
329: func.getParameters().remove(i);
330: }
331: --i;
332: methodToOriginalParamsMap
333: .put(x, originalParams);
334: }
335: }
336: }
337:
338: return true;
339: }
340:
341: @Override
342: public boolean visit(JMethodBody x, Context ctx) {
343: for (Iterator<JLocal> it = x.locals.iterator(); it
344: .hasNext();) {
345: JLocal local = it.next();
346: if (!referencedNonTypes.contains(local)) {
347: it.remove();
348: didChange = true;
349: }
350: }
351: return false;
352: }
353:
354: @Override
355: public boolean visit(JProgram program, Context ctx) {
356: for (Iterator<JReferenceType> it = program
357: .getDeclaredTypes().iterator(); it.hasNext();) {
358: JReferenceType type = it.next();
359: if (referencedTypes.contains(type)
360: || program.typeOracle.isInstantiatedType(type)) {
361: accept(type);
362: } else {
363: it.remove();
364: didChange = true;
365: }
366: }
367: return false;
368: }
369:
370: /**
371: * Returns <code>true</code> if a method is referenced.
372: */
373: private boolean methodIsReferenced(JMethod method) {
374: // Is the method directly referenced?
375: if (referencedNonTypes.contains(method)) {
376: return true;
377: }
378:
379: /*
380: * Special case: if method is the static impl for a live instance method,
381: * don't prune it unless this is the final prune.
382: *
383: * In some cases, the staticImpl can be inlined into the instance method
384: * but still be needed at other call sites.
385: */
386: JMethod staticImplFor = program.staticImplFor(method);
387: if (staticImplFor != null
388: && referencedNonTypes.contains(staticImplFor)) {
389: if (saveCodeGenTypes) {
390: return true;
391: }
392: }
393: return false;
394: }
395:
396: private boolean pruneViaNoninstantiability(
397: boolean isInstantiated, CanBeStatic it) {
398: return (!isInstantiated && !it.isStatic());
399: }
400: }
401:
402: /**
403: * Marks as "referenced" any types, methods, and fields that are reachable.
404: * Also marks as "instantiable" any the classes and interfaces that can
405: * possibly be instantiated.
406: *
407: * TODO(later): make RescueVisitor use less stack?
408: */
409: private class RescueVisitor extends JVisitor {
410:
411: private final Set<JReferenceType> instantiatedTypes = new HashSet<JReferenceType>();
412:
413: public void commitInstantiatedTypes() {
414: program.typeOracle.setInstantiatedTypes(instantiatedTypes);
415: }
416:
417: @Override
418: public boolean visit(JArrayType type, Context ctx) {
419: assert (referencedTypes.contains(type));
420: boolean isInstantiated = instantiatedTypes.contains(type);
421:
422: JType leafType = type.getLeafType();
423: int dims = type.getDims();
424:
425: // Rescue my super array type
426: if (leafType instanceof JReferenceType) {
427: JReferenceType rLeafType = (JReferenceType) leafType;
428: if (rLeafType.extnds != null) {
429: JArrayType super Array = program.getTypeArray(
430: rLeafType.extnds, dims);
431: rescue(super Array, true, isInstantiated);
432: }
433:
434: for (int i = 0; i < rLeafType.implments.size(); ++i) {
435: JInterfaceType intfType = rLeafType.implments
436: .get(i);
437: JArrayType intfArray = program.getTypeArray(
438: intfType, dims);
439: rescue(intfArray, true, isInstantiated);
440: }
441: }
442:
443: return false;
444: }
445:
446: @Override
447: public boolean visit(JBinaryOperation x, Context ctx) {
448: // special string concat handling
449: if (x.getOp() == JBinaryOperator.ADD
450: && x.getType() == program.getTypeJavaLangString()) {
451: rescueByConcat(x.getLhs().getType());
452: rescueByConcat(x.getRhs().getType());
453: } else if (x.getOp() == JBinaryOperator.ASG) {
454: // Don't rescue variables that are merely assigned to and never read
455: boolean doSkip = false;
456: JExpression lhs = x.getLhs();
457: if (lhs.hasSideEffects()) {
458: // cannot skip
459: } else if (lhs instanceof JLocalRef) {
460: // locals are ok to skip
461: doSkip = true;
462: } else if (lhs instanceof JParameterRef) {
463: // parameters are ok to skip
464: doSkip = true;
465: } else if (lhs instanceof JFieldRef) {
466: JFieldRef fieldRef = (JFieldRef) lhs;
467: /*
468: * Whether we can skip depends on what the qualifier is; we have to be
469: * certain the qualifier cannot be a null pointer (in which case we'd
470: * fail to throw the appropriate exception.
471: *
472: * TODO: better non-null tracking!
473: */
474: JExpression instance = fieldRef.getInstance();
475: if (fieldRef.getField().isStatic()) {
476: // statics are always okay
477: doSkip = true;
478: } else if (instance instanceof JThisRef) {
479: // a this ref cannot be null
480: doSkip = true;
481: }
482: }
483:
484: if (doSkip) {
485: accept(x.getRhs());
486: return false;
487: }
488: }
489: return true;
490: }
491:
492: @Override
493: public boolean visit(JClassType type, Context ctx) {
494: assert (referencedTypes.contains(type));
495: boolean isInstantiated = instantiatedTypes.contains(type);
496:
497: /*
498: * SPECIAL: Some classes contain methods used by code generation later.
499: * Unless those transforms have already been performed, we must rescue all
500: * contained methods for later user.
501: */
502: if (saveCodeGenTypes && program.codeGenTypes.contains(type)) {
503: for (int i = 0; i < type.methods.size(); ++i) {
504: JMethod it = type.methods.get(i);
505: rescue(it);
506: }
507: }
508:
509: // Rescue my super type
510: rescue(type.extnds, true, isInstantiated);
511:
512: // Rescue my clinit (it won't ever be explicitly referenced
513: rescue(type.methods.get(0));
514:
515: // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued.
516: // However, if I'm instantiated, let's mark them as instantiated.
517: for (int i = 0; i < type.implments.size(); ++i) {
518: JInterfaceType intfType = type.implments.get(i);
519: rescue(intfType, false, isInstantiated);
520: }
521:
522: return false;
523: }
524:
525: @Override
526: public boolean visit(JFieldRef ref, Context ctx) {
527: JField target = ref.getField();
528:
529: // JLS 12.4.1: references to static, non-final, or
530: // non-compile-time-constant fields rescue the enclosing class.
531: // JDT already folds in compile-time constants as literals, so we must
532: // rescue the enclosing types for any static fields that make it here.
533: if (target.isStatic()) {
534: rescue(target.getEnclosingType(), true, false);
535: }
536: rescue(target);
537: return true;
538: }
539:
540: @Override
541: public boolean visit(JInterfaceType type, Context ctx) {
542: boolean isReferenced = referencedTypes.contains(type);
543: boolean isInstantiated = instantiatedTypes.contains(type);
544: assert (isReferenced || isInstantiated);
545:
546: // Rescue my clinit (it won't ever be explicitly referenced
547: rescue(type.methods.get(0));
548:
549: // JLS 12.4.1: don't rescue my super interfaces just because I'm rescued.
550: // However, if I'm instantiated, let's mark them as instantiated.
551: if (isInstantiated) {
552: for (int i = 0; i < type.implments.size(); ++i) {
553: JInterfaceType intfType = type.implments.get(i);
554: rescue(intfType, false, true);
555: }
556: }
557:
558: // visit any field initializers
559: for (int i = 0; i < type.fields.size(); ++i) {
560: JField it = type.fields.get(i);
561: accept(it);
562: }
563:
564: return false;
565: }
566:
567: public boolean visit(JLocalDeclarationStatement x, Context ctx) {
568: /*
569: * A declaration by itself doesn't rescue a local (even if it has an
570: * initializer). Writes don't count, only reads.
571: */
572: if (x.getInitializer() != null) {
573: accept(x.getInitializer());
574: }
575: return false;
576: }
577:
578: @Override
579: public boolean visit(JLocalRef ref, Context ctx) {
580: JLocal target = ref.getLocal();
581: rescue(target);
582: return true;
583: }
584:
585: @Override
586: public boolean visit(final JMethod x, Context ctx) {
587: if (x.isNative()) {
588: // Manually rescue native parameter references
589: final JsniMethodBody body = (JsniMethodBody) x
590: .getBody();
591: final JsFunction func = body.getFunc();
592:
593: new JsVisitor() {
594: @Override
595: public void endVisit(JsNameRef nameRef,
596: JsContext<JsExpression> ctx) {
597: JsName ident = nameRef.getName();
598:
599: if (ident != null) {
600: // If we're referencing a parameter, rescue the associated
601: // JParameter
602: int index = func.getParameters().indexOf(
603: ident.getStaticRef());
604: if (index != -1) {
605: rescue(x.params.get(index));
606: }
607: }
608: }
609: }.accept(func);
610: }
611:
612: return true;
613: }
614:
615: @Override
616: public boolean visit(JMethodCall call, Context ctx) {
617: JMethod target = call.getTarget();
618: // JLS 12.4.1: references to static methods rescue the enclosing class
619: if (target.isStatic()) {
620: rescue(target.getEnclosingType(), true, false);
621: }
622: rescue(target);
623: return true;
624: }
625:
626: @Override
627: public boolean visit(JNewArray newArray, Context ctx) {
628: // rescue and instantiate the array type
629: JArrayType arrayType = newArray.getArrayType();
630: if (newArray.dims != null) {
631: // rescue my type and all the implicitly nested types (with fewer dims)
632: int nDims = arrayType.getDims();
633: JType leafType = arrayType.getLeafType();
634: assert (newArray.dims.size() == nDims);
635: for (int i = 0; i < nDims; ++i) {
636: if (newArray.dims.get(i) instanceof JAbsentArrayDimension) {
637: break;
638: }
639: rescue(program.getTypeArray(leafType, nDims - i),
640: true, true);
641: }
642: } else {
643: // just rescue my own specific type
644: rescue(arrayType, true, true);
645: }
646: return true;
647: }
648:
649: @Override
650: public boolean visit(JNewInstance newInstance, Context ctx) {
651: // rescue and instantiate the target class!
652: rescue(newInstance.getClassType(), true, true);
653: return true;
654: }
655:
656: @Override
657: public boolean visit(JParameterRef x, Context ctx) {
658: // rescue the parameter for future pruning purposes
659: rescue(x.getParameter());
660: return true;
661: }
662:
663: @Override
664: public boolean visit(JsniFieldRef x, Context ctx) {
665: /*
666: * SPECIAL: this could be an assignment that passes a value from
667: * JavaScript into Java.
668: *
669: * TODO(later): technically we only need to do this if the field is being
670: * assigned to.
671: */
672: maybeRescueJavaScriptObjectPassingIntoJava(x.getField()
673: .getType());
674: // JsniFieldRef rescues as JFieldRef
675: return visit((JFieldRef) x, ctx);
676: }
677:
678: @Override
679: public boolean visit(JsniMethodRef x, Context ctx) {
680: /*
681: * SPECIAL: each argument of the call passes a value from JavaScript into
682: * Java.
683: */
684: ArrayList<JParameter> params = x.getTarget().params;
685: for (int i = 0, c = params.size(); i < c; ++i) {
686: JParameter param = params.get(i);
687: maybeRescueJavaScriptObjectPassingIntoJava(param
688: .getType());
689:
690: /*
691: * Because we're not currently tracking methods through JSNI, we need to
692: * assume that it's not safe to prune parameters of a method referenced
693: * as such.
694: *
695: * A better solution would be to perform basic escape analysis to ensure
696: * that the function reference never escapes, or at minimum, ensure that
697: * the method is immediately called after retrieving the method
698: * reference.
699: */
700: rescue(param);
701: }
702: // JsniMethodRef rescues as JMethodCall
703: return visit((JMethodCall) x, ctx);
704: }
705:
706: @Override
707: public boolean visit(JStringLiteral literal, Context ctx) {
708: // rescue and instantiate java.lang.String
709: rescue(program.getTypeJavaLangString(), true, true);
710: return true;
711: }
712:
713: /**
714: * Subclasses of JavaScriptObject are never instantiated directly. They are
715: * created "magically" when a JSNI method passes a reference to an existing
716: * JS object into Java code. The point at which a subclass of JSO is passed
717: * into Java code constitutes "instantiation". We must identify these points
718: * and trigger a rescue and instantiation of that particular JSO subclass.
719: *
720: * @param type The type of the value passing from Java to JavaScript.
721: * @see com.google.gwt.core.client.JavaScriptObject
722: */
723: private void maybeRescueJavaScriptObjectPassingIntoJava(
724: JType type) {
725: if (type instanceof JReferenceType) {
726: JReferenceType refType = (JReferenceType) type;
727: if (program.typeOracle.canTriviallyCast(refType,
728: program.getJavaScriptObject())) {
729: rescue(refType, true, true);
730: }
731: }
732: }
733:
734: private boolean rescue(JMethod method) {
735: if (method != null) {
736: if (!referencedNonTypes.contains(method)) {
737: referencedNonTypes.add(method);
738: accept(method);
739: if (method.isNative()) {
740: /*
741: * SPECIAL: returning from this method passes a value from
742: * JavaScript into Java.
743: */
744: maybeRescueJavaScriptObjectPassingIntoJava(method
745: .getType());
746: }
747: return true;
748: }
749: }
750: return false;
751: }
752:
753: private void rescue(JReferenceType type, boolean isReferenced,
754: boolean isInstantiated) {
755: if (type != null) {
756:
757: boolean doVisit = false;
758: if (isInstantiated && !instantiatedTypes.contains(type)) {
759: instantiatedTypes.add(type);
760: doVisit = true;
761: }
762:
763: if (isReferenced && !referencedTypes.contains(type)) {
764: referencedTypes.add(type);
765: doVisit = true;
766: }
767:
768: if (doVisit) {
769: accept(type);
770: }
771: }
772: }
773:
774: private void rescue(JVariable var) {
775: if (var != null) {
776: if (!referencedNonTypes.contains(var)) {
777: referencedNonTypes.add(var);
778: }
779: }
780: }
781:
782: /**
783: * Handle special rescues needed implicitly to support concat.
784: */
785: private void rescueByConcat(JType type) {
786: JClassType stringType = program.getTypeJavaLangString();
787: JPrimitiveType charType = program.getTypePrimitiveChar();
788: if (type instanceof JReferenceType && type != stringType) {
789: /*
790: * Any reference types (except String, which works by default) that take
791: * part in a concat must rescue java.lang.Object.toString().
792: *
793: * TODO: can we narrow the focus by walking up the type heirarchy or
794: * doing explicit toString calls?
795: */
796: JMethod toStringMethod = program
797: .getIndexedMethod("Object.toString");
798: rescue(toStringMethod);
799: } else if (type == charType) {
800: /*
801: * Characters must rescue String.valueOf(char)
802: */
803: if (stringValueOfChar == null) {
804: for (int i = 0; i < stringType.methods.size(); ++i) {
805: JMethod meth = stringType.methods.get(i);
806: if (meth.getName().equals("valueOf")) {
807: List<JType> params = meth
808: .getOriginalParamTypes();
809: if (params.size() == 1) {
810: if (params.get(0) == charType) {
811: stringValueOfChar = meth;
812: break;
813: }
814: }
815: }
816: }
817: assert (stringValueOfChar != null);
818: }
819: rescue(stringValueOfChar);
820: }
821: }
822: }
823:
824: /**
825: * A method that isn't called directly can still be needed, if it overrides or
826: * implements any methods that are called.
827: */
828: private class UpRefVisitor extends JVisitor {
829:
830: private boolean didRescue = false;
831: private final RescueVisitor rescuer;
832:
833: public UpRefVisitor(RescueVisitor rescuer) {
834: this .rescuer = rescuer;
835: }
836:
837: public boolean didRescue() {
838: return didRescue;
839: }
840:
841: @Override
842: public boolean visit(JMethod x, Context ctx) {
843: if (referencedNonTypes.contains(x)) {
844: return false;
845: }
846:
847: for (JMethod override : program.typeOracle
848: .getAllOverrides(x)) {
849: if (referencedNonTypes.contains(override)) {
850: rescuer.rescue(x);
851: didRescue = true;
852: return false;
853: }
854: }
855: return false;
856: }
857:
858: @Override
859: public boolean visit(JProgram x, Context ctx) {
860: didRescue = false;
861: return true;
862: }
863: }
864:
865: public static boolean exec(JProgram program, boolean noSpecialTypes) {
866: return new Pruner(program, noSpecialTypes).execImpl();
867: }
868:
869: private final JProgram program;
870: private final Set<JNode> referencedNonTypes = new HashSet<JNode>();
871: private final Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>();
872: private final Map<JMethod, ArrayList<JParameter>> methodToOriginalParamsMap = new HashMap<JMethod, ArrayList<JParameter>>();
873: private final boolean saveCodeGenTypes;
874: private JMethod stringValueOfChar = null;
875:
876: private Pruner(JProgram program, boolean saveCodeGenTypes) {
877: this .program = program;
878: this .saveCodeGenTypes = saveCodeGenTypes;
879: }
880:
881: private boolean execImpl() {
882: boolean madeChanges = false;
883: while (true) {
884: RescueVisitor rescuer = new RescueVisitor();
885: // Always rescue and instantiate the "base" array type
886: rescuer.rescue(program.getIndexedType("Array"), true, true);
887: for (JReferenceType type : program.codeGenTypes) {
888: rescuer.rescue(type, true, saveCodeGenTypes);
889: }
890: for (JMethod method : program.entryMethods) {
891: rescuer.rescue(method);
892: }
893:
894: UpRefVisitor upRefer = new UpRefVisitor(rescuer);
895: do {
896: rescuer.commitInstantiatedTypes();
897: upRefer.accept(program);
898: } while (upRefer.didRescue());
899:
900: PruneVisitor pruner = new PruneVisitor();
901: pruner.accept(program);
902: if (!pruner.didChange()) {
903: break;
904: }
905:
906: CleanupRefsVisitor cleaner = new CleanupRefsVisitor();
907: cleaner.accept(program);
908:
909: referencedTypes.clear();
910: referencedNonTypes.clear();
911: methodToOriginalParamsMap.clear();
912: madeChanges = true;
913: }
914: return madeChanges;
915: }
916:
917: }
|