- Notifications
You must be signed in to change notification settings - Fork 1.9k
Ruby: Model flow through initialize constructors #10714
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
Conversation
72c1ebf to f25495e Compare
aibaars left a comment
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.
LGTM. We might want to add a case for Class.allocate() too. The allocate method creates a new object, but does not call initialize on it.
722f0fb to 373ed9f Compare python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll Fixed Show fixed Hide fixed
373ed9f to 15350fc Compare 0449522 to 9642e90 Compare 6ed01fd to 3b3d25d Compare 3b3d25d to db456fe Compare db456fe to b7d66c8 Compare 5e88040 to c51a0e7 Compare 78eceaa to 80376bd Compare 670581c to 53c3628 Compare 53c3628 to 5d9c64b Compare
asgerf left a comment
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.
I have some inline suggestions, which are probably best left for future PRs.
Looking at the evaluation results, we lose some call edges and a few sinks, but on the whole we gain much more than we lose. So I'd say it's still better to merge now than to leave the PR hanging.
| pragma[nomagic] | ||
| private predicate hasUserDefinedSelf(Module m) { | ||
| // cannot use `lookupSingletonMethod` due to negative recursion | ||
| singletonMethodOnModule(_, "new", m.getSuperClass*()) // not `getAnAncestor` because singleton methods cannot be included |
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.
I suggest renaming this predicate to hasUserDefinedNew.
Could it be implemented in terms of isUserDefinedNew defined earlier in this file?
Also, we seem to lose some call edges due to C.new not being seen as an instance of C when C has an override of self.new, but where the implementation of self.new always returns self.allocate.
I think a reasonably simple fix would be to redefine this predicate to look for overriden new that doesn't obviously return self.allocate:
| singletonMethodOnModule(_, "new", m.getSuperClass*()) // not `getAnAncestor` because singleton methods cannot be included | |
| exists(DataFlow::MethodNode method | | |
| // not `getAnAncestor` because singleton methods cannot be included | |
| singletonMethodOnModule(method.asCallableAstNode(), "new", m.getSuperClass*()) and | |
| not method.getSelfParameter().getAMethodCall("allocate").flowsTo(method.getAReturningNode()) | |
| ) |
But I'm OK with postponing this fix so as not to delay the PR any further.
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.
Good spot; thanks for checking. Let's fix follow-up.
Co-authored-by: Asger F <asgerf@github.com>
| exists(RelevantCall call0, Callable res | | ||
| call0 = call.asCall() and | ||
| res = result.asCallable() and | ||
| result = viableSourceCallable(call) and // make sure to not include e.g. private methods |
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.
This result = viableSourceCallable(call) clause seems to have been lost in this change - is that deliberate?
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.
It is replaced by the res = getTargetInstance(call0, name) calls.
As discussed with @asgerf offline, we have decided to model flow through
initializeconstructors in the following way:C.new(x1, ..., xn), pretend that it is actually a call toC#initialize(x1, ..., xn), but only ifCdoes not define it's ownself.newmethod (in which case we dispatch to that instead).NewReturnKind, andnewaccept onlyNewReturnKind(and notNormalReturnKind),self.newmethods have return kindNewReturnKindinstead ofNormalReturnKind, andinitializemethods haveNewReturnKindfor all post-update accesses toself(and still haveNormalReturnKindfor normal returns, in case someone were to invokeinitializedirectly).This means that if we have
then
ywill flow into the parameterxofinitalize, and from there to(post-update) self [field]. And since(post-update) selfhasNewReturnKind, we will get flow back out toC.new(y) [field].