# Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true require 'fiber' require 'monitor' require 'contrast/agent/scope' # This is the Scope component. # # It tracks /Contrast/ scope. That is, "are we currently doing assess or protect stuff within a patched method?" -- # this is how we avoid doing Contrast stuff on Contrast code or creating infinite loops -- or "are we in some other # execution context for which we need to special case?". module Contrast module Components module Scope # :nodoc: MONITOR = Monitor.new EXECUTION_CONTEXT = {} # rubocop:disable Style/MutableConstant class Interface # :nodoc: def initialize # This is probably redundant with #scope_for_current_ec's nil check. EXECUTION_CONTEXT[Fiber.current] = Contrast::Agent::Scope.new end # This returns the scope governing the current execution context. Use this sparingly, preferring the instance # & class methods to access and query scope, rather than interacting with the scope object directly. def scope_for_current_ec MONITOR.synchronize do return EXECUTION_CONTEXT[Fiber.current] ||= Contrast::Agent::Scope.new end end end module InstanceMethods # :nodoc: # For each instance method on a scope, define a forwarder to the scope on the current execution context's scope. def scope_for_current_ec MONITOR.synchronize do return EXECUTION_CONTEXT[Fiber.current] ||= Contrast::Agent::Scope.new end end def enter_contrast_scope! scope_for_current_ec.enter_contrast_scope! end def enter_deserialization_scope! scope_for_current_ec.enter_deserialization_scope! end def enter_split_scope! scope_for_current_ec.enter_split_scope! end def enter_scope! name scope_for_current_ec.enter_scope! name end def exit_contrast_scope! scope_for_current_ec.exit_contrast_scope! end def exit_deserialization_scope! scope_for_current_ec.exit_deserialization_scope! end def exit_split_scope! scope_for_current_ec.exit_split_scope! end def exit_scope! name scope_for_current_ec.exit_scope! name end def in_contrast_scope? scope_for_current_ec.in_contrast_scope? end def in_deserialization_scope? scope_for_current_ec.in_deserialization_scope? end def in_split_scope? scope_for_current_ec.in_split_scope? end def split_scope_depth scope_for_current_ec.split_scope_depth end def in_scope? name scope_for_current_ec.in_scope? name end def with_contrast_scope scope_for_current_ec.enter_contrast_scope! yield ensure scope_for_current_ec.exit_contrast_scope! end def with_deserialization_scope scope_for_current_ec.enter_deserialization_scope! yield ensure scope_for_current_ec.exit_deserialization_scope! end def with_split_scope scope_for_current_ec.enter_split_scope! yield ensure scope_for_current_ec.exit_split_scope! end end def self.sweep_dead_ecs # TODO: RUBY-534, #sweep_dead_ecs compensates for a lack of weak tables. when we can use WeakRef, we should # investigate removing this call and instead use the WeakRef for the Execution Context's Keys or using our # Finalizers Hash for Fibers MONITOR.synchronize do EXECUTION_CONTEXT.delete_if do |ec, _scope| !ec.alive? end end end ClassMethods = InstanceMethods end end end