Commit 49410a8c authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch 'ee-42125-extend-override-check-to-also-check-arity' into 'master'

EE: Resolve "Extend `override` check to also check arity"

See merge request gitlab-org/gitlab-ee!8960
parents 377e2875 7f4374a7
...@@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone ...@@ -5,7 +5,6 @@ class DashboardGroupMilestone < GlobalMilestone
attr_reader :group_name attr_reader :group_name
override :initialize
def initialize(milestone) def initialize(milestone)
super(milestone.title, Array(milestone)) super(milestone.title, Array(milestone))
......
---
title: Extend override check to also check arity
merge_request: 23498
author: Jacopo Beschi @jacopo-beschi
type: added
...@@ -6,7 +6,7 @@ module EE ...@@ -6,7 +6,7 @@ module EE
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :sign_in_and_redirect override :sign_in_and_redirect
def sign_in_and_redirect(user) def sign_in_and_redirect(user, *args)
# The counter gets incremented in `sign_in_and_redirect` # The counter gets incremented in `sign_in_and_redirect`
show_ldap_sync_flash if user.sign_in_count == 0 show_ldap_sync_flash if user.sign_in_count == 0
......
...@@ -4,16 +4,11 @@ module Gitlab ...@@ -4,16 +4,11 @@ module Gitlab
module Utils module Utils
module Override module Override
class Extension class Extension
def self.verify_class!(klass, method_name) def self.verify_class!(klass, method_name, arity)
instance_method_defined?(klass, method_name) || extension = new(klass)
raise( parents = extension.parents_for(klass)
NotImplementedError.new( extension.verify_method!(
"#{klass}\##{method_name} doesn't exist!")) klass: klass, parents: parents, method_name: method_name, sub_method_arity: arity)
end
def self.instance_method_defined?(klass, name, include_super: true)
klass.instance_methods(include_super).include?(name) ||
klass.private_instance_methods(include_super).include?(name)
end end
attr_reader :subject attr_reader :subject
...@@ -22,35 +17,77 @@ module Gitlab ...@@ -22,35 +17,77 @@ module Gitlab
@subject = subject @subject = subject
end end
def add_method_name(method_name) def parents_for(klass)
method_names << method_name index = klass.ancestors.index(subject)
end klass.ancestors.drop(index + 1)
def add_class(klass)
classes << klass
end end
def verify! def verify!
classes.each do |klass| classes.each do |klass|
index = klass.ancestors.index(subject) parents = parents_for(klass)
parents = klass.ancestors.drop(index + 1)
method_names.each_pair do |method_name, arity|
method_names.each do |method_name| verify_method!(
parents.any? do |parent| klass: klass,
self.class.instance_method_defined?( parents: parents,
parent, method_name, include_super: false) method_name: method_name,
end || sub_method_arity: arity)
raise(
NotImplementedError.new(
"#{klass}\##{method_name} doesn't exist!"))
end end
end end
end end
def verify_method!(klass:, parents:, method_name:, sub_method_arity:)
overridden_parent = parents.find do |parent|
instance_method_defined?(parent, method_name)
end
raise NotImplementedError.new("#{klass}\##{method_name} doesn't exist!") unless overridden_parent
super_method_arity = find_direct_method(overridden_parent, method_name).arity
unless arity_compatible?(sub_method_arity, super_method_arity)
raise NotImplementedError.new("#{subject}\##{method_name} has arity of #{sub_method_arity}, but #{overridden_parent}\##{method_name} has arity of #{super_method_arity}")
end
end
def add_method_name(method_name, arity = nil)
method_names[method_name] = arity
end
def add_class(klass)
classes << klass
end
def verify_override?(method_name)
method_names.has_key?(method_name)
end
private private
def instance_method_defined?(klass, name)
klass.instance_methods(false).include?(name) ||
klass.private_instance_methods(false).include?(name)
end
def find_direct_method(klass, name)
method = klass.instance_method(name)
method = method.super_method until method && klass == method.owner
method
end
def arity_compatible?(sub_method_arity, super_method_arity)
if sub_method_arity >= 0 && super_method_arity >= 0
# Regular arguments
sub_method_arity == super_method_arity
else
# It's too complex to check this case, just allow sub-method having negative arity
# But we don't allow sub_method_arity > 0 yet super_method_arity < 0
sub_method_arity < 0
end
end
def method_names def method_names
@method_names ||= [] @method_names ||= {}
end end
def classes def classes
...@@ -80,11 +117,21 @@ module Gitlab ...@@ -80,11 +117,21 @@ module Gitlab
def override(method_name) def override(method_name)
return unless ENV['STATIC_VERIFICATION'] return unless ENV['STATIC_VERIFICATION']
Override.extensions[self] ||= Extension.new(self)
Override.extensions[self].add_method_name(method_name)
end
def method_added(method_name)
super
return unless ENV['STATIC_VERIFICATION']
return unless Override.extensions[self]&.verify_override?(method_name)
method_arity = instance_method(method_name).arity
if is_a?(Class) if is_a?(Class)
Extension.verify_class!(self, method_name) Extension.verify_class!(self, method_name, method_arity)
else # We delay the check for modules else # We delay the check for modules
Override.extensions[self] ||= Extension.new(self) Override.extensions[self].add_method_name(method_name, method_arity)
Override.extensions[self].add_method_name(method_name)
end end
end end
......
...@@ -25,11 +25,21 @@ describe Gitlab::Utils::Override do ...@@ -25,11 +25,21 @@ describe Gitlab::Utils::Override do
let(:klass) { subject } let(:klass) { subject }
def good(mod) def good(mod, bad_arity: false, negative_arity: false)
mod.module_eval do mod.module_eval do
override :good override :good
def good
super.succ if bad_arity
def good(num)
end
elsif negative_arity
def good(*args)
super.succ
end
else
def good
super.succ
end
end end
end end
...@@ -56,6 +66,14 @@ describe Gitlab::Utils::Override do ...@@ -56,6 +66,14 @@ describe Gitlab::Utils::Override do
described_class.verify! described_class.verify!
end end
it 'checks ok for overriding method using negative arity' do
good(subject, negative_arity: true)
result = instance.good
expect(result).to eq(1)
described_class.verify!
end
it 'raises NotImplementedError when it is not overriding anything' do it 'raises NotImplementedError when it is not overriding anything' do
expect do expect do
bad(subject) bad(subject)
...@@ -63,6 +81,14 @@ describe Gitlab::Utils::Override do ...@@ -63,6 +81,14 @@ describe Gitlab::Utils::Override do
described_class.verify! described_class.verify!
end.to raise_error(NotImplementedError) end.to raise_error(NotImplementedError)
end end
it 'raises NotImplementedError when overriding a method with different arity' do
expect do
good(subject, bad_arity: true)
instance.good(1)
described_class.verify!
end.to raise_error(NotImplementedError)
end
end end
shared_examples 'checking as intended, nothing was overridden' do shared_examples 'checking as intended, nothing was overridden' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment