Java tutorial
/******************************************************************************* * Indus, a program analysis and transformation toolkit for Java. * Copyright (c) 2001, 2007 Venkatesh Prasad Ranganath * * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 which accompanies * the distribution containing this program, and is available at * http://www.opensource.org/licenses/eclipse-1.0.php. * * For questions about the license, copyright, and software, contact * Venkatesh Prasad Ranganath at venkateshprasad.ranganath@gmail.com * * This software was developed by Venkatesh Prasad Ranganath in SAnToS Laboratory * at Kansas State University. *******************************************************************************/ package edu.ksu.cis.indus.staticanalyses.concurrency.escape; import edu.ksu.cis.indus.common.datastructures.FastUnionFindElement; import edu.ksu.cis.indus.common.datastructures.HistoryAwareLIFOWorkBag; import edu.ksu.cis.indus.common.datastructures.IWorkBag; import edu.ksu.cis.indus.common.datastructures.Pair; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.builder.ToStringBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import soot.SootMethod; import soot.Type; /** * This class represents the method/site context as described in the techreport <a * href="http://www.cis.ksu.edu/santos/papers/technicalReports/SAnToS-TR2003-6.pdf">Honing the Detection of Interference and * Ready Dependence for Slicing Concurrent Java Programs.</a> This serves more as a container for the various alias * sets/equivalence classes that occur at the method interface. * * @author <a href="http://www.cis.ksu.edu/~rvprasad">Venkatesh Prasad Ranganath</a> * @author $Author: rvprasad $ * @version $Revision: 1.47 $ */ final class MethodContext extends FastUnionFindElement<MethodContext> implements Cloneable { /** * The logger used by instances of this class to log messages. */ private static final Logger LOGGER = LoggerFactory.getLogger(MethodContext.class); /** * The alias set associated with the return value of the associated method. * * @invariant AliasSet.canHaveAliasSet(method.getReturnType()) implies ret != null * @invariant not AliasSet.canHaveAliasSet(method.getReturnType()) implies ret == null */ AliasSet ret; /** * The alias set associated with the <code>this</code> variable of the associated method. * * @invariant method.isStatic() implies thisAS == null * @invariant not method.isStatic() implies thisAS != null */ AliasSet thisAS; /** * The alias set associated with the exceptions thrown by the associated method. * * @invariant thrown != null */ AliasSet thrown; /** * The alias sets associated with the arguments to the associated method. * * @invariant method.getParameterTypes()->forall(o | AliasSet.canHaveAliasSet(o) implies * argAliasSets.get(method.getParameterTypes().indexOf(o)) != null) * @invariant method.getParameterTypes()->forall(o | not AliasSet.canHaveAliasSet(o) implies * argAliasSets.get(method.getParameterTypes().indexOf(o)) == null) */ private List<AliasSet> argAliasSets; /** * The analysis that created this instance. */ private final EquivalenceClassBasedEscapeAnalysis ecba; /** * This indicates if the method/invocation context reads global data. */ private boolean globalDataRead; /** * This indicates if the method/invocation context writes global data. */ private boolean globalDataWritten; /** * The associated method. */ private final SootMethod method; /** * Creates a new MethodContext object. * * @param sm is the method being represented by this object. * @param thisASParam is the alias set corresponding to "this" variable. * @param argASs is the alias sets corresponding to the arguments/parameters of the method. * @param retAS is the alias set corresponding to the return value of the method. * @param thrownAS is the alias set corresponding to the exceptions thrown by the method. * @param analysis that created this instance. * @pre sm != null and argASs != null and thrownAS != null and analysis != null */ MethodContext(final SootMethod sm, final AliasSet thisASParam, final List<AliasSet> argASs, final AliasSet retAS, final AliasSet thrownAS, final EquivalenceClassBasedEscapeAnalysis analysis) { method = sm; ecba = analysis; argAliasSets = new ArrayList<AliasSet>(argASs); if (EquivalenceClassBasedEscapeAnalysis.canHaveAliasSet(sm.getReturnType())) { ret = retAS; } if (!sm.isStatic()) { this.thisAS = thisASParam; } thrown = thrownAS; } /** * Creates a new MethodContext object. The alias sets for the various parts of the method interface is created as * required. * * @param sm is the method being represented by this object. * @param analysis that created this instance. * @pre sm != null and analysis != null */ MethodContext(final SootMethod sm, final EquivalenceClassBasedEscapeAnalysis analysis) { method = sm; ecba = analysis; final int _paramCount = sm.getParameterCount(); if (_paramCount > 0) { argAliasSets = new ArrayList<AliasSet>(_paramCount); for (int _i = 0; _i < _paramCount; _i++) { argAliasSets.add(AliasSet.getASForType(sm.getParameterType(_i))); } } else { argAliasSets = Collections.emptyList(); } final Type _retType = sm.getReturnType(); ret = AliasSet.getASForType(_retType); thrown = AliasSet.createAliasSet(); if (!sm.isStatic()) { thisAS = AliasSet.createAliasSet(); if (sm.isSynchronized()) { thisAS.setLocked(); } } } /** * Fixes up the field maps of the alias sets in the given map. When alias sets are cloned, the field maps are cloned. * Hence, they are shallow copied. This method clones the relation between the alias sets among their clones. * * @param src2clone maps an representative alias set to it's clone. This is also an out parameter that will contain new * mappings. * @throws CloneNotSupportedException when <code>clone()</code> fails. */ private static void fixUpFieldMapsOfClone(final Map<AliasSet, AliasSet> src2clone) throws CloneNotSupportedException { final IWorkBag<AliasSet> _wb = new HistoryAwareLIFOWorkBag<AliasSet>(new HashSet<AliasSet>()); _wb.addAllWork(src2clone.keySet()); while (_wb.hasWork()) { final AliasSet _src = _wb.getWork(); final AliasSet _clone = src2clone.get(_src); final Map<String, AliasSet> _srcASFieldMap = _src.getFieldMap(); final Set<String> _srcASFields = _srcASFieldMap.keySet(); final Iterator<String> _i = _srcASFields.iterator(); final int _iEnd = _srcASFields.size(); for (int _iIndex = 0; _iIndex < _iEnd; _iIndex++) { final String _field = _i.next(); /* * We use the representative alias set as it is possible that a field may have 2 alias sets in different * contexts but the same representative alias set in both contexts. We don't do the same for clones as they * are the representatives until they are unified, which happens in the following block. */ final AliasSet _srcFieldAS = _src.getASForField(_field).find(); final AliasSet _cloneFieldAS; _cloneFieldAS = getCloneOf(src2clone, _srcFieldAS); // if the clone is different from the src only then we need to further process src. if (_cloneFieldAS != _srcFieldAS) { _wb.addWork(_srcFieldAS); } _clone.putASForField(_field, _cloneFieldAS); } } // Unify the clones to reflect the relation between their originators. for (final Iterator<AliasSet> _i = src2clone.keySet().iterator(); _i.hasNext();) { final AliasSet _k1 = _i.next(); for (final Iterator<AliasSet> _j = src2clone.keySet().iterator(); _j.hasNext();) { final AliasSet _k2 = _j.next(); if (_k1 != _k2 && _k1.find() == _k2.find()) { final AliasSet _v1 = src2clone.get(_k1); final AliasSet _v2 = src2clone.get(_k2); _v1.unifyAliasSetHelper(_v2, false); } } } return; } /** * Retrieves the clone corresponding to the equivalence class of the given alias set. * * @param src2clone maps alias set to alias set. * @param as for which the clone is required. * @return an alias set. * @throws CloneNotSupportedException <i>this should not occur.</i> * @pre as != null and src2clone != null * @pre src2clone.oclIsKindOf(Map(AliasSet, AliasSet)) * @pre src2clone.keySet()->forall(o | o = o.find()) * @post src2clone.keySet()->forall(o | o = o.find()) * @post src2clone.get(as.find()) != result and result != null */ private static AliasSet getCloneOf(final Map<AliasSet, AliasSet> src2clone, final AliasSet as) throws CloneNotSupportedException { final AliasSet _repr = as.find(); AliasSet _result = src2clone.get(_repr); if (_result == null) { _result = _repr.clone(); src2clone.put(_repr, _result); } return _result; } /** * Clones this object. * * @return clone of this object. * @throws CloneNotSupportedException if <code>java.lang.Object.clone()</code> fails. */ @Override public MethodContext clone() throws CloneNotSupportedException { MethodContext _clone = null; if (find() != this) { _clone = find().clone(); } else { _clone = (MethodContext) super.clone(); /* * map from the representative to it's clone. The clone is always the representative element, but this is not true * for the clonee. */ final Map<AliasSet, AliasSet> _clonee2clone = new HashMap<AliasSet, AliasSet>(); _clone.set = null; if (thisAS != null) { _clone.thisAS = getCloneOf(_clonee2clone, thisAS); } _clone.argAliasSets = new ArrayList<AliasSet>(); for (final Iterator<AliasSet> _i = argAliasSets.iterator(); _i.hasNext();) { final AliasSet _tmp = _i.next(); if (_tmp != null) { _clone.argAliasSets.add(getCloneOf(_clonee2clone, _tmp)); } else { _clone.argAliasSets.add(null); } } if (ret != null) { _clone.ret = getCloneOf(_clonee2clone, ret); } _clone.thrown = getCloneOf(_clonee2clone, thrown); MethodContext.fixUpFieldMapsOfClone(_clonee2clone); } return _clone; } /** * Erases intra thread and inter-procedural reference entities. */ public void eraseIntraThreadInterProcRefEntities() { if (find() != this) { find().eraseIntraThreadInterProcRefEntities(); } else { if (ret != null) { ret.eraseIntraThreadInterProcRefEntities(); } if (thrown != null) { thrown.eraseIntraThreadInterProcRefEntities(); } if (thisAS != null) { thisAS.eraseIntraThreadInterProcRefEntities(); } for (final Iterator<AliasSet> _i = argAliasSets.iterator(); _i.hasNext();) { final AliasSet _argAS = _i.next(); if (_argAS != null) { _argAS.eraseIntraThreadInterProcRefEntities(); } } for (final Iterator<AliasSet> _j = ecba.class2aliasSet.values().iterator(); _j.hasNext();) { final AliasSet _as = _j.next(); _as.eraseIntraThreadInterProcRefEntities(); } } } /** * @see java.lang.Object#toString() */ @Override public String toString() { return new ToStringBuilder(this).append("thrown", this.thrown).append("argAliasSets", this.argAliasSets) .append("ret", this.ret).append("thisAS", this.thisAS).append("method", this.method).toString(); } /** * Rewires the context such that it contains only representative alias sets and no the nominal(indirectional) alias sets. */ void discardReferentialAliasSets() { if (thrown != null) { thrown = thrown.find(); } if (thisAS != null) { thisAS = thisAS.find(); } if (ret != null) { ret = ret.find(); } if (argAliasSets != null && !argAliasSets.isEmpty()) { final int _size = argAliasSets.size(); for (int _i = 0; _i < _size; _i++) { final AliasSet _temp = argAliasSets.get(_i); if (_temp != null) { argAliasSets.set(_i, _temp.find()); } } } } /** * Retrieves the alias set in the given method context that corresponds to the given alias set in this method context. * * @param ref the reference alias set that occurs in this context. * @param context the context in which <code>ref</code> occurs. * @return the alias set in this context and that corresponds to <code>ref</code>. This will be <code>null</code> if * there is no such alias set. * @pre ref != null and context != null */ AliasSet getImageOfRefInGivenContext(final AliasSet ref, final MethodContext context) { AliasSet _result = null; final Collection<Pair<AliasSet, AliasSet>> _temp = new HashSet<Pair<AliasSet, AliasSet>>(); final AliasSet _thisAS = getThisAS(); final AliasSet _thisAS2 = context.getThisAS(); if (_thisAS != null && _thisAS2 != null) { _temp.clear(); _result = _thisAS.getImageOfRefUnderRoot(_thisAS2, ref, _temp); } for (int _i = argAliasSets.size() - 1; _i >= 0 && _result == null; _i--) { final AliasSet _paramAS = getParamAS(_i); final AliasSet _paramAS2 = context.getParamAS(_i); if (_paramAS != null && _paramAS2 != null) { _temp.clear(); _result = _paramAS.getImageOfRefUnderRoot(_paramAS2, ref, _temp); } } if (_result == null) { final AliasSet _thrownAS = getThrownAS(); final AliasSet _thrownAS2 = context.getThrownAS(); if (_thrownAS != null && _thrownAS2 != null) { _temp.clear(); _result = _thrownAS.getImageOfRefUnderRoot(_thrownAS2, ref, _temp); } if (_result == null) { final AliasSet _returnAS = getReturnAS(); final AliasSet _returnAS2 = context.getReturnAS(); if (_returnAS != null && _returnAS2 != null) { _temp.clear(); _result = _returnAS.getImageOfRefUnderRoot(_returnAS2, ref, _temp); } } } return _result; } /** * Retrieves the alias set corresponding to the parameter occuring at position <code>index</code> in the method * interface. * * @param index is the position of the parameter. * @return the corresponding alias set. */ AliasSet getParamAS(final int index) { return find().argAliasSets.get(index); } /** * Retrieves the alias set corresponding to the return value of the method. * * @return the corresponding alias set. */ AliasSet getReturnAS() { return find().ret; } /** * Retrieves the alias set corresponding to "this" variable of the method. * * @return the corresponding alias set. */ AliasSet getThisAS() { return find().thisAS; } /** * Retrieves the alias set corresponding to the exceptions thrown by the method. * * @return the corresponding alias set. * @post result != null */ AliasSet getThrownAS() { return find().thrown; } /** * Marks this method as reading global data. */ void globalDataWasRead() { globalDataRead = true; } /** * Marks this method as writing global data. */ void globalDataWasWritten() { globalDataWritten = true; } /** * Provides information if this method reads global data. * * @return <code>true</code> if global data is read by this method; <code>false</code>, otherwise. */ boolean isGlobalDataRead() { return globalDataRead; } /** * Provides information if this method writes global data. * * @return <code>true</code> if global data is written by this method; <code>false</code>, otherwise. */ boolean isGlobalDataWritten() { return globalDataWritten; } /** * Marks all reachable alias sets as being crossing thread boundary, i.e, visible in multiple threads.. */ void markAsCrossingThreadBoundary() { if (find() != this) { find().markAsCrossingThreadBoundary(); } else { if (ret != null) { ret.markAsCrossingThreadBoundary(); } if (thrown != null) { thrown.markAsCrossingThreadBoundary(); } if (thisAS != null) { thisAS.markAsCrossingThreadBoundary(); } for (final Iterator<AliasSet> _i = argAliasSets.iterator(); _i.hasNext();) { final AliasSet _argAS = _i.next(); if (_argAS != null) { _argAS.markAsCrossingThreadBoundary(); } } } } /** * Propogates the information from the source (this) context to the destination context. Please refer to the {@link * #unifyMethodContext(MethodContext) unify(MethodContext)} for important information. * * @param to is the destination of the information transfer. * @pre to != null */ void propogateInfoFromTo(final MethodContext to) { final MethodContext _fromRep = find(); final MethodContext _toRep = to.find(); if (_fromRep == _toRep) { return; } final int _paramCount = _fromRep.method.getParameterCount(); for (int _i = 0; _i < _paramCount; _i++) { if (EquivalenceClassBasedEscapeAnalysis.canHaveAliasSet(_fromRep.method.getParameterType(_i))) { final AliasSet _temp1 = argAliasSets.get(_i); if (_temp1 != null) { /* * NULL-ARGUMENT SCENARIO: We check if the argument at the position _i was a null constant when this * method context was created. If so, then we decided not propagate the effect. For this reason, the * reference to the original method context should be retained and the propagation should be considered to * be directional. */ final AliasSet _temp2 = _toRep.argAliasSets.get(_i); final AliasSet _temp3 = _fromRep.argAliasSets.get(_i); if (_temp3 != null && _temp2 != null) { _temp3.propogateInfoFromTo(_temp2); } } } } final AliasSet _retAS = _fromRep.ret; if (_retAS != null) { _retAS.propogateInfoFromTo(_toRep.ret); } _fromRep.thrown.propogateInfoFromTo(_toRep.thrown); final AliasSet _thisAS = _fromRep.thisAS; if (_thisAS != null) { _thisAS.propogateInfoFromTo(_toRep.thisAS); } } /** * Unifies this object with itself. This is required while dealing with call-sites which may be executed multiple times. */ void selfUnify() { final MethodContext _methodContext = find(); final int _paramCount = method.getParameterCount(); for (int _i = 0; _i < _paramCount; _i++) { if (EquivalenceClassBasedEscapeAnalysis.canHaveAliasSet(method.getParameterType(_i))) { final AliasSet _aliasSet = _methodContext.argAliasSets.get(_i); // it is possible that the argument at a site-context is null if (_aliasSet != null) { AliasSet.selfUnify(_aliasSet); } } } final AliasSet _mRet = _methodContext.ret; if (_mRet != null) { AliasSet.selfUnify(_mRet); } AliasSet.selfUnify(_methodContext.thrown); final AliasSet _mThis = _methodContext.thisAS; if (_mThis != null) { AliasSet.selfUnify(_mThis); } } /** * Unifies this context with the given context. * <p> * There is asymmetry in alias sets in case of contexts. It is possible that this context may not have alias set for an * argument location where as <code>p</code> may have one. This happens in cases where this context corresponds to a * site-context where <code>null</code> is being passed, hence, that arg position does not require an alias set whereas * <code>p</code> being the context of the method being called requires alias set as there may be sites where the * non-null argument may be provided. In such cases, we safely warn and continue. However, this cannot happen for thrown * exception, this, or return references. associated with call sites and methods. * </p> * * @param p is the context with which the unification should occur. */ void unifyMethodContext(final MethodContext p) { if (p == null) { LOGGER.error("Unification with null requested."); throw new IllegalArgumentException("Unification with null requested."); } final MethodContext _m = find(); final MethodContext _n = p.find(); if (_m != _n) { _m.union(_n); final MethodContext _representative = _m.find(); final MethodContext _represented; if (_representative == _m) { _represented = _n; } else { _represented = _m; } _representative.globalDataWritten |= _represented.globalDataWritten; _representative.globalDataRead |= _represented.globalDataRead; final int _paramCount = method.getParameterCount(); for (int _i = 0; _i < _paramCount; _i++) { if (EquivalenceClassBasedEscapeAnalysis.canHaveAliasSet(method.getParameterType(_i))) { final AliasSet _mAS = _representative.argAliasSets.get(_i); final AliasSet _nAS = _represented.argAliasSets.get(_i); if (_mAS == null) { _representative.argAliasSets.set(_i, _nAS); } else { _mAS.unifyAliasSet(_nAS); } } } unifyAliasSets(_representative.ret, _represented.ret); unifyAliasSets(_representative.thrown, _represented.thrown); unifyAliasSets(_representative.thisAS, _represented.thisAS); } } /** * Unifies the given alias sets. * * @param representative is one of the alias set to be unified. * @param represented is the other alias set to be unified. * @pre representative != null and represented != null */ private void unifyAliasSets(final AliasSet representative, final AliasSet represented) { if ((representative == null && represented != null) || (representative != null && represented == null)) { LOGGER.error("Incompatible method contexts being unified - representative - " + representative + "\n represented - " + represented); throw new IllegalStateException("Unifying null aliasSet"); } else if (representative != null) { representative.unifyAliasSet(represented); } } } // End of File