-
Notifications
You must be signed in to change notification settings - Fork 30
Improve interface of reachable method analysis and related states to prevent misuse #220
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
Changes from all commits
3f8735c
30bec19
65647b2
dee8a44
2c20434
8aa16f2
6742c9b
d5e32d1
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 |
---|---|---|
|
@@ -14,7 +14,6 @@ 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.EPS | ||
import org.opalj.fpcf.FinalP | ||
import org.opalj.fpcf.InterimPartialResult | ||
import org.opalj.fpcf.NoResult | ||
import org.opalj.fpcf.ProperPropertyComputationResult | ||
|
@@ -28,6 +27,8 @@ import org.opalj.tac.fpcf.properties.TACAI | |
* Base trait for analyses that are executed for every method that is reachable. | ||
* The analysis is performed by `processMethod`. | ||
* | ||
* Note that methods without a body are not processed unless `processMethodWithoutBody` is overridden. | ||
* | ||
* @author Florian Kuebler | ||
*/ | ||
trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | ||
|
@@ -36,73 +37,66 @@ 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 | ||
if (!declaredMethod.hasSingleDefinedMethod) | ||
return processMethodWithoutBody(callersEOptP); | ||
return processNewContexts(callersEOptP, NoCallers) { processMethodWithoutBody }; | ||
|
||
val method = declaredMethod.definedMethod | ||
|
||
// we only allow defined methods with declared type eq. to the class of the method | ||
if (method.classFile.thisType != declaredMethod.declaringClassType) | ||
return NoResult; | ||
|
||
if (method.body.isEmpty) | ||
if (method.body.isEmpty) { | ||
// happens in particular for native methods | ||
return processMethodWithoutBody(callersEOptP); | ||
return processNewContexts(callersEOptP, NoCallers) { processMethodWithoutBody }; | ||
}; | ||
|
||
val tacEP = propertyStore(method, TACAI.key) | ||
|
||
if (tacEP.hasUBP && tacEP.ub.tac.isDefined) { | ||
processMethod(callersEOptP, null, tacEP.asEPS) | ||
processMethodWithTAC(callersEOptP, NoCallers, tacEP.asEPS) | ||
} 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 | ||
private def processMethodWithTAC( | ||
eOptP: EOptionP[DeclaredMethod, Callers], | ||
oldCallers: Callers, | ||
tacEP: EPS[Method, TACAI] | ||
): ProperPropertyComputationResult = { | ||
processNewContexts(eOptP, oldCallers) { processMethod(_, tacEP) } | ||
} | ||
|
||
private[this] def processMethod( | ||
eOptP: EOptionP[DeclaredMethod, Callers], | ||
seen: Callers, | ||
tacEP: EPS[Method, TACAI] | ||
): ProperPropertyComputationResult = { | ||
private def processNewContexts( | ||
eOptP: EOptionP[DeclaredMethod, Callers], | ||
oldCallers: Callers | ||
)(processMethod: ContextType => ProperPropertyComputationResult): 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) | ||
} | ||
|
||
Results( | ||
InterimPartialResult(Set(eOptP), continuationForCallers(eOptP.ub, tacEP)), | ||
InterimPartialResult(Set(eOptP), continuationForCallers(processNewContexts(_, newCallers)(processMethod))), | ||
results | ||
) | ||
} | ||
|
||
protected def processMethodWithoutBody( | ||
callContext: ContextType | ||
): ProperPropertyComputationResult = Results() | ||
|
||
def processMethod( | ||
callContext: ContextType, | ||
tacEP: EPS[Method, TACAI] | ||
|
@@ -113,9 +107,9 @@ trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | |
)(someEPS: SomeEPS): ProperPropertyComputationResult = { | ||
someEPS match { | ||
case UBP(tac: TACAI) if tac.tac.isDefined => | ||
processMethod( | ||
processMethodWithTAC( | ||
propertyStore(declaredMethod, Callers.key), | ||
null, | ||
NoCallers, | ||
someEPS.asInstanceOf[EPS[Method, TACAI]] | ||
) | ||
case _ => | ||
|
@@ -124,13 +118,8 @@ trait ReachableMethodAnalysis extends FPCFAnalysis with TypeConsumerAnalysis { | |
} | ||
|
||
private[this] def continuationForCallers( | ||
oldCallers: Callers, | ||
tacEP: EPS[Method, TACAI] | ||
)( | ||
update: SomeEPS | ||
): ProperPropertyComputationResult = { | ||
val newCallers = update.asInstanceOf[EPS[DeclaredMethod, Callers]] | ||
processMethod(newCallers, oldCallers, tacEP) | ||
processMethod: EOptionP[DeclaredMethod, Callers] => ProperPropertyComputationResult | ||
)(someEPS: SomeEPS): ProperPropertyComputationResult = { | ||
processMethod(someEPS.asInstanceOf[EPS[DeclaredMethod, Callers]]) | ||
} | ||
|
||
} |
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).