| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * 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.android.dx.cf.code; |
| |
| import com.android.dx.rop.code.RegisterSpec; |
| import com.android.dx.rop.type.Type; |
| import com.android.dx.rop.type.TypeBearer; |
| import com.android.dx.util.ExceptionWithContext; |
| import com.android.dx.util.Hex; |
| import com.android.dx.util.MutabilityControl; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Representation of a set of local variable arrays, with Java semantics. |
| * This peculiar case is to support in-method subroutines, which can |
| * have different locals sets for each caller. |
| * |
| * <p><b>Note:</b> For the most part, the documentation for this class |
| * ignores the distinction between {@link com.android.dx.rop.type.Type} and {@link |
| * com.android.dx.rop.type.TypeBearer}.</p> |
| */ |
| public class LocalsArraySet extends LocalsArray { |
| |
| /** |
| * The primary LocalsArray represents the locals as seen from |
| * the subroutine itself, which is the merged representation of all the |
| * individual locals states. |
| */ |
| private final OneLocalsArray primary; |
| |
| /** |
| * Indexed by label of caller block: the locals specific to each caller's |
| * invocation of the subroutine. |
| */ |
| private final ArrayList<LocalsArray> secondaries; |
| |
| /** |
| * Constructs an instance. The locals array initially consists of |
| * all-uninitialized values (represented as <code>null</code>s). |
| * |
| * @param maxLocals >= 0; the maximum number of locals this instance |
| * can refer to |
| */ |
| public LocalsArraySet(int maxLocals) { |
| super(maxLocals != 0); |
| primary = new OneLocalsArray(maxLocals); |
| secondaries = new ArrayList(); |
| } |
| |
| /** |
| * Constructs an instance with the specified primary and secondaries set. |
| * |
| * @param primary non-null; primary locals to use |
| * @param secondaries non-null; secondaries set, indexed by subroutine |
| * caller label. |
| */ |
| public LocalsArraySet(OneLocalsArray primary, |
| ArrayList<LocalsArray> secondaries) { |
| super(primary.getMaxLocals() > 0); |
| |
| this.primary = primary; |
| this.secondaries = secondaries; |
| } |
| |
| /** |
| * Constructs an instance which is a copy of another. |
| * |
| * @param toCopy non-null; instance to copy. |
| */ |
| private LocalsArraySet(LocalsArraySet toCopy) { |
| super(toCopy.getMaxLocals() > 0); |
| |
| primary = toCopy.primary.copy(); |
| secondaries = new ArrayList(toCopy.secondaries.size()); |
| |
| int sz = toCopy.secondaries.size(); |
| for(int i = 0; i < sz; i++) { |
| LocalsArray la = toCopy.secondaries.get(i); |
| |
| if (la == null) { |
| secondaries.add(null); |
| } else { |
| secondaries.add(la.copy()); |
| } |
| } |
| } |
| |
| |
| /** @inheritDoc */ |
| @Override |
| public void setImmutable() { |
| primary.setImmutable(); |
| |
| for (LocalsArray la: secondaries) { |
| if (la != null) { |
| la.setImmutable(); |
| } |
| } |
| super.setImmutable(); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public LocalsArray copy() { |
| return new LocalsArraySet(this); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public void annotate(ExceptionWithContext ex) { |
| ex.addContext("(locals array set; primary)"); |
| primary.annotate(ex); |
| |
| int sz = secondaries.size(); |
| for(int label = 0; label < sz; label++) { |
| LocalsArray la = secondaries.get(label); |
| |
| if (la != null) { |
| ex.addContext("(locals array set: primary for caller " |
| + Hex.u2(label) + ')'); |
| |
| la.getPrimary().annotate(ex); |
| } |
| } |
| } |
| |
| /** {@inheritDoc*/ |
| public String toHuman() { |
| StringBuilder sb = new StringBuilder(); |
| |
| sb.append("(locals array set; primary)\n"); |
| |
| sb.append(getPrimary().toHuman()); |
| sb.append('\n'); |
| |
| int sz = secondaries.size(); |
| for(int label = 0; label < sz; label++) { |
| LocalsArray la = secondaries.get(label); |
| |
| if (la != null) { |
| sb.append("(locals array set: primary for caller " |
| + Hex.u2(label) + ")\n"); |
| |
| sb.append(la.getPrimary().toHuman()); |
| sb.append('\n'); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public void makeInitialized(Type type) { |
| int len = primary.getMaxLocals(); |
| |
| if (len == 0) { |
| // We have to check for this before checking for immutability. |
| return; |
| } |
| |
| throwIfImmutable(); |
| |
| primary.makeInitialized(type); |
| |
| for (LocalsArray la: secondaries) { |
| if (la != null) { |
| la.makeInitialized(type); |
| } |
| } |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public int getMaxLocals() { |
| return primary.getMaxLocals(); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public void set(int idx, TypeBearer type) { |
| throwIfImmutable(); |
| |
| primary.set(idx, type); |
| |
| for (LocalsArray la: secondaries) { |
| if (la != null) { |
| la.set(idx, type); |
| } |
| } |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public void set(RegisterSpec spec) { |
| set(spec.getReg(), spec); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public void invalidate(int idx) { |
| throwIfImmutable(); |
| |
| primary.invalidate(idx); |
| |
| for (LocalsArray la: secondaries) { |
| if (la != null) { |
| la.invalidate(idx); |
| } |
| } |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public TypeBearer getOrNull(int idx) { |
| return primary.getOrNull(idx); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public TypeBearer get(int idx) { |
| return primary.get(idx); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public TypeBearer getCategory1(int idx) { |
| return primary.getCategory1(idx); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public TypeBearer getCategory2(int idx) { |
| return primary.getCategory2(idx); |
| } |
| |
| /** |
| * Merges this set with another <code>LocalsArraySet</code> instance. |
| * |
| * @param other non-null; to merge |
| * @return non-null; this instance if merge was a no-op, or |
| * new merged instance. |
| */ |
| private LocalsArraySet mergeWithSet(LocalsArraySet other) { |
| OneLocalsArray newPrimary; |
| ArrayList<LocalsArray> newSecondaries; |
| boolean secondariesChanged = false; |
| |
| newPrimary = primary.merge(other.getPrimary()); |
| |
| int sz1 = secondaries.size(); |
| int sz2 = other.secondaries.size(); |
| int sz = Math.max(sz1, sz2); |
| newSecondaries = new ArrayList(sz); |
| |
| for (int i = 0; i < sz; i++) { |
| LocalsArray la1 = (i < sz1 ? secondaries.get(i) : null); |
| LocalsArray la2 = (i < sz2 ? other.secondaries.get(i) : null); |
| LocalsArray resultla = null; |
| |
| if (la1 == la2) { |
| resultla = la1; |
| } else if (la1 == null) { |
| resultla = la2; |
| } else if (la2 == null) { |
| resultla = la1; |
| } else { |
| try { |
| resultla = la1.merge(la2); |
| } catch (SimException ex) { |
| ex.addContext( |
| "Merging locals set for caller block " + Hex.u2(i)); |
| } |
| } |
| |
| secondariesChanged = secondariesChanged || (la1 != resultla); |
| |
| newSecondaries.add(resultla); |
| } |
| |
| if ((primary == newPrimary) && ! secondariesChanged ) { |
| return this; |
| } |
| |
| return new LocalsArraySet(newPrimary, newSecondaries); |
| } |
| |
| /** |
| * Merges this set with a <code>OneLocalsArray</code> instance. |
| * |
| * @param other non-null; to merge |
| * @return non-null; this instance if merge was a no-op, or |
| * new merged instance. |
| */ |
| private LocalsArraySet mergeWithOne(OneLocalsArray other) { |
| OneLocalsArray newPrimary; |
| ArrayList<LocalsArray> newSecondaries; |
| boolean secondariesChanged = false; |
| |
| newPrimary = primary.merge(other.getPrimary()); |
| newSecondaries = new ArrayList(secondaries.size()); |
| |
| int sz = secondaries.size(); |
| for (int i = 0; i < sz; i++) { |
| LocalsArray la = secondaries.get(i); |
| LocalsArray resultla = null; |
| |
| if (la != null) { |
| try { |
| resultla = la.merge(other); |
| } catch (SimException ex) { |
| ex.addContext("Merging one locals against caller block " |
| + Hex.u2(i)); |
| } |
| } |
| |
| secondariesChanged = secondariesChanged || (la != resultla); |
| |
| newSecondaries.add(resultla); |
| } |
| |
| if ((primary == newPrimary) && ! secondariesChanged ) { |
| return this; |
| } |
| |
| return new LocalsArraySet(newPrimary, newSecondaries); |
| } |
| |
| /** @inheritDoc */ |
| @Override |
| public LocalsArraySet merge(LocalsArray other) { |
| LocalsArraySet result; |
| |
| try { |
| if (other instanceof LocalsArraySet) { |
| result = mergeWithSet((LocalsArraySet) other); |
| } else { |
| result = mergeWithOne((OneLocalsArray) other); |
| } |
| } catch (SimException ex) { |
| ex.addContext("underlay locals:"); |
| annotate(ex); |
| ex.addContext("overlay locals:"); |
| other.annotate(ex); |
| throw ex; |
| } |
| |
| result.setImmutable(); |
| return result; |
| } |
| |
| /** |
| * Gets the <code>LocalsArray</code> instance for a specified subroutine |
| * caller label, or null if label has no locals associated with it. |
| * |
| * @param label >=0 subroutine caller label |
| * @return null-ok; locals if available. |
| */ |
| private LocalsArray getSecondaryForLabel(int label) { |
| if (label >= secondaries.size()) { |
| return null; |
| } |
| |
| return secondaries.get(label); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public LocalsArraySet mergeWithSubroutineCaller |
| (LocalsArray other, int predLabel) { |
| |
| LocalsArray mine = getSecondaryForLabel(predLabel); |
| LocalsArray newSecondary; |
| OneLocalsArray newPrimary; |
| |
| newPrimary = primary.merge(other.getPrimary()); |
| |
| if (mine == other) { |
| newSecondary = mine; |
| } else if (mine == null) { |
| newSecondary = other; |
| } else { |
| newSecondary = mine.merge(other); |
| } |
| |
| if ((newSecondary == mine) && (newPrimary == primary)) { |
| return this; |
| } else { |
| /* |
| * We're going to re-build a primary as a merge of all the |
| * secondaries. |
| */ |
| newPrimary = null; |
| |
| int szSecondaries = secondaries.size(); |
| int sz = Math.max(predLabel + 1, szSecondaries); |
| ArrayList<LocalsArray> newSecondaries = new ArrayList(sz); |
| for (int i = 0; i < sz; i++) { |
| LocalsArray la = null; |
| |
| if (i == predLabel) { |
| /* |
| * This LocalsArray always replaces any existing one, |
| * since this is the result of a refined iteration. |
| */ |
| la = newSecondary; |
| } else if (i < szSecondaries) { |
| la = secondaries.get(i); |
| } |
| |
| if (la != null) { |
| if (newPrimary == null) { |
| newPrimary = la.getPrimary(); |
| } else { |
| newPrimary = newPrimary.merge(la.getPrimary()); |
| } |
| } |
| |
| newSecondaries.add(la); |
| } |
| |
| LocalsArraySet result |
| = new LocalsArraySet(newPrimary, newSecondaries); |
| result.setImmutable(); |
| return result; |
| } |
| } |
| |
| /** |
| * Returns a LocalsArray instance representing the locals state that should |
| * be used when returning to a subroutine caller. |
| * |
| * @param subLabel >= 0; A calling label of a subroutine |
| * @return null-ok; an instance for this subroutine, or null if subroutine |
| * is not in this set. |
| */ |
| public LocalsArray subArrayForLabel(int subLabel) { |
| LocalsArray result = getSecondaryForLabel(subLabel); |
| return result; |
| } |
| |
| /**{@inheritDoc}*/ |
| @Override |
| protected OneLocalsArray getPrimary() { |
| return primary; |
| } |
| } |