contrast-agent 3.8.5 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
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;