Skip to content

Ruby: handle private module methods #7340

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

Merged
merged 5 commits into from
Dec 15, 2021
Merged

Ruby: handle private module methods #7340

merged 5 commits into from
Dec 15, 2021

Conversation

hmac
Copy link
Contributor

@hmac hmac commented Dec 9, 2021

Extend our modelling of private methods in two ways:

  1. Extend private modelling to uses in modules, as well as classes.
  2. Model private_class_method, which makes class methods (def self.foo) private.

@github-actions github-actions bot added the Ruby label Dec 9, 2021
@hmac hmac force-pushed the hmac/private-methods branch 2 times, most recently from 3c63de1 to 0644d79 Compare December 9, 2021 05:12
`private` can be used in both classes and modules.
@hmac hmac force-pushed the hmac/private-methods branch from 0644d79 to 5b99580 Compare December 9, 2021 05:13
`Module#private_class_method` takes a symbol representing the name of a
method in the current module scope and makes that module private. This
is similar to `private`, but applies only to class (singleton) methods.
Unlike `private`, it must be called with an argument, and does not
change the ambient visibility for any subsequent method definitions.

    class Foo
      def public
      end

      def private1
      end
      private_class_method :private1

      # This alternate form works because method definition
      # returns its name as a symbol:

      private_class_method def private2
      end
    end
@hmac hmac force-pushed the hmac/private-methods branch from 5b99580 to 8df5aaa Compare December 9, 2021 05:15
At the same time, rename some classes in `private.rb` so they don't
interact with identically-named modules in `calls.rb`.
@hmac hmac force-pushed the hmac/private-methods branch from 8421f05 to 6223b16 Compare December 13, 2021 03:24
@hmac hmac marked this pull request as ready for review December 13, 2021 03:56
@hmac hmac requested a review from a team as a code owner December 13, 2021 03:56
alexrford
alexrford previously approved these changes Dec 13, 2021
Copy link
Contributor

@alexrford alexrford left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a nice improvement 👍

exists(Namespace n |
n.getAStmt() = this and
n.getAStmt() = result and
result.getName() = this.getMethodArgument().(StringlikeLiteral).getValueText()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a join that is bound to go wrong (i.e., joining all string literals and methods that have matching values/names). The old isRef/isDeclaredIn approach avoided this by making sure that we always join on both name and declaring class/module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch - I'll see if I can resurrect the old approach whilst keeping these improvements.

this = c.getStmt(j) and
j > i
)
any(Private p).modifiesMethod(this.getEnclosingModule(), this.getName())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can still join on just this.getName(), so it is better to add a top-level predicate

pragma[noinline]
private predicate isDeclaredIn(MethodBase m, Namespace n, string name) {
  n = m.getEnclosingModule() and
  name = m.getName()
}

and then change the above to

exists(Namespace n, string name |
  any(Private p).modifiesMethod(n, name) and
  isDeclaredIn(this, n, name)
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting! That seems to work, and from some rough testing it also looks faster as well. But I don't really understand why it is faster. Why is it that the previous version can join on just this.getName(), rather than this.getName() and this.getEnclosingModule()? Are there some docs that I could read to better understand this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code above involves three relations (modifiesMethod, getEnclosingModule, and getName), so the join-orderer has to pick one of the 6 possible join-orders. The join-order modifiesMethod <-> getName <-> getEnclosingModule is the one we want to avoid. By folding getEnclosingModule and getName into a separate predicate, we are effectively making sure that modifiesMethod joins on the two relations simultaneously, and there is only one possible (good) join-order.

You can try to look at the Datalog Intermediate Language (DIL) code before and after (right-click a query run in VS Code and select "View DIL").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah that makes sense! Had I thought about it a bit harder I probably should have guessed something like that 🤔 . Thanks for the explanation.

@@ -203,7 +204,9 @@ class SingletonMethod extends MethodBase, TSingletonMethod {
* end
* ```
*/
override predicate isPrivate() { this = any(PrivateClassMethod p).getMethod() }
override predicate isPrivate() {
any(PrivateClassMethod p).modifiesMethod(this.getEnclosingModule(), this.getName())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

@hmac hmac merged commit 062f7fe into main Dec 15, 2021
@hmac hmac deleted the hmac/private-methods branch December 15, 2021 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants