-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve interface of reachable method analysis and related states to prevent misuse #220
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,8 +13,8 @@ import org.opalj.br.fpcf.FPCFAnalysis | |
import org.opalj.br.fpcf.properties.cg.Callers | ||
import org.opalj.br.fpcf.properties.cg.NoCallers | ||
import org.opalj.fpcf.EOptionP | ||
import org.opalj.fpcf.EPK | ||
import org.opalj.fpcf.EPS | ||
import org.opalj.fpcf.FinalP | ||
import org.opalj.fpcf.InterimPartialResult | ||
import org.opalj.fpcf.NoResult | ||
import org.opalj.fpcf.ProperPropertyComputationResult | ||
|
@@ -36,18 +36,9 @@ trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | |
|
||
final def analyze(declaredMethod: DeclaredMethod): PropertyComputationResult = { | ||
val callersEOptP = propertyStore(declaredMethod, Callers.key) | ||
(callersEOptP: @unchecked) match { | ||
case FinalP(NoCallers) => | ||
// nothing to do, since there is no caller | ||
return NoResult; | ||
|
||
case eps: EPS[_, _] => | ||
if (eps.ub eq NoCallers) { | ||
// we can not create a dependency here, so the analysis is not allowed to create | ||
// such a result | ||
throw new IllegalStateException("illegal immediate result for callers") | ||
} | ||
// the method is reachable, so we analyze it! | ||
|
||
if (callersEOptP.isFinal && callersEOptP.ub == NoCallers) { | ||
return NoResult; | ||
} | ||
|
||
// we only allow defined methods | ||
|
@@ -65,47 +56,41 @@ trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | |
return processMethodWithoutBody(callersEOptP); | ||
|
||
val tacEP = propertyStore(method, TACAI.key) | ||
|
||
if (tacEP.hasUBP && tacEP.ub.tac.isDefined) { | ||
processMethod(callersEOptP, null, tacEP.asEPS) | ||
processMethod(callersEOptP, NoCallers, tacEP) | ||
} else { | ||
InterimPartialResult(Set(tacEP), continuationForTAC(declaredMethod)) | ||
} | ||
} | ||
|
||
protected val processesMethodsWithoutBody = false | ||
|
||
protected def processMethodWithoutBody( | ||
eOptP: EOptionP[DeclaredMethod, Callers] | ||
): PropertyComputationResult = { | ||
if (processesMethodsWithoutBody) { | ||
processMethod(eOptP, null, null) | ||
} else | ||
NoResult | ||
} | ||
): PropertyComputationResult = processMethod(eOptP, NoCallers, EPK(eOptP.e.definedMethod, TACAI.key)) | ||
|
||
private[this] def processMethod( | ||
eOptP: EOptionP[DeclaredMethod, Callers], | ||
seen: Callers, | ||
tacEP: EPS[Method, TACAI] | ||
eOptP: EOptionP[DeclaredMethod, Callers], | ||
oldCallers: Callers, | ||
tacEOptP: EOptionP[Method, TACAI] | ||
): ProperPropertyComputationResult = { | ||
val newCallers = if (eOptP.hasUBP) eOptP.ub else NoCallers | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be more sensible to turn this into an if, only executing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we need the |
||
var results: List[ProperPropertyComputationResult] = Nil | ||
eOptP.ub.forNewCalleeContexts(seen, eOptP.e) { calleeContext => | ||
|
||
newCallers.forNewCalleeContexts(oldCallers, eOptP.e) { calleeContext => | ||
val theCalleeContext = | ||
if (calleeContext.hasContext) calleeContext.asInstanceOf[ContextType] | ||
else typeIterator.newContext(eOptP.e) | ||
results ::= processMethod(theCalleeContext, tacEP) | ||
results ::= processMethod(theCalleeContext, tacEOptP) | ||
} | ||
|
||
Results( | ||
InterimPartialResult(Set(eOptP), continuationForCallers(eOptP.ub, tacEP)), | ||
InterimPartialResult(Set(eOptP), continuationForCallers(newCallers, tacEOptP)), | ||
results | ||
) | ||
} | ||
|
||
def processMethod( | ||
callContext: ContextType, | ||
tacEP: EPS[Method, TACAI] | ||
tacEOptP: EOptionP[Method, TACAI] | ||
): ProperPropertyComputationResult | ||
|
||
protected def continuationForTAC( | ||
|
@@ -115,7 +100,7 @@ trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | |
case UBP(tac: TACAI) if tac.tac.isDefined => | ||
processMethod( | ||
propertyStore(declaredMethod, Callers.key), | ||
null, | ||
NoCallers, | ||
someEPS.asInstanceOf[EPS[Method, TACAI]] | ||
) | ||
case _ => | ||
|
@@ -125,12 +110,28 @@ trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | |
|
||
private[this] def continuationForCallers( | ||
oldCallers: Callers, | ||
tacEP: EPS[Method, TACAI] | ||
tacEOptP: EOptionP[Method, TACAI] | ||
)( | ||
update: SomeEPS | ||
): ProperPropertyComputationResult = { | ||
val newCallers = update.asInstanceOf[EPS[DeclaredMethod, Callers]] | ||
processMethod(newCallers, oldCallers, tacEP) | ||
processMethod(newCallers, oldCallers, tacEOptP) | ||
} | ||
} | ||
|
||
trait DefinedBodyReachableMethodAnalysis extends ReachableMethodAnalysis { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there seems to be only one ReachableMethodAnalysis that does process methods without body, might it be more sensible to switch the logic around, with the base trait not doing that and the specialized trait doing it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I pondered this and did could not answer this question. On one hand, if the specialized trait is used far more than the base trait, something might be wrong. Additionally, the specialized trait right now restricts the abilities of the base trait (if you want to view "does not process but just returns no result" as a restriction) which feels a bit against the core principles of inheritance. On the other hand, even if the base trait does not handle unknown bodies, it still needs to define extension points for
In short (using the flipped version): If you only extend the All in all, I feel more comfortable with the current solution, but I am happy to replace it with a better one if you have some ideas. |
||
|
||
override protected final def processMethodWithoutBody( | ||
eOptP: EOptionP[DeclaredMethod, Callers] | ||
): PropertyComputationResult = NoResult | ||
|
||
override final def processMethod( | ||
callContext: ContextType, | ||
tacEPOpt: EOptionP[Method, TACAI] | ||
): ProperPropertyComputationResult = processMethod(callContext, tacEPOpt.asEPS) | ||
|
||
def processMethod( | ||
callContext: ContextType, | ||
tacEP: EPS[Method, TACAI] | ||
): ProperPropertyComputationResult | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we keep the check for an illegal
NoCallers
interim state at least as an assertion? Right now it is not clearly visible that the method is reachable and should be analyzed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
NoCallers
interim state was illegal since the code at the top did not know the class could use the continuation without a depending caller. Since the class can invoke and handle continuations just fine without using a depender constructed from a caller, theNoCallers
interim state is not illegal anymore.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's an illegal state in general, isn't it? I don't think call graph analyses are supposed to ever create such an interim state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is an illegal state in general, then it should be asserted by the call graph generating framework and not the framework for analysis that use the call graph (even if they at least partially overlap). This class seems like the wrong area of code to make such an assertion (e.g. it would only be triggered when using such an analysis even though the result should be illegal regardless of using this class or not).