void doVirtual(final MethodEditor caller, final MemberRef callee) {
		// Figure out which methods the callee can resolve to.
		final Iterator resolvesToWith = hier.resolvesToWith(callee).iterator();

		while (resolvesToWith.hasNext()) {
			final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) resolvesToWith
					.next();
			CallGraph.db("      resolves to " + rtw.method);

			// Add all possible non-abstract methods to the call graph.
			// This way, when a blocked method becomes unblocked, it will
			// still be in the call graph.
			addCall(caller, rtw.method);

			Iterator rTypes = rtw.rTypes.iterator();
			boolean isLive = false; // Is one of the rTypes live?
			while (rTypes.hasNext()) {
				final Type rType = (Type) rTypes.next();
				if (liveClasses.contains(rType)) {
					isLive = true;
					CallGraph.db("      Method " + rtw.method + " is live");
					worklist.add(rtw.method);
					break;
				}
			}

			if (!isLive) {
				// If none of the receiver types is live, then the method is
				// blocked on all possible receiver types.
				rTypes = rtw.rTypes.iterator();
				final StringBuffer sb = new StringBuffer();
				while (rTypes.hasNext()) {
					final Type rType = (Type) rTypes.next();
					Set blockedMethods = (Set) blocked.get(rType);
					if (blockedMethods == null) {
						blockedMethods = new HashSet();
						blocked.put(rType, blockedMethods);
					}
					blockedMethods.add(rtw.method);
					sb.append(rType.toString());
					if (rTypes.hasNext()) {
						sb.append(',');
					}
				}
				CallGraph.db("      Blocked " + rtw.method + " on " + sb);
			}
		}
	}