|
41 | 41 | import java.util.concurrent.TimeUnit; |
42 | 42 | import java.util.concurrent.locks.Lock; |
43 | 43 | import java.util.concurrent.locks.ReentrantLock; |
| 44 | +import java.util.function.Consumer; |
44 | 45 | import java.util.function.Function; |
45 | 46 | import java.util.function.Supplier; |
46 | 47 | import java.util.stream.Collectors; |
@@ -210,131 +211,19 @@ private void handleProbesChanges(ConfigurationComparer changes, Configuration ne |
210 | 211 | } |
211 | 212 | List<Class<?>> changedClasses = |
212 | 213 | finder.getAllLoadedChangedClasses(instrumentation.getAllLoadedClasses(), changes); |
213 | | - changedClasses = detectMethodParameters(changes, changedClasses); |
214 | | - changedClasses = detectRecordWithTypeAnnotation(changes, changedClasses); |
| 214 | + changedClasses = |
| 215 | + JDKVersionSpecificHelper.detectMethodParameters( |
| 216 | + errorMsg -> reportError(changes, errorMsg), instrumentation, changedClasses); |
| 217 | + changedClasses = |
| 218 | + JDKVersionSpecificHelper.detectRecordWithTypeAnnotation( |
| 219 | + errorMsg -> reportError(changes, errorMsg), changedClasses); |
215 | 220 | retransformClasses(changedClasses); |
216 | 221 | // ensures that we have at least re-transformed 1 class |
217 | 222 | if (changedClasses.size() > 0) { |
218 | 223 | LOGGER.debug("Re-transformation done"); |
219 | 224 | } |
220 | 225 | } |
221 | 226 |
|
222 | | - /* |
223 | | - * Because of this bug (https://bugs.openjdk.org/browse/JDK-8240908), classes compiled with |
224 | | - * method parameters (javac -parameters) strip this attribute once retransformed |
225 | | - * Spring 6/Spring boot 3 rely exclusively on this attribute and may throw an exception |
226 | | - * if no attribute found. |
227 | | - */ |
228 | | - private List<Class<?>> detectMethodParameters( |
229 | | - ConfigurationComparer changes, List<Class<?>> changedClasses) { |
230 | | - if (JAVA_AT_LEAST_19) { |
231 | | - // bug is fixed since JDK19, no need to perform detection |
232 | | - return changedClasses; |
233 | | - } |
234 | | - List<Class<?>> result = new ArrayList<>(); |
235 | | - for (Class<?> changedClass : changedClasses) { |
236 | | - boolean addClass = true; |
237 | | - try { |
238 | | - Method[] declaredMethods = changedClass.getDeclaredMethods(); |
239 | | - // capping scanning of methods to 100 to avoid generated class with thousand of methods |
240 | | - // assuming that in those first 100 methods there is at least one with at least one |
241 | | - // parameter |
242 | | - for (int methodIdx = 0; |
243 | | - methodIdx < declaredMethods.length && methodIdx < 100; |
244 | | - methodIdx++) { |
245 | | - Method method = declaredMethods[methodIdx]; |
246 | | - Parameter[] parameters = method.getParameters(); |
247 | | - if (parameters.length == 0) { |
248 | | - continue; |
249 | | - } |
250 | | - if (parameters[0].isNamePresent()) { |
251 | | - if (!SpringHelper.isSpringUsingOnlyMethodParameters(instrumentation)) { |
252 | | - return changedClasses; |
253 | | - } |
254 | | - LOGGER.debug( |
255 | | - "Detecting method parameter: method={} param={}, Skipping retransforming this class", |
256 | | - method.getName(), |
257 | | - parameters[0].getName()); |
258 | | - // skip the class: compiled with -parameters |
259 | | - reportError( |
260 | | - changes, |
261 | | - "Method Parameters detected, instrumentation not supported for " |
262 | | - + changedClass.getTypeName()); |
263 | | - addClass = false; |
264 | | - } |
265 | | - // we found at leat a method with one parameter if name is not present we can stop there |
266 | | - break; |
267 | | - } |
268 | | - } catch (Exception e) { |
269 | | - LOGGER.debug("Exception scanning method parameters", e); |
270 | | - } |
271 | | - if (addClass) { |
272 | | - result.add(changedClass); |
273 | | - } |
274 | | - } |
275 | | - return result; |
276 | | - } |
277 | | - |
278 | | - private List<Class<?>> detectRecordWithTypeAnnotation( |
279 | | - ConfigurationComparer changes, List<Class<?>> changedClasses) { |
280 | | - if (!JAVA_AT_LEAST_16) { |
281 | | - // records introduced in JDK 16 (final version) |
282 | | - return changedClasses; |
283 | | - } |
284 | | - List<Class<?>> result = new ArrayList<>(); |
285 | | - for (Class<?> changedClass : changedClasses) { |
286 | | - boolean addClass = true; |
287 | | - try { |
288 | | - if (changedClass.getSuperclass() != null |
289 | | - && changedClass.getSuperclass().getTypeName().equals("java.lang.Record") |
290 | | - && Modifier.isFinal(changedClass.getModifiers())) { |
291 | | - if (hasTypeAnnotationOnRecordComponent(changedClass)) { |
292 | | - LOGGER.debug( |
293 | | - "Record with type annotation detected, instrumentation not supported for {}", |
294 | | - changedClass.getTypeName()); |
295 | | - reportError( |
296 | | - changes, |
297 | | - "Record with type annotation detected, instrumentation not supported for " |
298 | | - + changedClass.getTypeName()); |
299 | | - addClass = false; |
300 | | - } |
301 | | - } |
302 | | - } catch (Exception e) { |
303 | | - LOGGER.debug("Exception detecting record with type annotation", e); |
304 | | - } |
305 | | - if (addClass) { |
306 | | - result.add(changedClass); |
307 | | - } |
308 | | - } |
309 | | - return result; |
310 | | - } |
311 | | - |
312 | | - private boolean hasTypeAnnotationOnRecordComponent(Class<?> recordClass) { |
313 | | - if (GET_RECORD_COMPONENTS_METHOD == null || GET_ANNOTATED_TYPES_METHOD == null) { |
314 | | - return false; |
315 | | - } |
316 | | - try { |
317 | | - Object recordComponentsArray = GET_RECORD_COMPONENTS_METHOD.invoke(recordClass); |
318 | | - int len = Array.getLength(recordComponentsArray); |
319 | | - for (int i = 0; i < len; i++) { |
320 | | - Object recordComponent = Array.get(recordComponentsArray, i); |
321 | | - AnnotatedType annotatedType = |
322 | | - (AnnotatedType) GET_ANNOTATED_TYPES_METHOD.invoke(recordComponent); |
323 | | - for (Annotation annotation : annotatedType.getAnnotations()) { |
324 | | - Target annotationTarget = annotation.annotationType().getAnnotation(Target.class); |
325 | | - if (annotationTarget != null |
326 | | - && Arrays.stream(annotationTarget.value()) |
327 | | - .anyMatch(it -> it == ElementType.TYPE_USE)) { |
328 | | - return true; |
329 | | - } |
330 | | - } |
331 | | - } |
332 | | - return false; |
333 | | - } catch (Exception ex) { |
334 | | - return false; |
335 | | - } |
336 | | - } |
337 | | - |
338 | 227 | private void reportReceived(ConfigurationComparer changes) { |
339 | 228 | for (ProbeDefinition def : changes.getAddedDefinitions()) { |
340 | 229 | if (def instanceof ExceptionProbe) { |
@@ -461,4 +350,123 @@ Map<String, ProbeDefinition> getAppliedDefinitions() { |
461 | 350 | Map<String, InstrumentationResult> getInstrumentationResults() { |
462 | 351 | return instrumentationResults; |
463 | 352 | } |
| 353 | + |
| 354 | + private static class JDKVersionSpecificHelper { |
| 355 | + |
| 356 | + public static List<Class<?>> detectRecordWithTypeAnnotation( |
| 357 | + Consumer<String> reportError, List<Class<?>> changedClasses) { |
| 358 | + if (!JAVA_AT_LEAST_16) { |
| 359 | + // records introduced in JDK 16 (final version) |
| 360 | + return changedClasses; |
| 361 | + } |
| 362 | + List<Class<?>> result = new ArrayList<>(); |
| 363 | + for (Class<?> changedClass : changedClasses) { |
| 364 | + boolean addClass = true; |
| 365 | + try { |
| 366 | + if (changedClass.getSuperclass() != null |
| 367 | + && changedClass.getSuperclass().getTypeName().equals("java.lang.Record") |
| 368 | + && Modifier.isFinal(changedClass.getModifiers())) { |
| 369 | + if (hasTypeAnnotationOnRecordComponent(changedClass)) { |
| 370 | + LOGGER.debug( |
| 371 | + "Record with type annotation detected, instrumentation not supported for {}", |
| 372 | + changedClass.getTypeName()); |
| 373 | + reportError.accept( |
| 374 | + "Record with type annotation detected, instrumentation not supported for " |
| 375 | + + changedClass.getTypeName()); |
| 376 | + addClass = false; |
| 377 | + } |
| 378 | + } |
| 379 | + } catch (Exception e) { |
| 380 | + LOGGER.debug("Exception detecting record with type annotation", e); |
| 381 | + } |
| 382 | + if (addClass) { |
| 383 | + result.add(changedClass); |
| 384 | + } |
| 385 | + } |
| 386 | + return result; |
| 387 | + } |
| 388 | + |
| 389 | + private static boolean hasTypeAnnotationOnRecordComponent(Class<?> recordClass) { |
| 390 | + if (GET_RECORD_COMPONENTS_METHOD == null || GET_ANNOTATED_TYPES_METHOD == null) { |
| 391 | + return false; |
| 392 | + } |
| 393 | + try { |
| 394 | + Object recordComponentsArray = GET_RECORD_COMPONENTS_METHOD.invoke(recordClass); |
| 395 | + int len = Array.getLength(recordComponentsArray); |
| 396 | + for (int i = 0; i < len; i++) { |
| 397 | + Object recordComponent = Array.get(recordComponentsArray, i); |
| 398 | + AnnotatedType annotatedType = |
| 399 | + (AnnotatedType) GET_ANNOTATED_TYPES_METHOD.invoke(recordComponent); |
| 400 | + for (Annotation annotation : annotatedType.getAnnotations()) { |
| 401 | + Target annotationTarget = annotation.annotationType().getAnnotation(Target.class); |
| 402 | + if (annotationTarget != null |
| 403 | + && Arrays.stream(annotationTarget.value()) |
| 404 | + .anyMatch(it -> it == ElementType.TYPE_USE)) { |
| 405 | + return true; |
| 406 | + } |
| 407 | + } |
| 408 | + } |
| 409 | + return false; |
| 410 | + } catch (Exception ex) { |
| 411 | + return false; |
| 412 | + } |
| 413 | + } |
| 414 | + |
| 415 | + /* |
| 416 | + * Because of this bug (https://bugs.openjdk.org/browse/JDK-8240908), classes compiled with |
| 417 | + * method parameters (javac -parameters) strip this attribute once retransformed |
| 418 | + * Spring 6/Spring boot 3 rely exclusively on this attribute and may throw an exception |
| 419 | + * if no attribute found. |
| 420 | + */ |
| 421 | + public static List<Class<?>> detectMethodParameters( |
| 422 | + Consumer<String> reportError, |
| 423 | + Instrumentation instrumentation, |
| 424 | + List<Class<?>> changedClasses) { |
| 425 | + if (JAVA_AT_LEAST_19) { |
| 426 | + // bug is fixed since JDK19, no need to perform detection |
| 427 | + return changedClasses; |
| 428 | + } |
| 429 | + List<Class<?>> result = new ArrayList<>(); |
| 430 | + for (Class<?> changedClass : changedClasses) { |
| 431 | + boolean addClass = true; |
| 432 | + try { |
| 433 | + Method[] declaredMethods = changedClass.getDeclaredMethods(); |
| 434 | + // capping scanning of methods to 100 to avoid generated class with thousand of methods |
| 435 | + // assuming that in those first 100 methods there is at least one with at least one |
| 436 | + // parameter |
| 437 | + for (int methodIdx = 0; |
| 438 | + methodIdx < declaredMethods.length && methodIdx < 100; |
| 439 | + methodIdx++) { |
| 440 | + Method method = declaredMethods[methodIdx]; |
| 441 | + Parameter[] parameters = method.getParameters(); |
| 442 | + if (parameters.length == 0) { |
| 443 | + continue; |
| 444 | + } |
| 445 | + if (parameters[0].isNamePresent()) { |
| 446 | + if (!SpringHelper.isSpringUsingOnlyMethodParameters(instrumentation)) { |
| 447 | + return changedClasses; |
| 448 | + } |
| 449 | + LOGGER.debug( |
| 450 | + "Detecting method parameter: method={} param={}, Skipping retransforming this class", |
| 451 | + method.getName(), |
| 452 | + parameters[0].getName()); |
| 453 | + // skip the class: compiled with -parameters |
| 454 | + reportError.accept( |
| 455 | + "Method Parameters detected, instrumentation not supported for " |
| 456 | + + changedClass.getTypeName()); |
| 457 | + addClass = false; |
| 458 | + } |
| 459 | + // we found at leat a method with one parameter if name is not present we can stop there |
| 460 | + break; |
| 461 | + } |
| 462 | + } catch (Exception e) { |
| 463 | + LOGGER.debug("Exception scanning method parameters", e); |
| 464 | + } |
| 465 | + if (addClass) { |
| 466 | + result.add(changedClass); |
| 467 | + } |
| 468 | + } |
| 469 | + return result; |
| 470 | + } |
| 471 | + } |
464 | 472 | } |
0 commit comments