contrast-agent 3.8.5 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_array/cs__assess_array.c +1 -1
  3. data/ext/cs__assess_module/cs__assess_module.c +0 -1
  4. data/ext/cs__assess_yield_track/cs__assess_yield_track.c +34 -0
  5. data/ext/cs__assess_yield_track/cs__assess_yield_track.h +12 -0
  6. data/ext/{cs__scope → cs__assess_yield_track}/extconf.rb +0 -0
  7. data/ext/cs__common/cs__common.c +6 -6
  8. data/ext/cs__common/cs__common.h +3 -1
  9. data/ext/cs__contrast_patch/cs__contrast_patch.c +142 -119
  10. data/ext/cs__contrast_patch/cs__contrast_patch.h +3 -0
  11. data/funchook/autom4te.cache/requests +48 -48
  12. data/funchook/config.log +2 -2
  13. data/lib/contrast/agent.rb +15 -5
  14. data/lib/contrast/agent/assess.rb +0 -1
  15. data/lib/contrast/agent/assess/contrast_event.rb +9 -8
  16. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +68 -18
  17. data/lib/contrast/agent/assess/policy/policy.rb +0 -14
  18. data/lib/contrast/agent/assess/policy/policy_scanner.rb +1 -1
  19. data/lib/contrast/agent/assess/policy/preshift.rb +1 -1
  20. data/lib/contrast/agent/assess/policy/propagation_method.rb +4 -2
  21. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  22. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -1
  23. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -2
  24. data/lib/contrast/agent/assess/policy/propagator/split.rb +166 -1
  25. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +1 -0
  26. data/lib/contrast/agent/assess/policy/source_method.rb +199 -140
  27. data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +30 -0
  28. data/lib/contrast/agent/assess/policy/source_validation/source_validation.rb +36 -0
  29. data/lib/contrast/agent/assess/policy/trigger_method.rb +238 -153
  30. data/lib/contrast/agent/assess/policy/trigger_node.rb +54 -9
  31. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +13 -0
  32. data/lib/contrast/agent/assess/properties.rb +29 -0
  33. data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +35 -31
  34. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +1 -1
  35. data/lib/contrast/agent/class_reopener.rb +98 -55
  36. data/lib/contrast/agent/feature_state.rb +1 -1
  37. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  38. data/lib/contrast/agent/logger_manager.rb +2 -2
  39. data/lib/contrast/agent/middleware.rb +1 -3
  40. data/lib/contrast/agent/patching/policy/after_load_patch.rb +40 -4
  41. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +33 -8
  42. data/lib/contrast/agent/patching/policy/method_policy.rb +20 -7
  43. data/lib/contrast/agent/patching/policy/patch.rb +54 -23
  44. data/lib/contrast/agent/patching/policy/patch_status.rb +0 -2
  45. data/lib/contrast/agent/patching/policy/patcher.rb +10 -11
  46. data/lib/contrast/agent/patching/policy/policy.rb +4 -0
  47. data/lib/contrast/agent/patching/policy/policy_node.rb +14 -1
  48. data/lib/contrast/agent/patching/policy/trigger_node.rb +2 -1
  49. data/lib/contrast/agent/protect/policy/policy.rb +6 -6
  50. data/lib/contrast/agent/protect/rule/base.rb +1 -1
  51. data/lib/contrast/agent/protect/rule/deserialization.rb +3 -25
  52. data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
  53. data/lib/contrast/agent/railtie.rb +11 -5
  54. data/lib/contrast/agent/request.rb +1 -19
  55. data/lib/contrast/agent/request_context.rb +1 -1
  56. data/lib/contrast/agent/rewriter.rb +4 -3
  57. data/lib/contrast/agent/scope.rb +116 -19
  58. data/lib/contrast/agent/service_heartbeat.rb +5 -2
  59. data/lib/contrast/agent/settings_state.rb +12 -8
  60. data/lib/contrast/agent/version.rb +1 -1
  61. data/lib/contrast/api.rb +1 -0
  62. data/lib/contrast/api/speedracer.rb +2 -2
  63. data/lib/contrast/components/agent.rb +26 -7
  64. data/lib/contrast/components/app_context.rb +8 -45
  65. data/lib/contrast/components/contrast_service.rb +3 -4
  66. data/lib/contrast/components/interface.rb +1 -1
  67. data/lib/contrast/components/scope.rb +56 -26
  68. data/lib/contrast/config/ruby_configuration.rb +8 -3
  69. data/lib/contrast/delegators.rb +9 -0
  70. data/lib/contrast/delegators/application_update.rb +32 -0
  71. data/lib/contrast/extensions/framework/rack/cookie.rb +24 -0
  72. data/lib/contrast/extensions/framework/rack/request.rb +24 -0
  73. data/lib/contrast/extensions/framework/rack/response.rb +23 -0
  74. data/lib/contrast/extensions/framework/rails/action_controller_railties_helper_inherited.rb +20 -0
  75. data/lib/contrast/extensions/framework/rails/active_record.rb +26 -0
  76. data/lib/contrast/extensions/framework/rails/active_record_named.rb +53 -0
  77. data/lib/contrast/extensions/framework/rails/active_record_time_zone_inherited.rb +21 -0
  78. data/lib/contrast/extensions/framework/rails/buffer.rb +28 -0
  79. data/lib/contrast/extensions/framework/rails/configuration.rb +27 -0
  80. data/lib/contrast/extensions/framework/sinatra/base.rb +59 -0
  81. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess.rb +12 -11
  82. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/array.rb +4 -3
  83. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/assess_extension.rb +0 -2
  84. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/basic_object.rb +1 -1
  85. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/erb.rb +0 -0
  86. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/exec_trigger.rb +0 -0
  87. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/fiber.rb +3 -4
  88. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/hash.rb +0 -0
  89. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/kernel.rb +1 -1
  90. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/module.rb +1 -1
  91. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/regexp.rb +0 -0
  92. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/string.rb +0 -0
  93. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/tilt_template_trigger.rb +0 -0
  94. data/lib/contrast/extensions/ruby_core/assess/xpath_library_trigger.rb +40 -0
  95. data/lib/contrast/{core_extensions → extensions/ruby_core}/delegator.rb +0 -0
  96. data/lib/contrast/{core_extensions → extensions/ruby_core}/eval_trigger.rb +1 -1
  97. data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory.rb +0 -0
  98. data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory/datastores.rb +1 -1
  99. data/lib/contrast/extensions/ruby_core/module.rb +17 -0
  100. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect.rb +0 -0
  101. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_command_injection_rule.rb +8 -6
  102. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_deserialization_rule.rb +7 -5
  103. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_no_sqli_rule.rb +5 -3
  104. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_path_traversal_rule.rb +31 -27
  105. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_sqli_rule.rb +5 -3
  106. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_xxe_rule.rb +9 -7
  107. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/kernel.rb +0 -0
  108. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/psych.rb +1 -1
  109. data/lib/contrast/{core_extensions → extensions/ruby_core}/thread.rb +0 -0
  110. data/lib/contrast/framework/base_support.rb +63 -0
  111. data/lib/contrast/framework/manager.rb +109 -0
  112. data/lib/contrast/framework/platform_version.rb +21 -0
  113. data/lib/contrast/framework/rails_support.rb +88 -0
  114. data/lib/contrast/framework/sinatra_application_helper.rb +49 -0
  115. data/lib/contrast/framework/sinatra_support.rb +94 -0
  116. data/lib/contrast/framework/view_technologies_descriptor.rb +20 -0
  117. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  118. data/lib/contrast/utils/class_util.rb +92 -37
  119. data/lib/contrast/utils/duck_utils.rb +59 -39
  120. data/lib/contrast/utils/environment_util.rb +5 -75
  121. data/lib/contrast/utils/freeze_util.rb +3 -7
  122. data/lib/contrast/utils/invalid_configuration_util.rb +5 -5
  123. data/lib/contrast/utils/job_servers_running.rb +39 -0
  124. data/lib/contrast/utils/ruby_ast_rewriter.rb +2 -2
  125. data/lib/contrast/utils/service_response_util.rb +0 -6
  126. data/lib/contrast/utils/sinatra_helper.rb +6 -0
  127. data/lib/contrast/utils/stack_trace_utils.rb +1 -1
  128. data/resources/assess/policy.json +74 -23
  129. data/resources/inventory/policy.json +1 -1
  130. data/resources/protect/policy.json +11 -9
  131. data/resources/rubocops/object/frozen_cop.rb +1 -1
  132. data/ruby-agent.gemspec +2 -0
  133. data/service_executables/VERSION +1 -1
  134. data/service_executables/linux/contrast-service +0 -0
  135. data/service_executables/mac/contrast-service +0 -0
  136. metadata +94 -57
  137. data/ext/cs__scope/cs__scope.c +0 -96
  138. data/ext/cs__scope/cs__scope.h +0 -33
  139. data/lib/contrast/agent/assess/class_reverter.rb +0 -82
  140. data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +0 -28
  141. data/lib/contrast/core_extensions/module.rb +0 -42
  142. data/lib/contrast/core_extensions/object.rb +0 -27
  143. data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +0 -48
  144. data/lib/contrast/rails_extensions/assess/active_record.rb +0 -32
  145. data/lib/contrast/rails_extensions/assess/active_record_named.rb +0 -61
  146. data/lib/contrast/rails_extensions/assess/configuration.rb +0 -26
  147. data/lib/contrast/rails_extensions/buffer.rb +0 -30
  148. data/lib/contrast/rails_extensions/rack.rb +0 -45
  149. data/lib/contrast/sinatra_extensions/assess/cookie.rb +0 -26
  150. data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +0 -59
  151. data/lib/contrast/utils/operating_environment.rb +0 -38
  152. data/lib/contrast/utils/path_util.rb +0 -151
  153. data/lib/contrast/utils/scope_util.rb +0 -99
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b789de55c662b3c95a253bac0ab731c931c0d97dad6fe7c6f827c40a17d64ed7
4
- data.tar.gz: b7589a20f7d89191ca3b1633a93204fb7195f802272f2701b8d17553104979ae
3
+ metadata.gz: cc76e7cc9a47ace8f43df83fca283ee52dc8f4eb48bee2bca660981616c43124
4
+ data.tar.gz: c07ab0d747605fa69730f4409543ccd9ce7be5624f5d959ec27bd1dd227551db
5
5
  SHA512:
6
- metadata.gz: '00693f21684374256997ada3d2a40889c288e116931662126ab7305dd2504c5fbb5d6657acd03c215181fcc839950b5b7a3ceccb7f1a347d2b91e7ae8556cf3b'
7
- data.tar.gz: d44389e25b56d8da40e9b9de424ac9189c5d5929d252e2e082d09643dcf79b9d65b5b2179524a09cc9ab0978c5158eea02bcb3d042df12dea2e09b4bdf99a7b2
6
+ metadata.gz: 609dd7c247d30d0ec1a7567736d3d5377a422c1ba7e52ae9c52409a192e2f90f5054ef4f34b35e39d78bf5d05197449e791f2af5dc333b4d164fa9ccbfc015c1
7
+ data.tar.gz: c5336c941c6aece474c548549587e93fe61561ce8d4ef0576d5aefe875b0895a9f8e5c26afe2b41912f638eab9976c9357e268eee84c7cd6d4145aa507deaa86
@@ -30,7 +30,7 @@ static VALUE contrast_assess_array_join(const int argc, const VALUE *argv,
30
30
 
31
31
  void Init_cs__assess_array(void) {
32
32
  rb_sym_assess_array_join = rb_intern("cs__patched_join");
33
- rb_sym_assess_track_array_join = rb_intern("__cs_track_join");
33
+ rb_sym_assess_track_array_join = rb_intern("cs__track_join");
34
34
 
35
35
  VALUE array_class = rb_define_class("Array", rb_cObject);
36
36
  contrast_alias_method(array_class, "cs__patched_join", "join");
@@ -61,7 +61,6 @@ contrast_assess_module_module_eval(const int argc, const VALUE *argv,
61
61
  void Init_cs__assess_module(void) {
62
62
  rb_sym_assess_patch_eval = rb_intern("patch_assess_on_eval");
63
63
 
64
- VALUE assess_policy = rb_define_module_under(assess, "Policy");
65
64
  assess_patcher = rb_define_module_under(assess_policy, "Patcher");
66
65
 
67
66
  trigger_check_method = rb_intern("eval_trigger_check");
@@ -0,0 +1,34 @@
1
+ /* Copyright (c) 2020 Contrast Security, Inc. See
2
+ * https://www.contrastsecurity.com/enduser-terms-0317a for more details. */
3
+
4
+ #include "cs__assess_yield_track.h"
5
+ #include "../cs__common/cs__common.h"
6
+ #include <funchook.h>
7
+ #include <ruby.h>
8
+
9
+ static VALUE rb_yield_hook(VALUE val, const VALUE self) {
10
+ VALUE method = rb_funcall(rb_mKernel, rb_sym_method, 0);
11
+
12
+ if(method == split_method && RB_TYPE_P(val, T_STRING)) {
13
+ rb_funcall(split_class, propagate_yield, 1, val);
14
+ }
15
+ VALUE result = rb_yield_original(val);
16
+ return result;
17
+ }
18
+
19
+ static int install_yield_hooks() {
20
+ funchook_t *funchook = funchook_create();
21
+ rb_yield_original = rb_yield;
22
+ funchook_prepare(funchook, (void **)&rb_yield_original,
23
+ rb_yield_hook);
24
+ funchook_install(funchook, 0);
25
+ return 0;
26
+ }
27
+
28
+ void Init_cs__assess_yield_track(void) {
29
+ VALUE base = rb_define_class_under(assess_propagator, "Base", rb_cObject);
30
+ split_class = rb_define_class_under(assess_propagator, "Split", base);
31
+ propagate_yield = rb_intern("propagate_yield");
32
+ split_method = ID2SYM(rb_intern("split"));
33
+ install_yield_hooks();
34
+ }
@@ -0,0 +1,12 @@
1
+ #include <funchook.h>
2
+ #include <ruby.h>
3
+
4
+ static VALUE split_class;
5
+ static VALUE propagate_yield, split_method;
6
+
7
+ static VALUE *(*rb_yield_original)(VALUE val);
8
+ static VALUE rb_yield_hook(VALUE val, const VALUE self);
9
+
10
+ static int install_yield_hooks();
11
+
12
+ void Init_cs__assess_yield_track(void);
@@ -6,7 +6,9 @@
6
6
 
7
7
  /* Globals */
8
8
  /* These are defined w/ `extern` in the header */
9
- VALUE contrast, agent, patching, policy, assess, core_extensions, core_assess;
9
+ VALUE contrast, agent, patching, policy, assess;
10
+ VALUE core_extensions, core_assess;
11
+ VALUE assess_policy, assess_propagator;
10
12
 
11
13
  VALUE rb_sym_enter_scope;
12
14
  VALUE rb_sym_exit_scope;
@@ -50,11 +52,9 @@ void Init_cs__common(void) {
50
52
  policy = rb_define_module_under(patching, "Policy");
51
53
  patcher = rb_define_module_under(policy, "Patch");
52
54
 
55
+ assess_policy = rb_define_module_under(assess, "Policy");
56
+ assess_propagator = rb_define_module_under(assess_policy, "Propagator");
57
+
53
58
  core_extensions = rb_define_module_under(contrast, "CoreExtensions");
54
59
  core_assess = rb_define_module_under(core_extensions, "Assess");
55
-
56
- /* Ensure ScopeUtil is set on the Contrast patcher */
57
- VALUE utils = rb_define_module_under(contrast, "Utils");
58
- VALUE scopeUtil = rb_define_module_under(utils, "ScopeUtil");
59
- rb_extend_object(patcher, scopeUtil);
60
60
  }
@@ -6,7 +6,9 @@
6
6
  static VALUE cs__send_method;
7
7
  static VALUE cs__alias_method_sym;
8
8
 
9
- extern VALUE contrast, agent, patching, policy, assess, core_extensions, core_assess;
9
+ extern VALUE contrast, agent, patching, policy, assess;
10
+ extern VALUE core_extensions, core_assess;
11
+ extern VALUE assess_policy, assess_propagator;
10
12
 
11
13
  extern VALUE rb_sym_enter_scope;
12
14
  extern VALUE rb_sym_exit_scope;
@@ -28,11 +28,12 @@ VALUE contrast_patch_call_original(const VALUE *args) {
28
28
  int argc;
29
29
  VALUE method, method_id, object;
30
30
  VALUE *params;
31
- object = args[0];
32
- method = args[1];
31
+ argc = NUM2INT(args[0]);
32
+ params = (VALUE *)args[1];
33
+ object = args[2];
34
+ method = args[3];
33
35
  method_id = SYM2ID(method);
34
- argc = NUM2INT(args[2]);
35
- params = (VALUE *)args[3];
36
+
36
37
 
37
38
  /* It looks like we can find the last Ruby block given so long as we don't
38
39
  * change Ruby method scope (always call this function from C, not Ruby),
@@ -94,9 +95,6 @@ VALUE contrast_patch_call_rescue(const VALUE *args) {
94
95
 
95
96
  contrast_call_post_patch(method_policy, preshift, object, Qnil, argc, argv);
96
97
 
97
- /* exit scope */
98
- rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
99
-
100
98
  /* reraise the exception that got us here */
101
99
  rb_exc_raise(exception);
102
100
 
@@ -104,19 +102,119 @@ VALUE contrast_patch_call_rescue(const VALUE *args) {
104
102
  }
105
103
 
106
104
  VALUE contrast_call_super(const VALUE *args) {
107
- int argc = NUM2INT(args[0]);
108
- VALUE *argv = (VALUE *)args[1];
105
+ int argc;
106
+ VALUE *argv;
107
+ argc = NUM2INT(args[0]);
108
+ argv = (VALUE *)args[1];
109
109
 
110
110
  return rb_call_super(argc, argv);
111
111
  }
112
112
 
113
+ VALUE contrast_run_patches(const VALUE *wrapped_args) {
114
+ VALUE impl, method, method_policy, object, original_args, original_ret, preshift, transformed_ret;
115
+ int argc;
116
+ VALUE *argv;
117
+ VALUE rescue_args[6];
118
+
119
+ impl = wrapped_args[0];
120
+ original_args = wrapped_args[1];
121
+ method = wrapped_args[2];
122
+ method_policy = wrapped_args[3];
123
+ object = wrapped_args[4];
124
+ argc = NUM2INT(wrapped_args[5]);
125
+ argv = (VALUE *)wrapped_args[6];
126
+
127
+ rescue_args[0] = object;
128
+ rescue_args[1] = method;
129
+ rescue_args[2] = INT2NUM(argc);
130
+ rescue_args[3] = (VALUE)argv;
131
+ rescue_args[4] = method_policy;
132
+
133
+ /* Tracking, triggering, and propagation here. */
134
+ contrast_call_pre_patch(method_policy, method, object, argc, argv, Qnil);
135
+
136
+ /* Capture pre-call state */
137
+ preshift = build_preshift(method_policy, object, argc, argv);
138
+ rescue_args[5] = preshift;
139
+
140
+ /* We wrap a call to the original method with a rescue block, and we use
141
+ * rb_rescue2 to capture all Exception-inheriting exceptions (and if your
142
+ * software is well-behaved, all exceptions should inherit from Exception.)
143
+ *
144
+ * The rescue block is responsible for doing Contrast post-call analysis
145
+ * in the event the original method has thrown an exception.
146
+ *
147
+ * EDGE CASES:
148
+ * Given how extensively we patch and instrument, this code is
149
+ * prone to some esoteric edge cases that are not well-documented or
150
+ * easy to research.
151
+ *
152
+ * There is an esoteric edge case in core Ruby, upon Thread#kill, where
153
+ * it raises Fixnum 8 (Qnil==8). This is an intentional choice on the
154
+ * part of the core Ruby devs, as blindly rescuing Thread#kill would be
155
+ * disastrous.
156
+ * A consequence of this is that Thread#kill will leak scope, if you
157
+ * happen to ever instrument it.
158
+ *
159
+ * If you are within a catch block, and the original function results
160
+ * in a throw, you will leak scope. We handle this by not instrumenting
161
+ * methods that do that. (Tracked in RUBY-552.)
162
+ *
163
+ * If you're thinking of cleaning this up by using rb_protect,
164
+ * you will catch ALL exceptions, as well as ANYTHING
165
+ * else that unwinds the stack. This includes fiber context switches
166
+ * (which are used to implement Enumerator#next) and catch/throw blocks.
167
+ * I spent a week debugging that so you don't have to. -ajm
168
+ */
169
+
170
+ switch (impl) {
171
+ case IMPL_ALIAS_INSTANCE:
172
+ case IMPL_ALIAS_SINGLETON:
173
+ original_ret = rb_rescue2(
174
+ contrast_patch_call_original, original_args,
175
+ contrast_patch_call_rescue, (VALUE)rescue_args, rb_eException, 0);
176
+ break;
177
+ case IMPL_PREPEND:
178
+ original_ret = rb_rescue2(contrast_call_super, original_args,
179
+ contrast_patch_call_rescue,
180
+ (VALUE)rescue_args, rb_eException, 0);
181
+ break;
182
+ };
183
+
184
+ /* If you're here, the original method did not throw an exception
185
+ * (or unwind the stack otherwise).
186
+ * If the original method threw an exception, contrast_patch_call_rescue
187
+ * re-raises the original exception, which unwinds the stack back to the
188
+ * call site. This means the rest of this function is not executed.
189
+ */
190
+
191
+ /* Invoke Contrast post-call patching.
192
+ * Post-call patching may transform the return value,
193
+ * hence the assignment.
194
+ */
195
+ transformed_ret = contrast_call_post_patch(method_policy, preshift, object,
196
+ original_ret, argc, argv);
197
+
198
+ /* Special case for tracking frozen sources */
199
+ if (transformed_ret != Qnil) {
200
+ return transformed_ret;
201
+ } else {
202
+ return original_ret;
203
+ }
204
+ }
205
+
206
+ VALUE contrast_ensure_function(const VALUE method_policy) {
207
+ /* exit scope */
208
+ rb_funcall(contrast_patcher(), rb_sym_exit_method_scope, 1, method_policy);
209
+ rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
210
+
211
+ return Qnil;
212
+ }
213
+
113
214
  VALUE contrast_patch_dispatch(const int argc, const VALUE *argv,
114
215
  const patch_impl impl, const VALUE object) {
115
- VALUE cs__method, known, method, ret, transformed_ret, original_ret,
116
- method_policy, preshift;
216
+ VALUE cs__method, known, method, method_policy;
117
217
  VALUE original_args[4];
118
- VALUE rescue_args[7];
119
-
120
218
  int do_contrast, nested_scope;
121
219
 
122
220
  /* Do Contrast analysis, unless our subsequent checks tell us no. */
@@ -158,18 +256,6 @@ VALUE contrast_patch_dispatch(const int argc, const VALUE *argv,
158
256
  method_policy = Qnil;
159
257
  }
160
258
 
161
- if (impl == IMPL_ALIAS_INSTANCE || impl == IMPL_ALIAS_SINGLETON) {
162
- /* Alias patching moves the original method to "cs__#{method}" */
163
- cs__method = rb_funcall(known, rb_sym_brackets, 1, INT2NUM(1));
164
-
165
- /* We may not have built the alias yet */
166
- if (!RTEST(cs__method)) {
167
- cs__method =
168
- rb_funcall(contrast_patcher(), rb_sym_build_method_name, 2,
169
- object, method);
170
- }
171
- }
172
-
173
259
  /* Check conditions for not doing Contrast analysis */
174
260
  if (nested_scope) {
175
261
  /* if we were in scope */
@@ -190,112 +276,47 @@ VALUE contrast_patch_dispatch(const int argc, const VALUE *argv,
190
276
  do_contrast = 0;
191
277
  }
192
278
 
193
- switch (impl) {
194
- case IMPL_ALIAS_INSTANCE:
195
- case IMPL_ALIAS_SINGLETON:
196
- original_args[0] = object;
197
- original_args[1] = cs__method;
198
- original_args[2] = INT2NUM(argc);
199
- original_args[3] = (VALUE)argv;
200
- break;
201
- case IMPL_PREPEND:
202
- original_args[0] = INT2NUM(argc);
203
- original_args[1] = (VALUE)argv;
204
- break;
279
+ original_args[0] = INT2NUM(argc);
280
+ original_args[1] = (VALUE)argv;
281
+ original_args[2] = object;
282
+
283
+ if (impl == IMPL_ALIAS_INSTANCE || impl == IMPL_ALIAS_SINGLETON) {
284
+ /* Alias patching moves the original method to "cs__#{method}" */
285
+ cs__method = rb_funcall(known, rb_sym_brackets, 1, INT2NUM(1));
286
+
287
+ /* We may not have built the alias yet */
288
+ if (!RTEST(cs__method)) {
289
+ cs__method =
290
+ rb_funcall(contrast_patcher(), rb_sym_build_method_name, 2,
291
+ object, method);
292
+ }
293
+ original_args[3] = cs__method;
205
294
  }
206
295
 
296
+ /* Enter any scopes specific to method policy */
297
+ rb_funcall(contrast_patcher(), rb_sym_enter_method_scope, 1, method_policy);
298
+
207
299
  /* If we're not doing Contrast analysis, exit scope and treat as normal. */
208
300
  if (!do_contrast) {
209
301
  goto call_original;
210
302
  }
211
303
 
212
- rescue_args[0] = object;
213
- rescue_args[1] = method;
214
- rescue_args[2] = INT2NUM(argc);
215
- rescue_args[3] = (VALUE)argv;
216
- rescue_args[4] = method_policy;
217
-
218
- /* Tracking, triggering, and propagation here. */
219
- contrast_call_pre_patch(method_policy, method, object, argc, argv, Qnil);
304
+ /* Otherwise, invoke Contrast analysis. */
305
+ VALUE wrapped_args[7];
306
+ wrapped_args[0] = impl;
307
+ wrapped_args[1] = (VALUE)original_args;
308
+ wrapped_args[2] = method;
309
+ wrapped_args[3] = method_policy;
310
+ wrapped_args[4] = object;
311
+ wrapped_args[5] = INT2NUM(argc);
312
+ wrapped_args[6] = (VALUE)argv;
220
313
 
221
- /* Capture pre-call state */
222
- preshift = build_preshift(method_policy, object, argc, argv);
223
- rescue_args[5] = preshift;
224
-
225
- /* We wrap a call to the original method with a rescue block, and we use
226
- * rb_rescue2 to capture all Exception-inheriting exceptions (and if your
227
- * software is well-behaved, all exceptions should inherit from Exception.)
228
- *
229
- * The rescue block is responsible for doing Contrast post-call analysis
230
- * in the event the original method has thrown an exception.
231
- *
232
- * EDGE CASES:
233
- * Given how extensively we patch and instrument, this code is
234
- * prone to some esoteric edge cases that are not well-documented or
235
- * easy to research.
236
- *
237
- * There is an esoteric edge case in core Ruby, upon Thread#kill, where
238
- * it raises Fixnum 8 (Qnil==8). This is an intentional choice on the
239
- * part of the core Ruby devs, as blindly rescuing Thread#kill would be
240
- * disastrous.
241
- * A consequence of this is that Thread#kill will leak scope, if you
242
- * happen to ever instrument it.
243
- *
244
- * If you are within a catch block, and the original function results
245
- * in a throw, you will leak scope. We handle this by not instrumenting
246
- * methods that do that. (Tracked in RUBY-552.)
247
- *
248
- * If you're thinking of cleaning this up by using rb_protect,
249
- * you will catch ALL exceptions, as well as ANYTHING
250
- * else that unwinds the stack. This includes fiber context switches
251
- * (which are used to implement Enumerator#next) and catch/throw blocks.
252
- * I spent a week debugging that so you don't have to. -ajm
253
- */
254
-
255
- switch (impl) {
256
- case IMPL_ALIAS_INSTANCE:
257
- case IMPL_ALIAS_SINGLETON:
258
- original_ret = rb_rescue2(
259
- contrast_patch_call_original, (VALUE)original_args,
260
- contrast_patch_call_rescue, (VALUE)rescue_args, rb_eException, 0);
261
- break;
262
- case IMPL_PREPEND:
263
- original_ret = rb_rescue2(contrast_call_super, (VALUE)original_args,
264
- contrast_patch_call_rescue,
265
- (VALUE)rescue_args, rb_eException, 0);
266
- break;
267
- };
268
-
269
- /* If you're here, the original method did not throw an exception
270
- * (or unwind the stack otherwise).
271
- * If the original method threw an exception, contrast_patch_call_rescue
272
- * re-raises the original exception, which unwinds the stack back to the
273
- * call site. This means the rest of this function is not executed.
274
- */
275
-
276
- /* Invoke Contrast post-call patching.
277
- * Post-call patching may transform the return value,
278
- * hence the assignment.
279
- */
280
- transformed_ret = contrast_call_post_patch(method_policy, preshift, object,
281
- original_ret, argc, argv);
282
-
283
- /* Special case for tracking frozen sources */
284
- if (transformed_ret != Qnil) {
285
- ret = transformed_ret;
286
- } else {
287
- ret = original_ret;
288
- }
289
-
290
- /* exit scope */
291
- rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
292
-
293
- return ret;
314
+ return rb_ensure(contrast_run_patches, (VALUE)wrapped_args, contrast_ensure_function, method_policy);
294
315
 
295
316
  call_original:
296
317
 
297
318
  /* exit scope */
298
- rb_funcall(contrast_patcher(), rb_sym_exit_scope, 0);
319
+ contrast_ensure_function(method_policy);
299
320
 
300
321
  switch (impl) {
301
322
  case IMPL_ALIAS_INSTANCE:
@@ -430,6 +451,8 @@ void Init_cs__contrast_patch(void) {
430
451
  rb_sym_instance_method = rb_intern("instance_method");
431
452
  rb_sym_cs_singleton_class = rb_intern("cs__singleton_class");
432
453
 
454
+ rb_sym_enter_method_scope = rb_intern("enter_method_scope!");
455
+ rb_sym_exit_method_scope = rb_intern("exit_method_scope!");
433
456
 
434
457
  rb_define_module_function(contrast_patcher(), "contrast_define_method",
435
458
  contrast_patch_define_method, 3);
@@ -23,6 +23,9 @@ static VALUE rb_sym_cs_to_s;
23
23
 
24
24
  static VALUE rb_sym_in_request_context;
25
25
 
26
+ static VALUE rb_sym_enter_method_scope;
27
+ static VALUE rb_sym_exit_method_scope;
28
+
26
29
  static VALUE rb_sym_build_method_name;
27
30
  static VALUE rb_sym_info_for;
28
31
  static VALUE rb_sym_propagation_node;