Skip to content

WIP Instance methods level control#1027

Open
jeroenvandijk wants to merge 4 commits intobabashka:masterfrom
jeroenvandijk:instance-methods-control-exp
Open

WIP Instance methods level control#1027
jeroenvandijk wants to merge 4 commits intobabashka:masterfrom
jeroenvandijk:instance-methods-control-exp

Conversation

@jeroenvandijk
Copy link
Contributor

@jeroenvandijk jeroenvandijk commented Feb 21, 2026

Please answer the following questions and leave the below in as part of your PR.


Sci already offers control over what functions can and can't be called, and each function can get a custom implementation. For interop there is no such feature yet. Right now, you can either call an instance method as per the original implementation or you can't (configured via :classes). Ideally, there would be a middle ground where you can offer an alternative implementation to an otherwise potentially harmful method, or in some cases deny certain methods entirely.

This is a first take at adding method level based control to Sci. It should allow to give access to functionality of configured classes while blocking other undesirable functionality, e.g. think access control of a filesystem.

The implementation in this PR allows to add a custom implementation of a method (as a function) and other methods can be denied.

E.g.

     (testing "single method override"
       (let [config {:classes {'java.lang.String
                               {:class java.lang.String
                                :instance-methods {'toString
                                                   (fn [_s]
                                                     :dude)}}}}]
         (is (= :dude (tu/eval* "(.toString \"your name\")" config)))
         (is (= 9 (tu/eval* "(.length \"your name\")" config)))))

     (testing "single method allowed"
       (let [config {:classes {'java.lang.String
                               {:class java.lang.String
                                :instance-methods {:deny true
                                                   'toString
                                                   (fn [_s]
                                                     :dude)}}}}]
         (is (= :dude (tu/eval* "(.toString \"your name\")" config)))
         (is (thrown-with-msg? Exception #"allowed"
                               (tu/eval* "(.length \"your name\")" config)))))))

I believe the overhead per method invocation of this feature to traditional paths is limited to:

(if-let [instance-methods (:instance-methods class-config)]
  <NEW-CODE>
  <OLD-CODE>)

The option :allow is checked first and if true, the rest is ignored and interop is applied directly (like before), so no overhead in that case.

The option :deny in :instance-methods could be extended to static methods, static fields and instance fields.

(some-> x :field) expands to (if (nil? x) nil (:field x)) so adds some unnecessary overhead when fields are never nil
Does not interfere with :allow :all

E.g.

(sci/eval-string "(.toString \"your name\")"
                 {:classes {'java.lang.String 
                            {:class java.lang.String
                             :instance-methods {'toString
                                                (fn [_s] 
                                                  :dude)}}}})
@jeroenvandijk
Copy link
Contributor Author

This approach also works nicely with the new 1.12+ way of referring to an instance method:

'java.io.BufferedReader {:class java.io.BufferedReader 
                         :instance-methods 
                         {:deny true
                          'close java.io.BufferedReader/.close}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant