Skip to content

Commit

Permalink
CLJ-1973: sort proxy methods for reproducibility
Browse files Browse the repository at this point in the history
Comparator sorts methods by arity then name/param-types/return-types on clashes.

Signed-off-by: Alex Miller <alex.miller@cognitect.com>
  • Loading branch information
frenchy64 authored and puredanger committed Feb 2, 2022
1 parent 8957a93 commit 658693f
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 7 deletions.
1 change: 1 addition & 0 deletions build.xml
Expand Up @@ -105,6 +105,7 @@
<!--<sysproperty key="clojure.compiler.disable-locals-clearing" value="true"/>-->
<sysproperty key="clojure.compiler.direct-linking" value="${directlinking}"/>
<arg value="clojure.test-clojure.protocols.examples"/>
<arg value="clojure.test-clojure.proxy.examples"/>
<arg value="clojure.test-clojure.genclass.examples"/>
<arg value="clojure.test-clojure.compilation.load-ns"/>
<arg value="clojure.test-clojure.annotations"/>
Expand Down
15 changes: 10 additions & 5 deletions src/clj/clojure/core_proxy.clj
Expand Up @@ -241,12 +241,17 @@
mb (map #(vector (%1 %2) (vals (dissoc %1 %2))) mgroups rtypes)
bridge? (reduce1 into1 #{} (map second mb))
ifaces-meths (remove bridge? (vals ifaces-meths))
mm (remove bridge? (vals mm))]
mm (remove bridge? (vals mm))
reflect-Method-keyfn (fn [meth]
(let [[name param-types ^Class return-type] (method-sig meth)]
(-> [name]
(into1 (map #(.getName ^Class %) param-types))
(conj (.getName return-type)))))]
;add methods matching supers', if no mapping -> call super
(doseq [[^java.lang.reflect.Method dest bridges] mb
^java.lang.reflect.Method meth bridges]
(doseq [[^java.lang.reflect.Method dest bridges] (sort-by (comp reflect-Method-keyfn first) mb)
^java.lang.reflect.Method meth (sort-by reflect-Method-keyfn bridges)]
(gen-bridge meth dest))
(doseq [^java.lang.reflect.Method meth mm]
(doseq [^java.lang.reflect.Method meth (sort-by reflect-Method-keyfn mm)]
(gen-method meth
(fn [^GeneratorAdapter gen ^Method m]
(. gen (loadThis))
Expand All @@ -259,7 +264,7 @@
(. m (getDescriptor)))))))

;add methods matching interfaces', if no mapping -> throw
(doseq [^java.lang.reflect.Method meth ifaces-meths]
(doseq [^java.lang.reflect.Method meth (sort-by reflect-Method-keyfn ifaces-meths)]
(gen-method meth
(fn [^GeneratorAdapter gen ^Method m]
(. gen (throwException ex-type (. m (getName))))))))
Expand Down
38 changes: 36 additions & 2 deletions test/clojure/test_clojure/java_interop.clj
Expand Up @@ -11,8 +11,11 @@

(ns clojure.test-clojure.java-interop
(:use clojure.test)
(:require [clojure.inspector]
[clojure.set :as set])
(:require [clojure.data :as data]
[clojure.inspector]
[clojure.pprint :as pp]
[clojure.set :as set]
[clojure.test-clojure.proxy.examples :as proxy-examples])
(:import java.util.Base64
(java.util.concurrent.atomic AtomicLong AtomicInteger)))

Expand Down Expand Up @@ -176,6 +179,37 @@
str)
"chain chain chain")))

;https://clojure.atlassian.net/browse/CLJ-1973
(deftest test-proxy-method-order
(let [class-reader (clojure.asm.ClassReader. proxy-examples/proxy1-class-name)
method-order (atom [])
method-visitor (proxy [clojure.asm.ClassVisitor] [clojure.asm.Opcodes/ASM4 nil]
(visitMethod [access name descriptor signature exceptions]
(swap! method-order conj {:name name :descriptor descriptor})
nil))
_ (.accept class-reader method-visitor 0)
expected [{:name "<init>", :descriptor "()V"}
{:name "__initClojureFnMappings", :descriptor "(Lclojure/lang/IPersistentMap;)V"}
{:name "__updateClojureFnMappings", :descriptor "(Lclojure/lang/IPersistentMap;)V"}
{:name "__getClojureFnMappings", :descriptor "()Lclojure/lang/IPersistentMap;"}
{:name "clone", :descriptor "()Ljava/lang/Object;"}
{:name "hashCode", :descriptor "()I"}
{:name "toString", :descriptor "()Ljava/lang/String;"}
{:name "equals", :descriptor "(Ljava/lang/Object;)Z"}
{:name "a", :descriptor "(Ljava/io/File;)Z"}
{:name "a", :descriptor "(Ljava/lang/Boolean;)Ljava/lang/Object;"}
{:name "a", :descriptor "(Ljava/lang/Runnable;)Z"}
{:name "a", :descriptor "(Ljava/lang/String;)I"}
{:name "b", :descriptor "(Ljava/lang/String;)Ljava/lang/Object;"}
{:name "c", :descriptor "(Ljava/lang/String;)Ljava/lang/Object;"}
{:name "d", :descriptor "(Ljava/lang/String;)Ljava/lang/Object;"}
{:name "a", :descriptor "(Ljava/lang/Boolean;Ljava/lang/String;)I"}
{:name "a", :descriptor "(Ljava/lang/String;Ljava/io/File;)Z"}
{:name "a", :descriptor "(Ljava/lang/String;Ljava/lang/Runnable;)Z"}
{:name "a", :descriptor "(Ljava/lang/String;Ljava/lang/String;)I"}]
actual @method-order]
(is (= expected actual)
(with-out-str (pp/pprint (data/diff expected actual))))))

;; serialized-proxy can be regenerated using a modified version of
;; Clojure with the proxy serialization prohibition disabled and the
Expand Down
30 changes: 30 additions & 0 deletions test/clojure/test_clojure/proxy/examples.clj
@@ -0,0 +1,30 @@
; Copyright (c) Rich Hickey. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.

(ns ^{:doc "Test proxy classes that are AOT-compiled for the tests in
clojure.test-clojure.java-interop."
:author "Ambrose Bonnaire-Sergeant"}
clojure.test-clojure.proxy.examples)

(definterface A
(^int a [^String x])
(^boolean a [^java.io.File x])
(^boolean a [^Runnable x])
(a [^Boolean x])
(^int a [^Boolean x ^String y])
(^int a [^String x ^String y])
(^boolean a [^String x ^java.io.File y])
(^boolean a [^String x ^Runnable y])
(b [^String x])
(c [^String x])
(d [^String x]))

(def ^String proxy1-class-name
(-> (proxy [A] [])
class
.getName))

0 comments on commit 658693f

Please sign in to comment.