├── .gitignore
├── LICENCE.txt
├── README.md
├── agent
├── pom.xml
└── src
│ └── main
│ └── java
│ └── io
│ └── type
│ └── pollution
│ └── agent
│ ├── Agent.java
│ ├── AppendOnlyList.java
│ ├── ByteBuddyUtils.java
│ └── TraceInstanceOf.java
├── benchmarks
├── .gitignore
├── WHATIF.md
├── images
│ └── async
│ │ ├── async-profiler-lines.html
│ │ ├── async-profiler-lines.png
│ │ ├── async-profiler.html
│ │ └── async-profiler.png
├── pom.xml
├── reports
│ ├── async-profiler.jfr
│ ├── hotspot.log
│ └── perfasm.txt
├── src
│ └── main
│ │ └── java
│ │ └── io
│ │ └── type
│ │ └── pollution
│ │ └── benchmarks
│ │ ├── Context.java
│ │ ├── ContextUtil.java
│ │ ├── DuplicatedContext.java
│ │ ├── InternalContext.java
│ │ ├── Main.java
│ │ ├── NonDuplicatedContext.java
│ │ └── RequireNonNullCheckcastScalability.java
└── tools
│ └── perfasm.jar
├── example
├── pom.xml
└── src
│ └── main
│ └── java
│ └── io
│ └── type
│ └── pollution
│ └── example
│ └── Main.java
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project exclude paths
2 | /agent/target/
3 | /example/target/
4 | /.idea/
5 | /agent/type-pollution-agent.iml
6 | /example/type-pollution-example.iml
7 | /type-pollution-parent.iml
8 |
--------------------------------------------------------------------------------
/LICENCE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java agent to diagnose [JDK-8180450](https://bugs.openjdk.org/browse/JDK-8180450)
2 |
3 | ## What is JDK-8180450?
4 |
5 | See [the issue](https://bugs.openjdk.org/browse/JDK-8180450) for full details...
6 | But briefly, the problem is that the OpenJDK JVM caches the supertype of a class/interface on the JRE's `Klass` structure when evaluating things like `instanceof` or checking type casts and while that usually results in a performance win (because a slower traversal of the whole hierarchy can be avoided), in certain circumstances involving multiple inheritance of interfaces the cache is worse than useless.
7 | CPU will be expended updating the cache, but the cached value won't help avoiding the slow path.
8 | It's a problem because the surprising and significant difference in performance between the ideal and worst case behaviours means programmers can easily write poorly performing code without realising the costs (_usually_ `instanceof` is very cheap, for example).
9 |
10 | ## What is this?
11 |
12 | It's a Java agent which can be used to identify code which may be suffering from this problem.
13 | It attempts to count the number of times an a particular concrete class is used in `instanceof` (and similar) expressions with a different test type.
14 | For example at one point in the code you might have `foo instanceof I1` and somewhere else `bar instanceof I2` (where `foo` and `bar` have the same concrete type).
15 | The agent will estimate the number of times the cached supertype (`I1` and `I2`) is changing, i.e. how often you're hitting the slower path.
16 |
17 | ## Build
18 |
19 | ```
20 | $ # From the root dir
21 | $ mvn package
22 | ```
23 |
24 | ## Run
25 |
26 | ```
27 | $ # From the root dir
28 | $ java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar -jar example/target/type-pollution-example-0.1-SNAPSHOT.jar
29 | ```
30 | The output, with a default agent configuration, is:
31 | ```bash
32 | --------------------------
33 | Type Pollution Statistics:
34 | --------------------------
35 | 1: io.type.pollution.example.B
36 | Count: 6477753
37 | Types:
38 | io.type.pollution.example.I2
39 | io.type.pollution.example.I3
40 | io.type.pollution.example.I1
41 | Traces:
42 | io.type.pollution.example.Main.goo(Main.java:74)
43 | class: io.type.pollution.example.I3
44 | count: 1826427
45 | class: io.type.pollution.example.I2
46 | count: 1430748
47 | io.type.pollution.example.Main.foo(Main.java:69)
48 | class: io.type.pollution.example.I1
49 | count: 1803371
50 | io.type.pollution.example.Main$$Lambda$ByteBuddy$4.accept(Unknown Source)
51 | class: io.type.pollution.example.I2
52 | count: 1417207
53 | --------------------------
54 | 2: io.type.pollution.example.C
55 | Count: 4804011
56 | Types:
57 | io.type.pollution.example.I2
58 | io.type.pollution.example.I1
59 | Traces:
60 | io.type.pollution.example.Main.castToI1(Main.java:85)
61 | class: io.type.pollution.example.I1
62 | count: 2400934
63 | io.type.pollution.example.Main.castToI2(Main.java:89)
64 | class: io.type.pollution.example.I2
65 | count: 1848168
66 | io.type.pollution.example.Main$$Lambda$ByteBuddy$6.accept(Unknown Source)
67 | class: io.type.pollution.example.I2
68 | count: 554909
69 | --------------------------
70 | ```
71 | - **Count**: is the number of observed successful `checkcast`/`instanceof`/`Class::isAssignableFrom`/`Class::cast`/`Class::isInstance`
72 | against a different (interface) type from the last seen
73 | - **Types**: is the list of different (interface) types observed
74 | - **Traces**: is a list of top method stack traces along with the interface *class* seen and related *count* (ie observed
75 | secondary super cache invalidations).
76 |
77 | **note** `Traces` format is compatible with
78 | [Idea IntelliJ Stack Trace Viewer](https://www.jetbrains.com/help/idea/analyzing-external-stacktraces.html)
79 |
80 | The report is an ordered list which types are ordered by increasing `Count` ie
81 | `io.type.pollution.example.B` is the top type based on it and more likely the one with the highest chance
82 | to cause scalability issues.
83 |
84 | ### But what about OpenJDK JIT optimizations that could save any check to be performed?
85 |
86 | Due to the complexities of JIT mechanism (Inlining heuristic, method and bytecode Type Profiling, etc etc)
87 | the agent cannot detect if a potential treat translates into a real (scalability) performance issue:
88 | the smart and wise user should create system/[JMH](https://github.com/openjdk/jmh) benchmarks to analyze the
89 | results of changes and narrow the scope of the agent to ignore the innocent traces.
90 |
91 | To narrow the scope of the tracing, users can add a comma separated list of **package prefixes** (not full qualified class names!!!)
92 | to the agent configuration and the agent will place tracing around the usual suspects byte-code instructions just on these:
93 |
94 | eg
95 | ```
96 | $ java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar=io.other -jar example/target/type-pollution-example-0.1-SNAPSHOT.jar
97 |
98 | # IT WOULD PRINT...NOTHING! No packages starting with io.other exists :P
99 | ```
100 |
101 | Said that, cleaning up every `instanceof/checkcast` misuses is the best way to get rid
102 | of any potential scalability issue, without relying on how JIT optimizes specific tests.
103 |
104 | ### Full stack traces are available?
105 |
106 | Yes!
107 | To enable full-stack trace sampling **for each** type check execution, use:
108 | > **WARNING**: this can have a large performance impact!!
109 | ```
110 | -Dio.type.pollution.full.traces=true
111 | ```
112 |
113 | And, the output will become:
114 | ```bash
115 | --------------------------
116 | Type Pollution Statistics:
117 | --------------------------
118 | 1: io.type.pollution.example.B
119 | Count: 6477753
120 | Types:
121 | io.type.pollution.example.I2
122 | io.type.pollution.example.I3
123 | io.type.pollution.example.I1
124 | Traces:
125 | io.type.pollution.example.Main.goo(Main.java:74)
126 | class: io.type.pollution.example.I3
127 | count: 1826427
128 | class: io.type.pollution.example.I2
129 | count: 1430748
130 | io.type.pollution.example.Main.foo(Main.java:69)
131 | class: io.type.pollution.example.I1
132 | count: 1803371
133 | io.type.pollution.example.Main$$Lambda$ByteBuddy$4.accept(Unknown Source)
134 | class: io.type.pollution.example.I2
135 | count: 1417207
136 | Full Traces:
137 | --------------------------
138 | io.type.pollution.example.Main.goo(Main.java:74)
139 | io.type.pollution.example.Main.lambda$main$0(Main.java:57)
140 | io.type.pollution.example.Main$$Lambda$ByteBuddy$1/0x00000008001bf840.run(Unknown Source)
141 | java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
142 | java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
143 | java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
144 | java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
145 | java.base/java.lang.Thread.run(Thread.java:829)
146 | --------------------------
147 | io.type.pollution.example.Main$$Lambda$ByteBuddy$4/0x00000008001be440.accept(Unknown Source)
148 | io.type.pollution.example.Main.consumeAsI2(Main.java:81)
149 | io.type.pollution.example.Main.lambda$main$0(Main.java:58)
150 | io.type.pollution.example.Main$$Lambda$ByteBuddy$1/0x00000008001bf840.run(Unknown Source)
151 | java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
152 | java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
153 | java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
154 | java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
155 | java.base/java.lang.Thread.run(Thread.java:829)
156 | --------------------------
157 | io.type.pollution.example.Main.foo(Main.java:69)
158 | io.type.pollution.example.Main.lambda$main$0(Main.java:56)
159 | io.type.pollution.example.Main$$Lambda$ByteBuddy$1/0x00000008001bf840.run(Unknown Source)
160 | java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
161 | java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
162 | java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
163 | java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
164 | java.base/java.lang.Thread.run(Thread.java:829)
165 | --------------------------
166 | # Rest omitted for brevity....
167 | ```
168 | To reduce the performance impact due to collecting full stack traces for each execution, consider adding too
169 | ```
170 | -Dio.type.pollution.full.traces.ms=10
171 | ```
172 | that's going to perform stack sampling with a global tick time of 10 milliseconds: tune it to match your perf requirement
173 | and your type of load.
174 |
175 |
--------------------------------------------------------------------------------
/agent/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | io.forked.franz
8 | type-pollution-parent
9 | 0.1-SNAPSHOT
10 |
11 |
12 | type-pollution-agent
13 | 0.1-SNAPSHOT
14 | jar
15 |
16 |
17 |
18 | net.bytebuddy
19 | byte-buddy
20 | 1.12.13
21 |
22 |
23 |
24 |
25 |
26 |
27 | maven-jar-plugin
28 | 3.2.2
29 |
30 |
31 |
32 | io.type.pollution.agent.Agent
33 | true
34 |
35 |
36 |
37 |
38 |
39 |
40 | maven-shade-plugin
41 | 3.3.0
42 |
43 |
44 | package
45 |
46 | shade
47 |
48 |
49 |
50 |
51 | net.bytebuddy
52 | agent.net.bytebuddy
53 |
54 |
55 | false
56 |
57 |
58 |
59 |
60 |
61 | org.apache.maven.plugins
62 | maven-compiler-plugin
63 |
64 | 9
65 | 9
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/agent/src/main/java/io/type/pollution/agent/Agent.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.agent;
2 |
3 | import net.bytebuddy.agent.builder.AgentBuilder;
4 | import net.bytebuddy.asm.AsmVisitorWrapper;
5 | import net.bytebuddy.asm.TypeConstantAdjustment;
6 | import net.bytebuddy.description.field.FieldDescription;
7 | import net.bytebuddy.description.field.FieldList;
8 | import net.bytebuddy.description.method.MethodList;
9 | import net.bytebuddy.description.type.TypeDescription;
10 | import net.bytebuddy.implementation.Implementation;
11 | import net.bytebuddy.jar.asm.ClassWriter;
12 | import net.bytebuddy.matcher.ElementMatcher;
13 | import net.bytebuddy.pool.TypePool;
14 |
15 | import java.io.IOException;
16 | import java.lang.instrument.Instrumentation;
17 | import java.nio.ByteBuffer;
18 | import java.nio.channels.FileChannel;
19 | import java.nio.file.Paths;
20 | import java.nio.file.StandardOpenOption;
21 | import java.time.LocalDateTime;
22 | import java.time.format.DateTimeFormatter;
23 | import java.util.Collection;
24 | import java.util.concurrent.Executors;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | import static net.bytebuddy.matcher.ElementMatchers.*;
28 |
29 | public class Agent {
30 |
31 | private static final boolean ENABLE_FULL_STACK_TRACES = Boolean.getBoolean("io.type.pollution.full.traces");
32 |
33 | private static final String FILE_DUMP = System.getProperty("io.type.pollution.file");
34 |
35 | private static final int FULL_STACK_TRACES_SAMPLING_PERIOD_MS = Integer.getInteger("io.type.pollution.full.traces.ms", 0);
36 | static final int FULL_STACK_TRACES_LIMIT = Integer.getInteger("io.type.pollution.full.traces.limit", 20);
37 | private static final int TYPE_UPDATE_COUNT_MIN = Integer.getInteger("io.type.pollution.count.min", 10);
38 | private static final int TYPE_MISS_COUNT_MIN = Integer.getInteger("io.type.pollution.miss.count.min", 1);
39 | private static final int TRACING_DELAY_SECS = Integer.getInteger("io.type.pollution.delay", 0);
40 | private static final Long REPORT_INTERVAL_SECS = Long.getLong("io.type.pollution.report.interval");
41 | private static final boolean ENABLE_LAMBDA_INSTRUMENTATION = Boolean.getBoolean("io.type.pollution.lambda");
42 |
43 | public static void premain(String agentArgs, Instrumentation inst) {
44 | if (ENABLE_FULL_STACK_TRACES) {
45 | TraceInstanceOf.startMetronome(FULL_STACK_TRACES_SAMPLING_PERIOD_MS);
46 | }
47 | TraceInstanceOf.startTracing(TRACING_DELAY_SECS);
48 |
49 | if (REPORT_INTERVAL_SECS != null) {
50 | Executors.newSingleThreadScheduledExecutor(r -> {
51 | Thread t = new Thread(r);
52 | t.setDaemon(true);
53 | t.setName("type-pollution-periodic-report");
54 | return t;
55 | }).scheduleWithFixedDelay(Agent::printLiveReport, TRACING_DELAY_SECS + REPORT_INTERVAL_SECS, REPORT_INTERVAL_SECS, TimeUnit.SECONDS);
56 | }
57 |
58 | Runtime.getRuntime().addShutdownHook(new Thread(Agent::printFinalReport));
59 |
60 | final String[] agentArgsValues = agentArgs == null ? null : agentArgs.split(",");
61 | ElementMatcher.Junction super TypeDescription> acceptedTypes = any();
62 | if (agentArgsValues != null && agentArgsValues.length > 0) {
63 | for (String startWith : agentArgsValues)
64 | acceptedTypes = acceptedTypes.and(nameStartsWith(startWith));
65 | }
66 | new AgentBuilder.Default()
67 | .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
68 | .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
69 | .with(ENABLE_LAMBDA_INSTRUMENTATION ?
70 | AgentBuilder.LambdaInstrumentationStrategy.ENABLED :
71 | AgentBuilder.LambdaInstrumentationStrategy.DISABLED)
72 | .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
73 | .type(acceptedTypes
74 | .and(not(nameStartsWith("net.bytebuddy.")))
75 | .and(not(nameStartsWith("com.sun")))
76 | .and(not(nameStartsWith("io.type.pollution.agent"))))
77 | .transform((builder,
78 | typeDescription,
79 | classLoader,
80 | module,
81 | protectionDomain) -> builder
82 | .visit(TypeConstantAdjustment.INSTANCE)
83 | .visit(new AsmVisitorWrapper.AbstractBase() {
84 |
85 | @Override
86 | public int mergeWriter(int flags) {
87 | return flags | ClassWriter.COMPUTE_FRAMES;
88 | }
89 |
90 | @Override
91 | public int mergeReader(int flags) {
92 | return flags;
93 | }
94 |
95 | @Override
96 | public net.bytebuddy.jar.asm.ClassVisitor wrap(TypeDescription instrumentedType,
97 | net.bytebuddy.jar.asm.ClassVisitor classVisitor,
98 | Implementation.Context implementationContext,
99 | TypePool typePool,
100 | FieldList fields,
101 | MethodList> methods,
102 | int writerFlags, int readerFlags) {
103 | return new ByteBuddyUtils.ByteBuddyTypePollutionClassVisitor(net.bytebuddy.jar.asm.Opcodes.ASM9, classVisitor);
104 | }
105 | })).installOn(inst);
106 | }
107 |
108 | private static void printFinalReport() {
109 | printReport(true);
110 | }
111 |
112 | private static void printLiveReport() {
113 | printReport(false);
114 | }
115 |
116 | private static final DateTimeFormatter REPORT_TIMESTAMP = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
117 | private static FileChannel DUMP;
118 | private static ByteBuffer DUMP_TMP_BUFFER;
119 | private static boolean DUMP_ERROR;
120 |
121 | private static boolean LAST_REPORT = false;
122 |
123 | private static CharSequence reportOf(Collection counters) {
124 | if (counters.isEmpty()) {
125 | return "";
126 | }
127 | StringBuilder report = new StringBuilder();
128 | int rowId = 0;
129 | for (TraceInstanceOf.TraceCounter.Snapshot counter : counters) {
130 | report.append("--------------------------\n");
131 | rowId++;
132 | report.append(rowId).append(":\t").append(counter.clazz.getName()).append('\n');
133 | report.append("Count:\t").append(counter.updateCount).append('\n');
134 | report.append("Types:\n");
135 | for (Class> seen : counter.seen) {
136 | report.append("\t").append(seen.getName()).append('\n');
137 | }
138 | report.append("Traces:\n");
139 | for (TraceInstanceOf.TraceCounter.Snapshot.TraceSnapshot stack : counter.traces) {
140 | report.append("\t").append(stack.trace).append('\n');
141 | for (TraceInstanceOf.TraceCounter.Snapshot.TraceSnapshot.ClassCount count : stack.interfaceSeenCounters) {
142 | report.append("\t\tclass: ").append(count.interfaceClazz.getName()).append('\n');
143 | report.append("\t\tcount: ").append(count.count).append('\n');
144 | }
145 | }
146 | if (ENABLE_FULL_STACK_TRACES) {
147 | report.append("Full Traces:\n");
148 | for (StackTraceElement[] fullFrames : counter.fullStackFrames) {
149 | report.append("\t--------------------------\n");
150 | for (StackTraceElement frame : fullFrames) {
151 | report.append("\t").append(frame).append('\n');
152 | }
153 | }
154 | }
155 | }
156 | return report;
157 | }
158 |
159 | private static void printReport(boolean last) {
160 | if (LAST_REPORT) {
161 | return;
162 | }
163 | if (last) {
164 | LAST_REPORT = true;
165 | }
166 | StringBuilder summary = new StringBuilder("--------------------------\nType Check Statistics:\n--------------------------\n");
167 | summary.append("Date:\t").append(REPORT_TIMESTAMP.format(LocalDateTime.now())).append('\n');
168 | summary.append("Last:\t").append(last).append('\n');
169 | CharSequence typePollutionReport = reportOf(TraceInstanceOf.orderedTypePollutionCountersSnapshot(TYPE_UPDATE_COUNT_MIN));
170 | if (typePollutionReport.length() > 0) {
171 | summary.append("--------------------------\nType Pollution:\n");
172 | summary.append(typePollutionReport);
173 | }
174 | CharSequence missReport = reportOf(TraceInstanceOf.orderedMissCountersSnapshot(TYPE_MISS_COUNT_MIN));
175 | if (missReport.length() > 0) {
176 | summary.append("--------------------------\nMiss:\n");
177 | summary.append(missReport);
178 | }
179 | boolean emptyReports = typePollutionReport.length() == 0 && missReport.length() == 0;
180 | if (!emptyReports) {
181 | summary.append("--------------------------\n");
182 | if (DUMP_ERROR || FILE_DUMP == null) {
183 | System.out.println(summary);
184 | return;
185 | }
186 | if (DUMP == null) {
187 | try {
188 | DUMP = FileChannel.open(Paths.get(FILE_DUMP), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
189 | DUMP_TMP_BUFFER = ByteBuffer.allocateDirect(summary.length());
190 | } catch (IOException e) {
191 | System.err.println("ERROR while creating the Type Pollution Statistics dump to " + FILE_DUMP + " due to: " + e);
192 | DUMP_ERROR = true;
193 | DUMP_TMP_BUFFER = null;
194 | DUMP = null;
195 | System.out.println(summary);
196 | return;
197 | }
198 | }
199 | if (DUMP_TMP_BUFFER.capacity() < summary.length()) {
200 | DUMP_TMP_BUFFER = ByteBuffer.allocateDirect(summary.length());
201 | }
202 | DUMP_TMP_BUFFER.clear().limit(summary.length());
203 | // latin encoding rocks for the report, let's keep it simple
204 | for (int i = 0; i < summary.length(); i++) {
205 | DUMP_TMP_BUFFER.put(i, (byte) summary.charAt(i));
206 | }
207 | try {
208 | DUMP.write(DUMP_TMP_BUFFER);
209 | } catch (IOException e) {
210 | System.err.println("ERROR while dumping the Type Pollution Statistics to " + FILE_DUMP + " due to: " + e);
211 | DUMP_ERROR = true;
212 | DUMP_TMP_BUFFER = null;
213 | if (!closeDump()) {
214 | System.out.println(summary);
215 | }
216 | return;
217 | }
218 | if (last) {
219 | if (!closeDump()) {
220 | System.out.println(summary);
221 | }
222 | }
223 | }
224 | }
225 |
226 | private static boolean closeDump() {
227 | try {
228 | DUMP.close();
229 | return true;
230 | } catch (IOException ex) {
231 | DUMP_ERROR = true;
232 | DUMP_TMP_BUFFER = null;
233 | System.err.println("ERROR while closing the Type Pollution Statistics dump on " + FILE_DUMP + " due to: " + ex);
234 | return false;
235 | } finally {
236 | DUMP = null;
237 | }
238 | }
239 |
240 | }
241 |
242 |
--------------------------------------------------------------------------------
/agent/src/main/java/io/type/pollution/agent/AppendOnlyList.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.agent;
2 |
3 | import java.util.concurrent.ConcurrentLinkedQueue;
4 | import java.util.concurrent.atomic.AtomicLong;
5 | import java.util.concurrent.atomic.AtomicReference;
6 | import java.util.concurrent.atomic.AtomicReferenceArray;
7 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
8 | import java.util.function.Consumer;
9 |
10 | /**
11 | * The algorithm of this list is a mix of
12 | * https://github.com/JCTools/JCTools/blob/master/jctools-core/src/main/java/org/jctools/queues/MpscUnboundedXaddArrayQueue.java
13 | * and the enhanced Michael-Scott queue on {@link ConcurrentLinkedQueue}.
14 | */
15 | class AppendOnlyList {
16 | private static final class Chunk extends AtomicReferenceArray {
17 | private static final AtomicReferenceFieldUpdater NEXT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(Chunk.class, Chunk.class, "next");
18 | private Chunk prev;
19 | private long id;
20 | private volatile Chunk next;
21 |
22 | public Chunk(long id) {
23 | this(id, null);
24 | }
25 |
26 | public Chunk(long id, Chunk prev) {
27 | super(CHUNK_SIZE);
28 | this.id = id;
29 | this.prev = prev;
30 | }
31 |
32 | public boolean trySetNext(Chunk next) {
33 | return NEXT_UPDATER.compareAndSet(this, null, next);
34 | }
35 |
36 | }
37 |
38 | // This MUST be a power of 2
39 | private static final int CHUNK_SIZE = 128;
40 | private static final int CHUNK_MASK = CHUNK_SIZE - 1;
41 | private static final int CHUNK_SHIFT = Integer.numberOfTrailingZeros(CHUNK_SIZE);
42 |
43 | private final AtomicLong appenderSequence = new AtomicLong();
44 | private final Chunk firstChunk;
45 | private final AtomicReference lastChunk = new AtomicReference<>();
46 |
47 | public AppendOnlyList() {
48 | firstChunk = new Chunk(0);
49 | lastChunk.lazySet(firstChunk);
50 | }
51 |
52 | private Chunk producerChunkForIndex(final Chunk initialChunk, final long requiredChunkId) {
53 | final AtomicReference lastChunk = this.lastChunk;
54 | Chunk currentChunk = initialChunk;
55 | long jumpBackward;
56 | Chunk tmpChunk = null;
57 | while (true) {
58 | if (currentChunk == null) {
59 | currentChunk = lastChunk.get();
60 | }
61 | final long currentChunkId = currentChunk.id;
62 | // if the required chunk id is less than the current chunk id then we need to walk the linked list of
63 | // chunks back to the required id
64 | jumpBackward = currentChunkId - requiredChunkId;
65 | if (jumpBackward >= 0) {
66 | break;
67 | }
68 | final long nextChunkId = currentChunkId + 1;
69 | Chunk nextChunk;
70 | // fast-path to save allocating a new chunk
71 | if ((nextChunk = currentChunk.next) != null) {
72 | // try help
73 | lastChunk.compareAndSet(currentChunk, nextChunk);
74 | if (requiredChunkId == nextChunkId) {
75 | return nextChunk;
76 | }
77 | currentChunk = null;
78 | } else {
79 | // slow (maybe) allocating path
80 | if (tmpChunk == null) {
81 | tmpChunk = new Chunk(nextChunkId, currentChunk);
82 | } else {
83 | tmpChunk.id = nextChunkId;
84 | tmpChunk.prev = currentChunk;
85 | }
86 | if (currentChunk.trySetNext(tmpChunk)) {
87 | lastChunk.compareAndSet(currentChunk, tmpChunk);
88 | if (requiredChunkId == nextChunkId) {
89 | return tmpChunk;
90 | }
91 | currentChunk = null;
92 | tmpChunk = null;
93 | } else {
94 | // reset it
95 | tmpChunk.prev = null;
96 | // failed to append to next, try help moving lastChunk forward
97 | nextChunk = currentChunk.next;
98 | lastChunk.compareAndSet(currentChunk, nextChunk);
99 | if (requiredChunkId == nextChunkId) {
100 | return nextChunk;
101 | }
102 | currentChunk = null;
103 | }
104 | }
105 | }
106 | for (long i = 0; i < jumpBackward; i++) {
107 | currentChunk = currentChunk.prev;
108 | assert currentChunk != null;
109 | }
110 | return currentChunk;
111 | }
112 |
113 | public void add(E e) {
114 | if (null == e) {
115 | throw new NullPointerException();
116 | }
117 | final long pIndex = appenderSequence.getAndIncrement();
118 |
119 | final int piChunkOffset = (int) (pIndex & CHUNK_MASK);
120 | final long piChunkId = pIndex >> CHUNK_SHIFT;
121 |
122 | Chunk pChunk = lastChunk.get();
123 | if (pChunk.id != piChunkId) {
124 | pChunk = producerChunkForIndex(pChunk, piChunkId);
125 | }
126 | pChunk.lazySet(piChunkOffset, e);
127 | }
128 |
129 | public void forEach(Consumer super E> accept) {
130 | long remaining = appenderSequence.get();
131 | if (remaining == 0) {
132 | return;
133 | }
134 | Chunk currentChunk = firstChunk;
135 | while (true) {
136 | final int batch = (int) Math.min(CHUNK_SIZE, remaining);
137 | for (int i = 0; i < batch; i++) {
138 | Object e;
139 | while ((e = currentChunk.get(i)) == null) {
140 | Thread.onSpinWait();
141 | }
142 | accept.accept((E) e);
143 | }
144 | remaining -= batch;
145 | if (remaining == 0) {
146 | return;
147 | }
148 | Chunk nextChunk = currentChunk.next;
149 | if (nextChunk == null) {
150 | final Chunk newChunk = new Chunk(currentChunk.id + 1, currentChunk);
151 | if (nextChunk.trySetNext(newChunk)) {
152 | nextChunk = newChunk;
153 | } else {
154 | // help GC
155 | newChunk.prev = null;
156 | nextChunk = currentChunk.next;
157 | assert nextChunk != null : "trySetNext can fail just if others succeed";
158 | }
159 | }
160 | currentChunk = nextChunk;
161 | }
162 | }
163 |
164 | public long size() {
165 | return appenderSequence.get();
166 | }
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/agent/src/main/java/io/type/pollution/agent/ByteBuddyUtils.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.agent;
2 |
3 |
4 | import net.bytebuddy.jar.asm.Label;
5 | import net.bytebuddy.jar.asm.Opcodes;
6 | import net.bytebuddy.jar.asm.Type;
7 |
8 | public class ByteBuddyUtils {
9 |
10 | static class ByteBuddyTypePollutionInstructionAdapter extends net.bytebuddy.jar.asm.MethodVisitor {
11 |
12 | private final String classDescriptor;
13 | private final String methodName;
14 |
15 | private final String classFile;
16 |
17 | private String tracePrefix;
18 |
19 | private int line;
20 |
21 | protected ByteBuddyTypePollutionInstructionAdapter(int api, net.bytebuddy.jar.asm.MethodVisitor methodVisitor, String classDescriptor, String methodName, String classFile) {
22 | super(api, methodVisitor);
23 | this.classDescriptor = classDescriptor;
24 | this.methodName = methodName;
25 | this.classFile = classFile;
26 | }
27 |
28 | private String trace() {
29 | if (tracePrefix == null) {
30 | tracePrefix = classDescriptor.replace('/', '.') + "." + methodName + "(" + (classFile != null ? classFile : "Unknown Source)");
31 | }
32 | if (classFile != null) {
33 | return tracePrefix + ":" + line + ")";
34 | } else {
35 | return tracePrefix;
36 | }
37 | }
38 |
39 | @Override
40 | public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface) {
41 | if (opcode == Opcodes.INVOKEVIRTUAL && "java/lang/Class".equals(owner)) {
42 | switch (name) {
43 | case "cast":
44 | mv.visitInsn(Opcodes.DUP2);
45 | mv.visitLdcInsn(trace());
46 | mv.visitMethodInsn(net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC,
47 | Type.getInternalName(TraceInstanceOf.class),
48 | "traceCast",
49 | "(Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/String;)V", false);
50 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
51 | break;
52 | case "isInstance":
53 | mv.visitLdcInsn(trace());
54 | mv.visitMethodInsn(Opcodes.INVOKESTATIC,
55 | Type.getInternalName(TraceInstanceOf.class),
56 | "traceIsInstance",
57 | "(Ljava/lang/Class;Ljava/lang/Object;Ljava/lang/String;)Z", false);
58 | break;
59 | case "isAssignableFrom":
60 | mv.visitInsn(Opcodes.DUP2);
61 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
62 | mv.visitLdcInsn(trace());
63 | mv.visitMethodInsn(net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC,
64 | Type.getInternalName(TraceInstanceOf.class),
65 | "traceIsAssignableFrom",
66 | "(Ljava/lang/Class;Ljava/lang/Class;ZLjava/lang/String;)Z", false);
67 | break;
68 | default:
69 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
70 | }
71 | } else {
72 | super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
73 | }
74 | }
75 |
76 | @Override
77 | public void visitTypeInsn(final int opcode, final String type) {
78 | switch (opcode) {
79 | case Opcodes.CHECKCAST:
80 | checkcast(Type.getObjectType(type));
81 | break;
82 | case Opcodes.INSTANCEOF:
83 | instanceOf(Type.getObjectType(type));
84 | break;
85 | default:
86 | super.visitTypeInsn(opcode, type);
87 | }
88 | }
89 |
90 | @Override
91 | public void visitLineNumber(final int line, final Label start) {
92 | this.line = line;
93 | super.visitLineNumber(line, start);
94 | }
95 |
96 | public void checkcast(final Type type) {
97 | mv.visitInsn(net.bytebuddy.jar.asm.Opcodes.DUP);
98 | mv.visitLdcInsn(type);
99 | mv.visitLdcInsn(trace());
100 | mv.visitMethodInsn(net.bytebuddy.jar.asm.Opcodes.INVOKESTATIC,
101 | Type.getInternalName(TraceInstanceOf.class),
102 | "traceCheckcast",
103 | "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)V", false);
104 | super.visitTypeInsn(net.bytebuddy.jar.asm.Opcodes.CHECKCAST, type.getInternalName());
105 | }
106 |
107 | public void instanceOf(final Type type) {
108 | mv.visitLdcInsn(type);
109 | mv.visitLdcInsn(trace());
110 | mv.visitMethodInsn(Opcodes.INVOKESTATIC,
111 | Type.getInternalName(TraceInstanceOf.class),
112 | "traceInstanceOf",
113 | "(Ljava/lang/Object;Ljava/lang/Class;Ljava/lang/String;)Z", false);
114 | }
115 | }
116 |
117 | static class ByteBuddyTypePollutionClassVisitor extends net.bytebuddy.jar.asm.ClassVisitor {
118 |
119 | private String name;
120 | private String source;
121 |
122 | ByteBuddyTypePollutionClassVisitor(int api, net.bytebuddy.jar.asm.ClassVisitor cv) {
123 | super(api, cv);
124 | }
125 |
126 | @Override
127 | public void visitSource(final String source, final String debug) {
128 | this.source = source;
129 | super.visitSource(source, debug);
130 | }
131 |
132 | @Override
133 | public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
134 | this.name = name;
135 | super.visit(version, access, name, signature, superName, interfaces);
136 | }
137 |
138 | @Override
139 | public net.bytebuddy.jar.asm.MethodVisitor visitMethod(int flags, String name,
140 | String desc, String signature, String[] exceptions) {
141 | return new ByteBuddyTypePollutionInstructionAdapter(api, super.visitMethod(flags, name, desc,
142 | signature, exceptions), this.name, name, source);
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/agent/src/main/java/io/type/pollution/agent/TraceInstanceOf.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.agent;
2 |
3 | import java.util.*;
4 | import java.util.concurrent.ConcurrentHashMap;
5 | import java.util.concurrent.CopyOnWriteArraySet;
6 | import java.util.concurrent.TimeUnit;
7 | import java.util.concurrent.atomic.AtomicBoolean;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 | import java.util.concurrent.atomic.AtomicLongFieldUpdater;
10 | import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
11 |
12 | public class TraceInstanceOf {
13 |
14 | private static volatile long GLOBAL_SAMPLING_TICK = System.nanoTime();
15 | private static final AtomicInteger METRONOME_PERIOD_MS = new AtomicInteger(-1);
16 |
17 | // not ideal perf-wise:
18 | // a good candidate to perform one-shot changing checks is
19 | // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/invoke/SwitchPoint.html
20 | private static final AtomicBoolean TRACING_STARTED = new AtomicBoolean();
21 | private static final Thread METRONOME = new Thread(() -> {
22 | // this isn't supposed to change
23 | final int samplingPeriod = METRONOME_PERIOD_MS.get();
24 | final Thread current = Thread.currentThread();
25 | while (!current.isInterrupted()) {
26 | try {
27 | Thread.sleep(samplingPeriod);
28 | } catch (InterruptedException e) {
29 | // let's stop
30 | return;
31 | }
32 | GLOBAL_SAMPLING_TICK = System.nanoTime();
33 | }
34 |
35 | });
36 |
37 | public static void startTracing(final int seconds) {
38 | if (seconds <= 0) {
39 | startTracing();
40 | } else {
41 | final long start = System.nanoTime();
42 | final Thread startTracingThread = new Thread(() -> {
43 | final long timeToStartThread = System.nanoTime() - start;
44 | final long remaningBeforeStartTracing =
45 | TimeUnit.SECONDS.toNanos(seconds) - timeToStartThread;
46 | if (remaningBeforeStartTracing > 0) {
47 | try {
48 | TimeUnit.NANOSECONDS.sleep(remaningBeforeStartTracing);
49 | startTracing();
50 | } catch (InterruptedException ignore) {
51 | // we're stopping
52 | }
53 | }
54 | });
55 | startTracingThread.setName("start-tracing");
56 | startTracingThread.setDaemon(true);
57 | startTracingThread.start();
58 | }
59 | }
60 |
61 | private static void startTracing() {
62 | TRACING_STARTED.compareAndSet(false, true);
63 | }
64 |
65 | public static void startMetronome(int samplingPeriod) {
66 | if (METRONOME_PERIOD_MS.compareAndSet(-1, samplingPeriod) && samplingPeriod > 0) {
67 | METRONOME.setDaemon(true);
68 | METRONOME.setName("type-pollution-metronome");
69 | METRONOME.start();
70 | }
71 | }
72 |
73 | public static final class MissTraceCounter extends TraceCounter {
74 |
75 |
76 | private MissTraceCounter(Class clazz) {
77 | super(clazz);
78 | }
79 |
80 | public void onTypeCheckMiss(Class interfaceClazz, String trace) {
81 | updateTraceCount(interfaceClazz, trace);
82 | }
83 | }
84 |
85 | public static final class TypePollutionTraceCounter extends TraceCounter {
86 | private static final AtomicReferenceFieldUpdater LAST_SEEN_INTERFACE_UPDATER =
87 | AtomicReferenceFieldUpdater.newUpdater(TypePollutionTraceCounter.class, Class.class, "lastSeenInterface");
88 |
89 | private volatile Class lastSeenInterface = null;
90 |
91 | private TypePollutionTraceCounter(Class clazz) {
92 | super(clazz);
93 | }
94 |
95 | public void onTypeCheckHit(Class interfaceClazz, String trace) {
96 | final Class lastSeen = lastSeenInterface;
97 | if (interfaceClazz.equals(lastSeen)) {
98 | return;
99 | }
100 | // ok to lose some sample
101 | LAST_SEEN_INTERFACE_UPDATER.lazySet(this, interfaceClazz);
102 | if (lastSeen != null) {
103 | updateTraceCount(interfaceClazz, trace);
104 | }
105 | }
106 | }
107 |
108 | public static class TraceCounter {
109 |
110 | private static final class StackTraceArrayList extends ArrayList {
111 |
112 | public StackTraceArrayList(int capacity) {
113 | super(capacity);
114 | }
115 | }
116 |
117 |
118 | private static final AtomicLongFieldUpdater SAMPLING_TICK_UPDATER =
119 | AtomicLongFieldUpdater.newUpdater(TraceCounter.class, "lastSamplingTick");
120 |
121 | private final Class clazz;
122 | private volatile long lastSamplingTick = System.nanoTime();
123 | private final ConcurrentHashMap traces = new ConcurrentHashMap<>();
124 | private static final ThreadLocal TRACE = ThreadLocal.withInitial(Trace::new);
125 |
126 | public static class Trace {
127 | private Class interfaceClazz;
128 | private String trace;
129 |
130 | public Trace() {
131 |
132 | }
133 |
134 | public Trace with(Class interfaceClazz, String trace) {
135 | this.interfaceClazz = interfaceClazz;
136 | this.trace = trace;
137 | return this;
138 | }
139 |
140 | public Trace clear() {
141 | this.interfaceClazz = null;
142 | this.trace = null;
143 | return this;
144 | }
145 |
146 | @Override
147 | public boolean equals(final Object o) {
148 | if (this == o) return true;
149 | if (o == null || getClass() != o.getClass()) return false;
150 |
151 | final Trace trace1 = (Trace) o;
152 |
153 | if (!interfaceClazz.equals(trace1.interfaceClazz)) return false;
154 | return trace.equals(trace1.trace);
155 | }
156 |
157 | @Override
158 | public int hashCode() {
159 | int result = interfaceClazz.hashCode();
160 | result = 31 * result + trace.hashCode();
161 | return result;
162 | }
163 | }
164 |
165 | public static class TraceData {
166 |
167 | private static final ThreadLocal FULL_STACK_TRACE = new ThreadLocal<>();
168 |
169 | private static final AtomicLongFieldUpdater COUNT_UPDATER =
170 | AtomicLongFieldUpdater.newUpdater(TraceData.class, "count");
171 |
172 | private volatile long count;
173 | private final CopyOnWriteArraySet sampledStackTraces = new CopyOnWriteArraySet<>();
174 |
175 | public void weakIncrementUpdateCount() {
176 | COUNT_UPDATER.lazySet(this, count + 1);
177 | }
178 |
179 | private static StackTraceArrayList acquireStackTraceListOf(int capacity) {
180 | StackTraceArrayList list = FULL_STACK_TRACE.get();
181 | if (list == null) {
182 | list = new StackTraceArrayList(capacity);
183 | FULL_STACK_TRACE.set(list);
184 | }
185 | list.ensureCapacity(capacity);
186 | return list;
187 | }
188 |
189 | private static void releaseStackTraceArrayList() {
190 | FULL_STACK_TRACE.set(null);
191 | }
192 |
193 | public boolean addFullStackTrace() {
194 | // this is not nice :( we KNOW which level we are so can hard-code this
195 | // but is brittle, and it depends on where the trace collection happens
196 | final int START_STACK = 5;
197 | StackTraceElement[] stackTraces = Thread.currentThread().getStackTrace();
198 | final int stackTraceMaxDepth;
199 | if (Agent.FULL_STACK_TRACES_LIMIT <= 0) {
200 | stackTraceMaxDepth = stackTraces.length;
201 | } else {
202 | stackTraceMaxDepth = Math.min(Agent.FULL_STACK_TRACES_LIMIT + START_STACK, stackTraces.length);
203 | }
204 | final StackTraceArrayList fullStackTraces = acquireStackTraceListOf(stackTraceMaxDepth);
205 | boolean addedFullStackSample = false;
206 | try {
207 | for (int i = START_STACK; i < stackTraceMaxDepth; i++) {
208 | fullStackTraces.add(stackTraces[i]);
209 | }
210 | addedFullStackSample = sampledStackTraces.add(fullStackTraces);
211 | } catch (Throwable ignore) {
212 |
213 | }
214 | if (addedFullStackSample) {
215 | // cannot reuse it
216 | releaseStackTraceArrayList();
217 | } else {
218 | // keep on reusing it
219 | fullStackTraces.clear();
220 | }
221 | return addedFullStackSample;
222 | }
223 |
224 | }
225 |
226 | private TraceCounter(Class clazz) {
227 | this.clazz = clazz;
228 | }
229 |
230 | protected final void updateTraceCount(Class interfaceClazz, String trace) {
231 | final Trace pooledTrace = TRACE.get().with(interfaceClazz, trace);
232 | final boolean added;
233 | TraceData data = traces.get(pooledTrace);
234 | if (data == null) {
235 | TraceData newData = new TraceData();
236 | data = traces.putIfAbsent(pooledTrace, newData);
237 | if (data == null) {
238 | // cannot reuse the pooled trace anymore!
239 | added = true;
240 | data = newData;
241 | } else {
242 | added = false;
243 | }
244 | } else {
245 | added = false;
246 | }
247 | if (added) {
248 | // replace it!
249 | TRACE.set(new Trace());
250 | } else {
251 | pooledTrace.clear();
252 | }
253 | data.weakIncrementUpdateCount();
254 | final int samplingPeriod = METRONOME_PERIOD_MS.get();
255 | if (samplingPeriod >= 0) {
256 | if (samplingPeriod == 0) {
257 | data.addFullStackTrace();
258 | } else {
259 | final long tick = lastSamplingTick;
260 | final long globalTick = GLOBAL_SAMPLING_TICK;
261 | if (tick - globalTick < 0) {
262 | // move forward our tick
263 | if (SAMPLING_TICK_UPDATER.compareAndSet(this, tick, globalTick)) {
264 | data.addFullStackTrace();
265 | }
266 | }
267 | }
268 | }
269 | }
270 |
271 | public long count() {
272 | long count = 0;
273 | for (Map.Entry trace : traces.entrySet()) {
274 | count += trace.getValue().count;
275 | }
276 | return count;
277 | }
278 |
279 | public static class Snapshot implements Comparable {
280 |
281 | public static class TraceSnapshot {
282 |
283 | public static class ClassCount {
284 | public final Class interfaceClazz;
285 | public final long count;
286 |
287 | private ClassCount(final Class interfaceClazz, final long count) {
288 | this.interfaceClazz = interfaceClazz;
289 | this.count = count;
290 | }
291 | }
292 |
293 | public final String trace;
294 | public final ClassCount[] interfaceSeenCounters;
295 |
296 | private TraceSnapshot(final String trace, final ClassCount[] interfaceSeenCounters) {
297 | this.trace = trace;
298 | this.interfaceSeenCounters = interfaceSeenCounters;
299 | }
300 | }
301 |
302 | public final Class clazz;
303 | public final Class[] seen;
304 | public final TraceSnapshot[] traces;
305 | public final StackTraceElement[][] fullStackFrames;
306 | public final long updateCount;
307 |
308 | private Snapshot(Class clazz, Class[] seen, TraceSnapshot[] traces, StackTraceElement[][] fullStackFrame) {
309 | this.clazz = clazz;
310 | this.seen = seen;
311 | this.fullStackFrames = fullStackFrame;
312 | this.traces = traces;
313 | this.updateCount = updateCount(traces);
314 | }
315 |
316 | private static long updateCount(TraceSnapshot[] traces) {
317 | long count = 0;
318 | for (TraceSnapshot trace : traces) {
319 | for (TraceSnapshot.ClassCount counter : trace.interfaceSeenCounters) {
320 | count += counter.count;
321 | }
322 | }
323 | return count;
324 | }
325 |
326 | @Override
327 | public int compareTo(Snapshot o) {
328 | return Long.compare(updateCount, o.updateCount);
329 | }
330 | }
331 |
332 | private static Snapshot.TraceSnapshot[] buildOrderedTraceSnapshots(Map> topStackTraces) {
333 | final Snapshot.TraceSnapshot[] traceSnapshots = new Snapshot.TraceSnapshot[topStackTraces.size()];
334 | int i = 0;
335 | for (Map.Entry> topStackTrace : topStackTraces.entrySet()) {
336 | Snapshot.TraceSnapshot.ClassCount[] classCounts = topStackTrace.getValue()
337 | .toArray(new Snapshot.TraceSnapshot.ClassCount[0]);
338 | // order update count(s) based on
339 | Arrays.sort(classCounts,
340 | Comparator.comparingLong(classCount -> classCount.count).reversed());
341 | traceSnapshots[i] = new Snapshot.TraceSnapshot(topStackTrace.getKey(), classCounts);
342 | i++;
343 | }
344 | // order trace snapshot(s) based on total (ie sum) update count
345 | Arrays.sort(traceSnapshots, Comparator.comparingLong(traceSnapshot -> {
346 | long totalUpdateCount = 0;
347 | for (Snapshot.TraceSnapshot.ClassCount classCount : traceSnapshot.interfaceSeenCounters) {
348 | totalUpdateCount += classCount.count;
349 | }
350 | return totalUpdateCount;
351 | }).reversed());
352 | return traceSnapshots;
353 | }
354 |
355 | private static class Counter {
356 | long value;
357 | }
358 |
359 | private static Class[] buildOrderedInterfaceClasses(final Map interfaceCounters) {
360 | final Class[] interfaceClasses = new Class[interfaceCounters.size()];
361 | int j = 0;
362 | for (Map.Entry interfaceCounter : interfaceCounters.entrySet()) {
363 | interfaceClasses[j] = interfaceCounter.getKey();
364 | j++;
365 | }
366 | Arrays.sort(interfaceClasses,
367 | Comparator.comparingLong(aClass -> interfaceCounters.get(aClass).value).reversed());
368 | return interfaceClasses;
369 | }
370 |
371 | private static StackTraceElement[][] buildUnorderedFullStackTraces(Set fullStackFrames) {
372 | final StackTraceElement[][] fullStackTraces = new StackTraceElement[fullStackFrames.size()][];
373 | int z = 0;
374 | for (StackTraceArrayList fullStackFrame : fullStackFrames) {
375 | fullStackTraces[z] = fullStackFrame.toArray(new StackTraceElement[0]);
376 | z++;
377 | }
378 | return fullStackTraces;
379 | }
380 |
381 | public Snapshot snapshot() {
382 | final int tracesCount = traces.size();
383 | if (tracesCount == 0) {
384 | return null;
385 | }
386 | final Map> topStackTraces = new HashMap<>(tracesCount);
387 | final Set fullStackFrames = new HashSet<>(tracesCount);
388 |
389 | final Map interfaceCounters = new HashMap<>();
390 | traces.forEach((trace, traceData) -> {
391 | for (StackTraceArrayList fullStackTrace : traceData.sampledStackTraces) {
392 | fullStackFrames.add(fullStackTrace);
393 | }
394 | topStackTraces.computeIfAbsent(trace.trace, t -> new ArrayList<>(1))
395 | .add(new Snapshot.TraceSnapshot.ClassCount(trace.interfaceClazz, traceData.count));
396 | interfaceCounters.computeIfAbsent(trace.interfaceClazz, t -> new Counter()).value += traceData.count;
397 | });
398 | final Snapshot.TraceSnapshot[] traceSnapshots = buildOrderedTraceSnapshots(topStackTraces);
399 | final Class[] interfaceClasses = buildOrderedInterfaceClasses(interfaceCounters);
400 | final StackTraceElement[][] fullStackTraces = buildUnorderedFullStackTraces(fullStackFrames);
401 | return new TraceCounter.Snapshot(clazz, interfaceClasses, traceSnapshots,
402 | fullStackTraces);
403 | }
404 |
405 | }
406 |
407 | private static final AppendOnlyList TYPE_POLLUTION_COUNTERS = new AppendOnlyList<>();
408 | private static final AppendOnlyList MISS_COUNTERS = new AppendOnlyList<>();
409 |
410 | private static final ClassValue TYPE_POLLUTION_COUNTER_CACHE = new ClassValue<>() {
411 | @Override
412 | protected TypePollutionTraceCounter computeValue(Class> aClass) {
413 | final TypePollutionTraceCounter counter = new TypePollutionTraceCounter(aClass);
414 | TYPE_POLLUTION_COUNTERS.add(counter);
415 | return counter;
416 | }
417 | };
418 |
419 | private static final ClassValue MISS_COUNTER_CACHE = new ClassValue<>() {
420 | @Override
421 | protected MissTraceCounter computeValue(Class> aClass) {
422 | final MissTraceCounter counter = new MissTraceCounter(aClass);
423 | MISS_COUNTERS.add(counter);
424 | return counter;
425 | }
426 | };
427 |
428 | public static boolean traceIsInstance(Class interfaceClazz, Object o, String trace) {
429 | if (!interfaceClazz.isInstance(o)) {
430 | if (o != null && isTracingStarted() && interfaceClazz.isInterface()) {
431 | MISS_COUNTER_CACHE.get(o.getClass()).onTypeCheckMiss(interfaceClazz, trace);
432 | }
433 | return false;
434 | }
435 | if (!isTracingStarted()) {
436 | return true;
437 | }
438 | // unnecessary tracing
439 | if (!interfaceClazz.isInterface()) {
440 | return true;
441 | }
442 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace);
443 | return true;
444 | }
445 |
446 | public static boolean traceIsAssignableFrom(Class interfaceClazz, Class oClazz, boolean result, String trace) {
447 | if (!result) {
448 | if (isTracingStarted() && interfaceClazz.isInterface()) {
449 | MISS_COUNTER_CACHE.get(oClazz).onTypeCheckMiss(interfaceClazz, trace);
450 | }
451 | return false;
452 | }
453 | if (!isTracingStarted()) {
454 | return true;
455 | }
456 | if (!interfaceClazz.isInterface()) {
457 | return true;
458 | }
459 | TYPE_POLLUTION_COUNTER_CACHE.get(oClazz).onTypeCheckHit(interfaceClazz, trace);
460 | return true;
461 | }
462 |
463 | public static void traceCast(Class interfaceClazz, Object o, String trace) {
464 | if (!isTracingStarted()) {
465 | return;
466 | }
467 | if (!interfaceClazz.isInterface()) {
468 | return;
469 | }
470 | if (!interfaceClazz.isInstance(o)) {
471 | return;
472 | }
473 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace);
474 | }
475 |
476 | public static boolean traceInstanceOf(Object o, Class interfaceClazz, String trace) {
477 | if (!interfaceClazz.isInstance(o)) {
478 | if (o!= null && isTracingStarted() && interfaceClazz.isInterface()) {
479 | MISS_COUNTER_CACHE.get(o.getClass()).onTypeCheckMiss(interfaceClazz, trace);
480 | }
481 | return false;
482 | }
483 | if (!isTracingStarted()) {
484 | return true;
485 | }
486 | if (!interfaceClazz.isInterface()) {
487 | return true;
488 | }
489 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace);
490 | return true;
491 | }
492 |
493 | private static boolean isTracingStarted() {
494 | return TRACING_STARTED.get();
495 | }
496 |
497 | public static void traceCheckcast(Object o, Class interfaceClazz, String trace) {
498 | if (!isTracingStarted()) {
499 | return;
500 | }
501 | if (!interfaceClazz.isInterface()) {
502 | return;
503 | }
504 | if (!interfaceClazz.isInstance(o)) {
505 | return;
506 | }
507 | TYPE_POLLUTION_COUNTER_CACHE.get(o.getClass()).onTypeCheckHit(interfaceClazz, trace);
508 | }
509 |
510 | private static Collection orderedCountersSnapshots(AppendOnlyList extends TraceCounter> counters, final int minUpdateCount) {
511 | final int size = (int) counters.size();
512 | ArrayList snapshots = new ArrayList<>(size);
513 | counters.forEach(traceCounter -> {
514 | final int minCount = Math.max(1, minUpdateCount);
515 | if (traceCounter.count() > minCount) {
516 | final TraceCounter.Snapshot snapshot = traceCounter.snapshot();
517 | if (snapshot != null) {
518 | snapshots.add(snapshot);
519 | }
520 | }
521 | });
522 | snapshots.sort(Comparator.reverseOrder());
523 | // collect
524 | return snapshots;
525 | }
526 |
527 | public static Collection orderedTypePollutionCountersSnapshot(final int minUpdateCount) {
528 | return orderedCountersSnapshots(TYPE_POLLUTION_COUNTERS, minUpdateCount);
529 | }
530 |
531 | public static Collection orderedMissCountersSnapshot(final int minUpdateCount) {
532 | return orderedCountersSnapshots(MISS_COUNTERS, minUpdateCount);
533 | }
534 |
535 | }
536 |
--------------------------------------------------------------------------------
/benchmarks/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/modules.xml
8 | .idea/jarRepositories.xml
9 | .idea/compiler.xml
10 | .idea/libraries/
11 | *.iws
12 | *.iml
13 | *.ipr
14 |
15 | ### Eclipse ###
16 | .apt_generated
17 | .classpath
18 | .factorypath
19 | .project
20 | .settings
21 | .springBeans
22 | .sts4-cache
23 |
24 | ### NetBeans ###
25 | /nbproject/private/
26 | /nbbuild/
27 | /dist/
28 | /nbdist/
29 | /.nb-gradle/
30 | build/
31 | !**/src/main/**/build/
32 | !**/src/test/**/build/
33 |
34 | ### VS Code ###
35 | .vscode/
36 |
37 | ### Mac OS ###
38 | .DS_Store
--------------------------------------------------------------------------------
/benchmarks/WHATIF.md:
--------------------------------------------------------------------------------
1 | # Troubleshooting JDK-8180450
2 |
3 | In the perfect, self-contained and controlled environment of a micro-benchmark
4 | written with JMH, troubleshooting JDK-8180450 is challenging but still possible, but what
5 | if it happens in the real world?
6 |
7 | ## Who says we have *any* perf issue?
8 |
9 | *Required*:
10 | - https://github.com/jvm-profiling-tools/async-profiler
11 | - perf-tools
12 | - perf-c2c
13 |
14 | Let's run an example (dumb) application that suffer from this issue:
15 |
16 | ```bash
17 | $ java -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2
18 | ```
19 | and attaching async-profiler to it with:
20 | ```bash
21 | $ ./profiler.sh -d 10 -t -f out.html -e cpu
22 | ```
23 | we get
24 |
25 | [](https://htmlpreview.github.io/?https://github.com/RedHatPerf/type-pollution-agent/blob/master/benchmarks/images/async/async-profiler.html)
26 |
27 | That's not terribly useful, given that the blamed method doesn't match our expectation,
28 | but still give us a good hint that we're CPU limited:
29 | this is clear by counting the number of samples ie 100 Hz * 10 seconds * 2 threads = 2000 samples.
30 |
31 | We can dig on this more by using `perf`:
32 | ```bash
33 | $ perf stat --timeout 10000 --pid
34 |
35 | Performance counter stats for process id '1247858':
36 |
37 | 20,013.68 msec task-clock # 1.999 CPUs utilized
38 | 616 context-switches # 30.779 /sec
39 | 39 cpu-migrations # 1.949 /sec
40 | 20 page-faults # 0.999 /sec
41 | 82,267,123,004 cycles # 4.111 GHz
42 | 41,091,980,552 instructions # 0.50 insn per cycle
43 | 9,477,353,472 branches # 473.544 M/sec
44 | 483,549 branch-misses # 0.01% of all branches
45 |
46 | 10.010738228 seconds time elapsed
47 |
48 | ```
49 | IPC is 0.5? IPC < 1 it usually means stalls presence.
50 | A quick check (spoiler; we already know it would report something useful :P)
51 | with [perf c2c](https://man7.org/linux/man-pages/man1/perf-c2c.1.html) it's a no-brain:
52 | ```bash
53 | $ timeout 20s perf c2c record -- --delay 10000 java -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2
54 | Events disabled
55 | # ...10 seconds after...
56 | Events enabled
57 | # ...10 seconds after...
58 | [ perf record: Woken up 49 times to write data ]
59 | [ perf record: Captured and wrote 12.552 MB perf.data (148076 samples) ]
60 | ```
61 |
62 | and to generate the report:
63 | ```bash
64 | $ perf c2c report --stats
65 | =================================================
66 | Trace Event Information
67 | =================================================
68 | Total records : 148076
69 | Locked Load/Store Operations : 18
70 | Load Operations : 70418
71 | Loads - uncacheable : 0
72 | Loads - IO : 0
73 | Loads - Miss : 0
74 | Loads - no mapping : 4
75 | Load Fill Buffer Hit : 70200
76 | Load L1D hit : 71
77 | Load L2D hit : 0
78 | Load LLC hit : 121
79 | Load Local HITM : 19
80 | Load Remote HITM : 0
81 | Load Remote HIT : 0
82 | Load Local DRAM : 22
83 | Load Remote DRAM : 0
84 | Load MESI State Exclusive : 0
85 | Load MESI State Shared : 22
86 | Load LLC Misses : 22
87 | Load access blocked by data : 0
88 | Load access blocked by address : 0
89 | LLC Misses to Local DRAM : 100.0%
90 | LLC Misses to Remote DRAM : 0.0%
91 | LLC Misses to Remote cache (HIT) : 0.0%
92 | LLC Misses to Remote cache (HITM) : 0.0%
93 | Store Operations : 77658
94 | Store - uncacheable : 0
95 | Store - no mapping : 0
96 | Store L1D Hit : 34054
97 | Store L1D Miss : 43604
98 | Store No available memory level : 0
99 | No Page Map Rejects : 815
100 | Unable to parse data source : 0
101 |
102 | =================================================
103 | Global Shared Cache Line Event Information
104 | =================================================
105 | Total Shared Cache Lines : 4
106 | Load HITs on shared lines : 70125
107 | Fill Buffer Hits on shared lines : 70106
108 | L1D hits on shared lines : 0
109 | L2D hits on shared lines : 0
110 | LLC hits on shared lines : 19
111 | Locked Access on shared lines : 0
112 | Blocked Access on shared lines : 0
113 | Store HITs on shared lines : 45153
114 | Store L1D hits on shared lines : 1556
115 | Store No available memory level : 0
116 | Total Merged records : 45172
117 |
118 | =================================================
119 | c2c details
120 | =================================================
121 | Events : cpu/mem-loads,ldlat=30/P
122 | : cpu/mem-stores/P
123 | : dummy:HG
124 | Cachelines sort on : Total HITMs
125 | Cacheline data grouping : offset,iaddr
126 | ```
127 | Without digging too much in the output, the suspicious parts are
128 | ```bash
129 | =================================================
130 | Global Shared Cache Line Event Information
131 | =================================================
132 | Total Shared Cache Lines : 4
133 | Load HITs on shared lines : 70125
134 | Fill Buffer Hits on shared lines : 70106
135 | L1D hits on shared lines : 0
136 | L2D hits on shared lines : 0
137 | LLC hits on shared lines : 19
138 | Locked Access on shared lines : 0
139 | Blocked Access on shared lines : 0
140 | Store HITs on shared lines : 45153
141 | Store L1D hits on shared lines : 1556
142 | Store No available memory level : 0
143 | Total Merged records : 45172
144 | ```
145 | presence of shared lines and Store HITs! Who says sharing is a good thing?!
146 |
147 | Let's come back to the Java profiling side: we know we have some CPU-bound related problem
148 | and both OS and Java profiler(s) agree on that, but, how to get the best of both worlds
149 | while getting the right level of detail to understand what's going on?
150 |
151 | ## Down to the Rabbit Hole
152 |
153 | Thanks to https://builds.shipilev.net/perfasm/, we have a nice tool we can use to further narrow
154 | the issue (we need a JVM capable of `PrintAssembly`, see [How to print disassembly from JIT Code?](https://jpbempel.github.io/2012/10/16/how-to-print-disassembly-from-JIT-code.html)
155 | and [PrintAssembly output explained!](https://jpbempel.github.io/2015/12/30/printassembly-output-explained.html)).
156 |
157 | This approach emulate what JMH does under the hood with its handy `perfasm` profiler
158 | ie [LinuxPerfAsmProfiler](https://github.com/openjdk/jmh/blob/master/jmh-core/src/main/java/org/openjdk/jmh/profile/LinuxPerfAsmProfiler.java)
159 |
160 | Let's run it:
161 | ```bash
162 | $ timeout 20s perf record --delay 10000 --event cycles java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+PrintNMethods -XX:+PrintNativeNMethods -XX:+PrintSignatureHandlers -XX:+PrintAdapterHandlers -XX:+PrintStubCode -XX:+LogCompilation -XX:LogFile=hotspot.log -XX:+DebugNonSafepoints -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2
163 | ```
164 | And then, process the collected samples (and compilation logs) with:
165 | ```bash
166 | $ java -jar benchmarks/tools/perfasm.jar "delay=1000;length=10000000;hotThreshold=0.01" hotspot.log perf.data
167 | ```
168 | Which relevant output
169 | ```asm
170 | ....[Hottest Region 1]..............................................................................
171 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (101 bytes)
172 |
173 | 0x00007fb54f2d0d97: jne 0x00007fb54f2d0da1
174 | 0x00007fb54f2d0d9d: mov %rax,0x20(%rsi)
175 | 0x00007fb54f2d0da1: je 0x00007fb54f2d0cd2
176 | 0x00007fb54f2d0da7: mov $0xffffffde,%esi
177 | 0x00007fb54f2d0dac: mov %r11,%rbp
178 | 0x00007fb54f2d0daf: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
179 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
180 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
181 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
182 | ; {runtime_call UncommonTrapBlob}
183 | 0.01% 0x00007fb54f2d0db4: movabs $0x800062248,%rax ; {metadata('io/type/pollution/benchmarks/Context')}
184 | 0x00007fb54f2d0dbe: push %rax
185 | 0.02% 0x00007fb54f2d0dbf: mov %rax,%rax
186 | 0.27% 0x00007fb54f2d0dc2: mov 0x28(%rsi),%rdi
187 | 23.61% 0x00007fb54f2d0dc6: mov (%rdi),%ecx
188 | 1.40% 0x00007fb54f2d0dc8: add $0x8,%rdi
189 | 0x00007fb54f2d0dcc: test %rax,%rax
190 | 0x00007fb54f2d0dcf: repnz scas %es:(%rdi),%rax
191 | 10.78% 0x00007fb54f2d0dd2: pop %rax
192 | 2.76% ╭ 0x00007fb54f2d0dd3: jne 0x00007fb54f2d0ddd
193 | 0.00% │ 0x00007fb54f2d0dd9: mov %rax,0x20(%rsi)
194 | 0.60% ↘ 0x00007fb54f2d0ddd: je 0x00007fb54f2d0d0f
195 | 0x00007fb54f2d0de3: mov $0xffffffde,%esi
196 | 0x00007fb54f2d0de8: mov %r8,%rbp
197 | 0x00007fb54f2d0deb: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
198 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
199 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8)
200 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
201 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
202 | ; {runtime_call UncommonTrapBlob}
203 | 0.08% 0x00007fb54f2d0df0: movabs $0x800062448,%rax ; {metadata('io/type/pollution/benchmarks/InternalContext')}
204 | 0.18% 0x00007fb54f2d0dfa: push %rax
205 | 0.02% 0x00007fb54f2d0dfb: mov %rax,%rax
206 | 0x00007fb54f2d0dfe: mov 0x28(%rsi),%rdi
207 | 23.53% 0x00007fb54f2d0e02: mov (%rdi),%ecx
208 | 1.51% 0x00007fb54f2d0e04: add $0x8,%rdi
209 | 0.00% 0x00007fb54f2d0e08: test %rax,%rax
210 | 1.17% 0x00007fb54f2d0e0b: repnz scas %es:(%rdi),%rax
211 | 9.96% 0x00007fb54f2d0e0e: pop %rax
212 | 2.82% ╭ 0x00007fb54f2d0e0f: jne 0x00007fb54f2d0e19
213 | 0.00% │ 0x00007fb54f2d0e15: mov %rax,0x20(%rsi)
214 | 0.59% ↘ 0x00007fb54f2d0e19: je 0x00007fb54f2d0d22
215 | 0x00007fb54f2d0e1f: mov $0xffffffde,%esi
216 | 0x00007fb54f2d0e24: mov %r8,%rbp
217 | 0x00007fb54f2d0e27: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
218 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
219 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
220 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
221 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
222 | ; {runtime_call UncommonTrapBlob}
223 | 0x00007fb54f2d0e2c: cmp $0x67c70,%r10d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')}
224 | 0x00007fb54f2d0e33: jne 0x00007fb54f2d0e52 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
225 | ....................................................................................................
226 | 79.33%
227 | ```
228 | And luckly for us, on JDK (11, the one used to run the application), there are not many parts
229 | making uses of `repnz scas` ie [MacroAssembler::check_klass_subtype_slow_path](https://github.com/openjdk/jdk11/blob/37115c8ea4aff13a8148ee2b8832b20888a5d880/src/hotspot/cpu/x86/macroAssembler_x86.cpp#L5514),
230 | responsible to emitting the slow path code for instanceof/checkcast (not only, more on later)
231 | type checks.
232 |
233 | ## What if...this agent was already implemented?
234 |
235 | In a better world, where we can run upfront the agent on cases as suspicious as this one,
236 | what we can get is:
237 | ```
238 | $ timeout 10s java -javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar -jar benchmarks/target/type-pollution-benchmarks-0.1-SNAPSHOT.jar 2
239 | --------------------------
240 | Type Pollution Statistics:
241 | --------------------------
242 | 1: io.type.pollution.benchmarks.DuplicatedContext
243 | Count: 126152118
244 | Types:
245 | io.type.pollution.benchmarks.InternalContext
246 | io.type.pollution.benchmarks.Context
247 | Traces:
248 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:9)
249 | class: io.type.pollution.benchmarks.InternalContext
250 | count: 63904149
251 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:8)
252 | class: io.type.pollution.benchmarks.Context
253 | count: 62247969
254 | --------------------------
255 | 2: io.type.pollution.benchmarks.NonDuplicatedContext
256 | Count: 21999
257 | Types:
258 | io.type.pollution.benchmarks.InternalContext
259 | io.type.pollution.benchmarks.Context
260 | Traces:
261 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:9)
262 | class: io.type.pollution.benchmarks.InternalContext
263 | count: 11000
264 | io.type.pollution.benchmarks.ContextUtil.isDuplicatedContext(ContextUtil.java:8)
265 | class: io.type.pollution.benchmarks.Context
266 | count: 10999
267 | --------------------------
268 | ```
269 | As expected, it correctly reports 2 concrete types performing type checks (secondary super cache invalidations) against the same interfaces,
270 | with `DuplicatedContext` as the main offender, while `NonDuplicatedContext`, instead, doing it way less (count is ~ matching the forced warmup in the code).
271 |
272 | ## BONUS: Why async-profiler's flamegraph was broken?
273 |
274 | To answer this question we've opened [Unknown Java Frame on JDK-8180450 reproducer](https://github.com/jvm-profiling-tools/async-profiler/issues/673)
275 | which important parts are:
276 | - JIT's [sender frame computation](https://github.com/openjdk/jdk/blob/2baf2516e1d172268ec7c4c066a1b53bb0bf0779/src/hotspot/cpu/x86/frame_x86.cpp#L141) assumes `sp` to not change, but the type check logic perform `push/pop` operations on the stack that modify `sp` while running in Java
277 | - Using [perf map agent](https://github.com/jvm-profiling-tools/perf-map-agent) and running the application with `PreserveFramePointer` works, showing correct stack frames, because `perf` just care about the frame pointer
278 |
279 | `AsyncGetCallTrace`-based profilers need to perform their own mitigation/stack-recovery mechanism and
280 | sadly, while writing this guide, the current version of async-profiler ([v2.8](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.8)) has disabled most of the mitigations,
281 | to save crashes while trying to recover the right stack-frame: just switching to [v2.7](https://github.com/jvm-profiling-tools/async-profiler/releases/tag/v2.7) will make the profiler able again
282 | to correctly report the guilty method(s).
283 |
284 | ```bash
285 | $ ./profiler.sh -d 10 -t -f async-profiler.jfr -e cpu
286 | # JFR format store additional info eg BCI, Line Of Code
287 | $ java -cp build/converter.jar jfr2flame --threads --lines async-profiler.jfr async-profiler-lines.html
288 | ```
289 | That correctly reports:
290 | [](https://htmlpreview.github.io/?https://github.com/RedHatPerf/type-pollution-agent/blob/master/benchmarks/images/async/async-profiler-lines.html)
291 |
292 |
293 |
294 |
295 |
--------------------------------------------------------------------------------
/benchmarks/images/async/async-profiler-lines.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 | Flame Graph
21 |
22 |
23 |
24 |
25 | Matched: ❌
26 |
27 |
300 |
--------------------------------------------------------------------------------
/benchmarks/images/async/async-profiler-lines.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/images/async/async-profiler-lines.png
--------------------------------------------------------------------------------
/benchmarks/images/async/async-profiler.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
21 | CPU profile
22 |
23 |
24 |
25 |
26 | Matched: ❌
27 |
28 |
281 |
--------------------------------------------------------------------------------
/benchmarks/images/async/async-profiler.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/images/async/async-profiler.png
--------------------------------------------------------------------------------
/benchmarks/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | io.forked.franz
8 | type-pollution-parent
9 | 0.1-SNAPSHOT
10 |
11 |
12 | type-pollution-benchmarks
13 | 0.1-SNAPSHOT
14 | jar
15 |
16 |
17 | 1.35
18 |
19 |
20 |
21 |
22 | org.openjdk.jmh
23 | jmh-core
24 | ${jmh-version}
25 |
26 |
27 | org.openjdk.jmh
28 | jmh-generator-annprocess
29 | ${jmh-version}
30 | provided
31 |
32 |
33 |
34 |
35 |
36 |
37 | maven-jar-plugin
38 | 3.2.2
39 |
40 |
41 |
42 | true
43 | io.type.pollution.benchmarks.Main
44 |
45 |
46 |
47 |
48 |
49 | org.apache.maven.plugins
50 | maven-shade-plugin
51 | 3.3.0
52 |
53 |
54 | package
55 |
56 | shade
57 |
58 |
59 | benchmark
60 |
61 |
63 | org.openjdk.jmh.Main
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | org.apache.maven.plugins
72 | maven-compiler-plugin
73 |
74 | 10
75 | 10
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/benchmarks/reports/async-profiler.jfr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/reports/async-profiler.jfr
--------------------------------------------------------------------------------
/benchmarks/reports/perfasm.txt:
--------------------------------------------------------------------------------
1 | PrintAssembly processed: 31535 total address lines.
2 | Perf output processed (skipped 1.000 seconds):
3 | Column 1: cycles (69834 events)
4 |
5 | Hottest code regions (>1.00% "cycles" events):
6 |
7 | ....[Hottest Region 1]..............................................................................
8 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (101 bytes)
9 |
10 | 0x00007fb54f2d0d97: jne 0x00007fb54f2d0da1
11 | 0x00007fb54f2d0d9d: mov %rax,0x20(%rsi)
12 | 0x00007fb54f2d0da1: je 0x00007fb54f2d0cd2
13 | 0x00007fb54f2d0da7: mov $0xffffffde,%esi
14 | 0x00007fb54f2d0dac: mov %r11,%rbp
15 | 0x00007fb54f2d0daf: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
16 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
17 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
18 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
19 | ; {runtime_call UncommonTrapBlob}
20 | 0.01% 0x00007fb54f2d0db4: movabs $0x800062248,%rax ; {metadata('io/type/pollution/benchmarks/Context')}
21 | 0x00007fb54f2d0dbe: push %rax
22 | 0.02% 0x00007fb54f2d0dbf: mov %rax,%rax
23 | 0.27% 0x00007fb54f2d0dc2: mov 0x28(%rsi),%rdi
24 | 23.61% 0x00007fb54f2d0dc6: mov (%rdi),%ecx
25 | 1.40% 0x00007fb54f2d0dc8: add $0x8,%rdi
26 | 0x00007fb54f2d0dcc: test %rax,%rax
27 | 0x00007fb54f2d0dcf: repnz scas %es:(%rdi),%rax
28 | 10.78% 0x00007fb54f2d0dd2: pop %rax
29 | 2.76% ╭ 0x00007fb54f2d0dd3: jne 0x00007fb54f2d0ddd
30 | 0.00% │ 0x00007fb54f2d0dd9: mov %rax,0x20(%rsi)
31 | 0.60% ↘ 0x00007fb54f2d0ddd: je 0x00007fb54f2d0d0f
32 | 0x00007fb54f2d0de3: mov $0xffffffde,%esi
33 | 0x00007fb54f2d0de8: mov %r8,%rbp
34 | 0x00007fb54f2d0deb: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
35 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
36 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8)
37 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
38 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
39 | ; {runtime_call UncommonTrapBlob}
40 | 0.08% 0x00007fb54f2d0df0: movabs $0x800062448,%rax ; {metadata('io/type/pollution/benchmarks/InternalContext')}
41 | 0.18% 0x00007fb54f2d0dfa: push %rax
42 | 0.02% 0x00007fb54f2d0dfb: mov %rax,%rax
43 | 0x00007fb54f2d0dfe: mov 0x28(%rsi),%rdi
44 | 23.53% 0x00007fb54f2d0e02: mov (%rdi),%ecx
45 | 1.51% 0x00007fb54f2d0e04: add $0x8,%rdi
46 | 0.00% 0x00007fb54f2d0e08: test %rax,%rax
47 | 1.17% 0x00007fb54f2d0e0b: repnz scas %es:(%rdi),%rax
48 | 9.96% 0x00007fb54f2d0e0e: pop %rax
49 | 2.82% ╭ 0x00007fb54f2d0e0f: jne 0x00007fb54f2d0e19
50 | 0.00% │ 0x00007fb54f2d0e15: mov %rax,0x20(%rsi)
51 | 0.59% ↘ 0x00007fb54f2d0e19: je 0x00007fb54f2d0d22
52 | 0x00007fb54f2d0e1f: mov $0xffffffde,%esi
53 | 0x00007fb54f2d0e24: mov %r8,%rbp
54 | 0x00007fb54f2d0e27: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
55 | ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
56 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
57 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
58 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
59 | ; {runtime_call UncommonTrapBlob}
60 | 0x00007fb54f2d0e2c: cmp $0x67c70,%r10d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')}
61 | 0x00007fb54f2d0e33: jne 0x00007fb54f2d0e52 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
62 | ....................................................................................................
63 | 79.33%
64 |
65 | ....[Hottest Region 2]..............................................................................
66 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (55 bytes)
67 |
68 | nul chk table [0x00007fb54f2d1128,0x00007fb54f2d1140] = 24
69 | ----------------------------------------------------------------------
70 | io/type/pollution/benchmarks/Main.applicationStack(Lio/type/pollution/benchmarks/Context;I)Z [0x00007fb54f2d0c60, 0x00007fb54f2d0ec8] 616 bytes
71 | [Entry Point]
72 | [Verified Entry Point]
73 | [Constants]
74 | # {method} {0x00007fb52d0bf8b8} 'applicationStack' '(Lio/type/pollution/benchmarks/Context;I)Z' in 'io/type/pollution/benchmarks/Main'
75 | # parm0: rsi:rsi = 'io/type/pollution/benchmarks/Context'
76 | # parm1: rdx = int
77 | # [sp+0x30] (sp of caller)
78 | 0.68% 0x00007fb54f2d0c60: mov %eax,-0x14000(%rsp)
79 | 2.38% 0x00007fb54f2d0c67: push %rbp
80 | 1.48% 0x00007fb54f2d0c68: sub $0x20,%rsp ;*synchronization entry
81 | ; - io.type.pollution.benchmarks.Main::applicationStack@-1 (line 37)
82 | 0.00% 0x00007fb54f2d0c6c: mov %rsi,%r8
83 | 0.38% 0x00007fb54f2d0c6f: cmp $0x1,%edx
84 | ╭ 0x00007fb54f2d0c72: je 0x00007fb54f2d0c98 ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0}
85 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@2 (line 37)
86 | 1.29% │ 0x00007fb54f2d0c74: mov %edx,%r11d
87 | 0.00% │ 0x00007fb54f2d0c77: dec %r11d
88 | 0.00% │ 0x00007fb54f2d0c7a: cmp $0x1,%r11d
89 | 0.00% │ 0x00007fb54f2d0c7e: je 0x00007fb54f2d0ce7 ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0}
90 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@2 (line 37)
91 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
92 | 0.25% │ 0x00007fb54f2d0c80: add $0xfffffffe,%edx
93 | 0.79% │ 0x00007fb54f2d0c83: callq 0x00007fb54f2d0c60 ; ImmutableOopMap{}
94 | │ ;*invokestatic applicationStack {reexecute=0 rethrow=0 return_oop=0}
95 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
96 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
97 | │ ; {static_call}
98 | 0.02% │ 0x00007fb54f2d0c88: add $0x20,%rsp
99 | 0.01% │ 0x00007fb54f2d0c8c: pop %rbp
100 | 3.87% │ 0x00007fb54f2d0c8d: mov 0x108(%r15),%r10
101 | 0.01% │ 0x00007fb54f2d0c94: test %eax,(%r10) ; {poll_return}
102 | 3.97% │ 0x00007fb54f2d0c97: retq
103 | ↘ 0x00007fb54f2d0c98: mov %rsi,%r11
104 | 0x00007fb54f2d0c9b: mov 0x8(%rsi),%r10d ; implicit exception: dispatches to 0x00007fb54f2d0e7d
105 | 0x00007fb54f2d0c9f: movabs $0x800000000,%rsi
106 | 0x00007fb54f2d0ca9: add %r10,%rsi
107 | 0x00007fb54f2d0cac: mov 0x20(%rsi),%r10
108 | 0x00007fb54f2d0cb0: movabs $0x800062248,%r8 ; {metadata('io/type/pollution/benchmarks/Context')}
109 | 0x00007fb54f2d0cba: cmp %r8,%r10
110 | 0x00007fb54f2d0cbd: jne 0x00007fb54f2d0d3a ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
111 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8)
112 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
113 | ....................................................................................................
114 | 15.14%
115 |
116 | ....[Hottest Region 3]..............................................................................
117 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (63 bytes)
118 |
119 | 0x00007fb54f2d0ccc: jne 0x00007fb54f2d0d78 ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
120 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
121 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
122 | 0x00007fb54f2d0cd2: mov 0x8(%r11),%r10d
123 | 0x00007fb54f2d0cd6: cmp $0x67840,%r10d ; {metadata('io/type/pollution/benchmarks/NonDuplicatedContext')}
124 | 0x00007fb54f2d0cdd: jne 0x00007fb54f2d0e2c ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
125 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9)
126 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
127 | 0x00007fb54f2d0ce3: xor %eax,%eax
128 | 0x00007fb54f2d0ce5: jmp 0x00007fb54f2d0c88
129 | 0.11% 0x00007fb54f2d0ce7: mov 0x8(%rsi),%r11d ; implicit exception: dispatches to 0x00007fb54f2d0e8c
130 | 0.49% 0x00007fb54f2d0ceb: movabs $0x800000000,%rsi
131 | 0x00007fb54f2d0cf5: add %r11,%rsi
132 | 0x00007fb54f2d0cf8: mov 0x20(%rsi),%r11
133 | 0.17% 0x00007fb54f2d0cfc: movabs $0x800062248,%r10 ; {metadata('io/type/pollution/benchmarks/Context')}
134 | 0.42% 0x00007fb54f2d0d06: cmp %r10,%r11
135 | 0x00007fb54f2d0d09: jne 0x00007fb54f2d0db4 ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
136 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@4 (line 8)
137 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
138 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
139 | 0.02% 0x00007fb54f2d0d0f: movabs $0x800062448,%r10 ; {metadata('io/type/pollution/benchmarks/InternalContext')}
140 | 0.00% 0x00007fb54f2d0d19: cmp %r10,%r11
141 | 0x00007fb54f2d0d1c: jne 0x00007fb54f2d0df0 ;*checkcast {reexecute=0 rethrow=0 return_oop=0}
142 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
143 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
144 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
145 | 0.01% 0x00007fb54f2d0d22: mov 0x8(%r8),%r11d
146 | 0.84% 0x00007fb54f2d0d26: cmp $0x67840,%r11d ; {metadata('io/type/pollution/benchmarks/NonDuplicatedContext')}
147 | 0x00007fb54f2d0d2d: jne 0x00007fb54f2d0e3f ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
148 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9)
149 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
150 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
151 | 0x00007fb54f2d0d33: xor %eax,%eax
152 | 0x00007fb54f2d0d35: jmpq 0x00007fb54f2d0c88
153 | 0x00007fb54f2d0d3a: movabs $0x800062248,%rax ; {metadata('io/type/pollution/benchmarks/Context')}
154 | 0x00007fb54f2d0d44: push %rax
155 | 0x00007fb54f2d0d45: mov %rax,%rax
156 | 0x00007fb54f2d0d48: mov 0x28(%rsi),%rdi
157 | ....................................................................................................
158 | 2.06%
159 |
160 | ....[Hottest Region 4]..............................................................................
161 | c2, level 4, io.type.pollution.benchmarks.Main::lambda$main$0, version 114 (49 bytes)
162 |
163 | ; - java.lang.Thread::isInterrupted@2 (line 1033)
164 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@11 (line 22)
165 | ; {optimized virtual_call}
166 | 0x00007fb54f2d241c: test %eax,%eax
167 | 0x00007fb54f2d241e: jne 0x00007fb54f2d2481
168 | 0x00007fb54f2d2420: mov (%rsp),%r8
169 | 0x00007fb54f2d2424: nopl 0x0(%rax,%rax,1)
170 | 0x00007fb54f2d242c: data16 data16 xchg %ax,%ax ;*invokevirtual isInterrupted {reexecute=0 rethrow=0 return_oop=0}
171 | ; - java.lang.Thread::isInterrupted@2 (line 1033)
172 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@11 (line 22)
173 | 0.62% ↗ 0x00007fb54f2d2430: mov %r8,%rbp
174 | 0.00% │ 0x00007fb54f2d2433: mov %r8,%rsi
175 | 0.00% │ 0x00007fb54f2d2436: mov $0x6,%edx
176 | │ 0x00007fb54f2d243b: callq 0x00007fb54784a780 ; ImmutableOopMap{rbp=Oop }
177 | │ ;*invokestatic applicationStack {reexecute=0 rethrow=0 return_oop=0}
178 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
179 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
180 | │ ; - io.type.pollution.benchmarks.Main::applicationStackBase@3 (line 32)
181 | │ ; - io.type.pollution.benchmarks.Main::lambda$main$0@18 (line 23)
182 | │ ; {static_call}
183 | 0.00% │ 0x00007fb54f2d2440: mov %rbp,%r8
184 | 0.00% │ 0x00007fb54f2d2443: mov 0x320(%r15),%rsi
185 | 0.00% │ 0x00007fb54f2d244a: mov 0x320(%r15),%r10
186 | 0.53% │ 0x00007fb54f2d2451: cmp %rsi,%r10
187 | │ 0x00007fb54f2d2454: jne 0x00007fb54f2d240e ;*invokestatic currentThread {reexecute=0 rethrow=0 return_oop=0}
188 | │ ; - io.type.pollution.benchmarks.Main::lambda$main$0@8 (line 22)
189 | 0.00% │ 0x00007fb54f2d2456: mov 0x270(%r15),%r10
190 | 0.00% │ 0x00007fb54f2d245d: mov 0x14(%r10),%r11d
191 | 0.55% │ 0x00007fb54f2d2461: test %r11d,%r11d
192 | ╰ 0x00007fb54f2d2464: je 0x00007fb54f2d2430
193 | 0x00007fb54f2d2466: mov $0x1,%ebp ;*invokevirtual isInterrupted {reexecute=0 rethrow=0 return_oop=0}
194 | ; - java.lang.Thread::isInterrupted@2 (line 1033)
195 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@11 (line 22)
196 | 0x00007fb54f2d246b: mov $0xffffff4d,%esi
197 | 0x00007fb54f2d2470: mov %r8,(%rsp)
198 | 0x00007fb54f2d2474: data16 xchg %ax,%ax
199 | 0x00007fb54f2d2477: callq 0x00007fb547849e00 ; ImmutableOopMap{[0]=Oop }
200 | ;*ifne {reexecute=1 rethrow=0 return_oop=0}
201 | ; - io.type.pollution.benchmarks.Main::lambda$main$0@14 (line 22)
202 | ....................................................................................................
203 | 1.72%
204 |
205 | ....[Hottest Region 5]..............................................................................
206 | c2, level 4, io.type.pollution.benchmarks.Main::applicationStack, version 104 (9 bytes)
207 |
208 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@9 (line 9)
209 | ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
210 | ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
211 | ; {runtime_call UncommonTrapBlob}
212 | 0x00007fb54f2d0e2c: cmp $0x67c70,%r10d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')}
213 | ╭ 0x00007fb54f2d0e33: jne 0x00007fb54f2d0e52 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
214 | │ ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9)
215 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
216 | │ 0x00007fb54f2d0e35: mov $0x1,%eax
217 | │ 0x00007fb54f2d0e3a: jmpq 0x00007fb54f2d0c88
218 | 0.63% │ 0x00007fb54f2d0e3f: cmp $0x67c70,%r11d ; {metadata('io/type/pollution/benchmarks/DuplicatedContext')}
219 | │ 0x00007fb54f2d0e46: jne 0x00007fb54f2d0e60 ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
220 | │ ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9)
221 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@6 (line 38)
222 | │ ; - io.type.pollution.benchmarks.Main::applicationStack@15 (line 41)
223 | 0.63% │ 0x00007fb54f2d0e48: mov $0x1,%eax
224 | │ 0x00007fb54f2d0e4d: jmpq 0x00007fb54f2d0c88
225 | ↘ 0x00007fb54f2d0e52: mov $0xffffffc6,%esi
226 | 0x00007fb54f2d0e57: mov %r11,%rbp
227 | 0x00007fb54f2d0e5a: nop
228 | 0x00007fb54f2d0e5b: callq 0x00007fb547849e00 ; ImmutableOopMap{rbp=Oop }
229 | ;*invokeinterface isDuplicated {reexecute=0 rethrow=0 return_oop=0}
230 | ; - io.type.pollution.benchmarks.ContextUtil::isDuplicatedContext@12 (line 9)
231 | ....................................................................................................
232 | 1.27%
233 |
234 | ....[Hottest Regions]...............................................................................
235 | 79.33% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (101 bytes)
236 | 15.14% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (55 bytes)
237 | 2.06% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (63 bytes)
238 | 1.72% c2, level 4 io.type.pollution.benchmarks.Main::lambda$main$0, version 114 (49 bytes)
239 | 1.27% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104 (9 bytes)
240 | 0.21% [kernel.kallsyms] native_write_msr (0 bytes)
241 | 0.01% [kernel.kallsyms] perf_event_task_tick (60 bytes)
242 | 0.01% [kernel.kallsyms] xhci_update_erst_dequeue (3 bytes)
243 | 0.01% [kernel.kallsyms] xhci_ring_ep_doorbell (0 bytes)
244 | 0.01% [kernel.kallsyms] xhci_irq (0 bytes)
245 | 0.01% [kernel.kallsyms] read_tsc (0 bytes)
246 | 0.01% [kernel.kallsyms] cpuacct_account_field (7 bytes)
247 | 0.01% [kernel.kallsyms] check_preemption_disabled (35 bytes)
248 | 0.01% [kernel.kallsyms] asm_sysvec_apic_timer_interrupt (0 bytes)
249 | 0.01% [kernel.kallsyms] sync_regs (0 bytes)
250 | 0.00% [kernel.kallsyms] __intel_pmu_enable_all.constprop.0 (0 bytes)
251 | 0.00% [kernel.kallsyms] update_cfs_group (27 bytes)
252 | 0.00% [kernel.kallsyms] timerqueue_add (19 bytes)
253 | 0.00% libjvm.so VMError::is_error_reported (8 bytes)
254 | 0.00% [kernel.kallsyms] __intel_pmu_enable_all.constprop.0 (0 bytes)
255 | 0.16% <...other 101 warm regions...>
256 | ....................................................................................................
257 | 100.00%
258 |
259 | ....[Hottest Methods (after inlining)]..............................................................
260 | 97.80% c2, level 4 io.type.pollution.benchmarks.Main::applicationStack, version 104
261 | 1.72% c2, level 4 io.type.pollution.benchmarks.Main::lambda$main$0, version 114
262 | 0.21% [kernel.kallsyms] native_write_msr
263 | 0.01% [kernel.kallsyms] perf_event_task_tick
264 | 0.01% [kernel.kallsyms] xhci_update_erst_dequeue
265 | 0.01% [kernel.kallsyms] xhci_ring_ep_doorbell
266 | 0.01% [kernel.kallsyms] xhci_irq
267 | 0.01% [kernel.kallsyms] cpuacct_account_field
268 | 0.01% [kernel.kallsyms] read_tsc
269 | 0.01% [kernel.kallsyms] __intel_pmu_enable_all.constprop.0
270 | 0.01% [kernel.kallsyms] update_cfs_group
271 | 0.01% [kernel.kallsyms] check_preemption_disabled
272 | 0.01% [kernel.kallsyms] asm_sysvec_apic_timer_interrupt
273 | 0.01% [kernel.kallsyms] update_irq_load_avg
274 | 0.01% [kernel.kallsyms] trigger_load_balance
275 | 0.01% [kernel.kallsyms] __update_load_avg_se
276 | 0.01% [kernel.kallsyms] sync_regs
277 | 0.00% [kernel.kallsyms] update_load_avg
278 | 0.00% [kernel.kallsyms] reweight_entity
279 | 0.00% libjvm.so VMError::is_error_reported
280 | 0.14% <...other 74 warm methods...>
281 | ....................................................................................................
282 | 100.00%
283 |
284 | ....[Distribution by Source]........................................................................
285 | 99.52% c2, level 4
286 | 0.45% [kernel.kallsyms]
287 | 0.03% libjvm.so
288 | 0.00% libc.so.6
289 | ....................................................................................................
290 | 100.00%
291 |
292 |
293 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/Context.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | public interface Context {
4 | // some public API
5 | }
6 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/ContextUtil.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | import java.util.Objects;
4 |
5 | class ContextUtil {
6 |
7 | public static boolean isDuplicatedContext(Context context) {
8 | Context actual = Objects.requireNonNull(context);
9 | return ((InternalContext) actual).isDuplicated();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/DuplicatedContext.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | class DuplicatedContext implements InternalContext {
4 | @Override
5 | public boolean isDuplicated() {
6 | return true;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/InternalContext.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | interface InternalContext extends Context {
4 | boolean isDuplicated();
5 | }
6 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/Main.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | import org.openjdk.jmh.annotations.CompilerControl;
4 |
5 | import java.util.concurrent.ExecutorService;
6 | import java.util.concurrent.Executors;
7 |
8 | public class Main {
9 | private static final int STACK_DEPTH = 8;
10 |
11 | public static void main(String[] args) {
12 | // type pollution :(
13 | var ctx = new NonDuplicatedContext();
14 | for (int i = 0; i < 11000; i++) {
15 | applicationStackBase(ctx);
16 | }
17 | int numThreads = Integer.parseInt(args[0]);
18 | ExecutorService es = Executors.newFixedThreadPool(numThreads);
19 | for (int i = 0; i < numThreads; i++) {
20 | es.submit(() -> {
21 | var dupCtx = new DuplicatedContext();
22 | while (!Thread.currentThread().isInterrupted()) {
23 | applicationStackBase(dupCtx);
24 | }
25 | });
26 | }
27 | es.shutdown();
28 | }
29 |
30 | @CompilerControl(CompilerControl.Mode.DONT_INLINE)
31 | public static boolean applicationStackBase(Context ctx) {
32 | return applicationStack(ctx, STACK_DEPTH);
33 | }
34 |
35 | @CompilerControl(CompilerControl.Mode.DONT_INLINE)
36 | public static boolean applicationStack(Context ctx, int depth) {
37 | if (depth == 1) {
38 | return ContextUtil.isDuplicatedContext(ctx);
39 | } else {
40 | depth--;
41 | return applicationStack(ctx, depth);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/NonDuplicatedContext.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | class NonDuplicatedContext implements InternalContext {
4 | @Override
5 | public boolean isDuplicated() {
6 | return false;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/benchmarks/src/main/java/io/type/pollution/benchmarks/RequireNonNullCheckcastScalability.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.benchmarks;
2 |
3 | import org.openjdk.jmh.annotations.*;
4 | import org.openjdk.jmh.infra.Blackhole;
5 |
6 | import java.util.Objects;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | @State(Scope.Thread)
10 | @Measurement(iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS)
11 | @Warmup(iterations = 10, time = 200, timeUnit = TimeUnit.MILLISECONDS)
12 | @OutputTimeUnit(TimeUnit.MICROSECONDS)
13 | @Fork(2)
14 | /**
15 | * To have more fun, run this with:
16 | * -prof perfasm
17 | * -prof jfr
18 | * -prof "async:output=flamegraph;dir=/tmp;libPath=/libasyncProfiler.so"
19 | *
20 | * and
21 | *
22 | * --jvmArgs="-javaagent:agent/target/type-pollution-agent-0.1-SNAPSHOT.jar"
23 | *
24 | * Interesting case is:
25 | * -p typePollution=true -p fixed=false
26 | */
27 | public class RequireNonNullCheckcastScalability {
28 | private Context ctx;
29 |
30 | @Param({"false", "true"})
31 | public boolean typePollution;
32 |
33 | @Param({"false", "true"})
34 | public boolean fixed;
35 |
36 | @Setup
37 | public void init(Blackhole bh) {
38 | if (typePollution) {
39 | ctx = new NonDuplicatedContext();
40 | // let's warm it enough to get it compiled with C2 (by default)
41 | for (int i = 0; i < 11000; i++) {
42 | boolean result = fixed ? fixedIsDuplicated(ctx) : wrongIsDuplicated(ctx);
43 | bh.consume(result);
44 | }
45 | // deopt on warmup
46 | }
47 | ctx = new DuplicatedContext();
48 | }
49 |
50 | private static boolean wrongIsDuplicated(Context ctx) {
51 | return ContextUtil.isDuplicatedContext(ctx);
52 | }
53 |
54 | private static boolean fixedIsDuplicated(Context ctx) {
55 | return Objects.requireNonNull((InternalContext) ctx).isDuplicated();
56 | }
57 |
58 | @Benchmark
59 | @Threads(1)
60 | @CompilerControl(CompilerControl.Mode.DONT_INLINE)
61 | public boolean isDuplicated1() {
62 | return isDuplicated();
63 | }
64 |
65 | @Benchmark
66 | @Threads(2)
67 | @CompilerControl(CompilerControl.Mode.DONT_INLINE)
68 | public boolean isDuplicated2() {
69 | return isDuplicated();
70 | }
71 |
72 | private boolean isDuplicated() {
73 | if (fixed) {
74 | return fixedIsDuplicated(ctx);
75 | }
76 | return wrongIsDuplicated(ctx);
77 | }
78 | }
--------------------------------------------------------------------------------
/benchmarks/tools/perfasm.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedHatPerf/type-pollution-agent/6b91058065e4700163e4d03577d4e6f1d45be9fd/benchmarks/tools/perfasm.jar
--------------------------------------------------------------------------------
/example/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | io.forked.franz
8 | type-pollution-parent
9 | 0.1-SNAPSHOT
10 |
11 |
12 | type-pollution-example
13 | 0.1-SNAPSHOT
14 | jar
15 |
16 |
17 |
18 |
19 | maven-jar-plugin
20 | 3.2.2
21 |
22 |
23 |
24 | true
25 | io.type.pollution.example.Main
26 |
27 |
28 |
29 |
30 |
31 | org.apache.maven.plugins
32 | maven-compiler-plugin
33 |
34 | 8
35 | 8
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/example/src/main/java/io/type/pollution/example/Main.java:
--------------------------------------------------------------------------------
1 | package io.type.pollution.example;
2 |
3 | import java.io.Serializable;
4 | import java.util.concurrent.ExecutorService;
5 | import java.util.concurrent.Executors;
6 | import java.util.function.Consumer;
7 |
8 | interface I1 {
9 | void do1();
10 | }
11 |
12 | interface I2 {
13 |
14 | void do2();
15 | }
16 |
17 | interface I3 extends I1, I2 {
18 | }
19 |
20 | class A implements I1 {
21 |
22 | @Override
23 | public void do1() {
24 |
25 | }
26 | }
27 |
28 | class B implements I3 {
29 |
30 | @Override
31 | public void do2() {
32 |
33 | }
34 |
35 | @Override
36 | public void do1() {
37 |
38 | }
39 | }
40 |
41 | class C implements I3 {
42 |
43 | @Override
44 | public void do2() {
45 |
46 | }
47 |
48 | @Override
49 | public void do1() {
50 |
51 | }
52 | }
53 |
54 | public class Main {
55 |
56 | public static void main(String[] args) {
57 | int numThreads = 2;
58 | int loopCount = 1_000_000_000;
59 | ExecutorService es = Executors.newFixedThreadPool(numThreads);
60 | Object a = new A();
61 | I3 b = new B();
62 | I3 c = new C();
63 | for (int i = 0; i != numThreads; i++) {
64 | es.submit(() -> {
65 | for (int j = 0; j != loopCount; j++) {
66 | foo(b);
67 | goo(b);
68 | consumeAsI2(I2::do2, b);
69 | castToI1(c);
70 | castToI2(c);
71 | consumeAsI2(I2::do2, c);
72 | tryCastTo(a, I2.class);
73 | }
74 | });
75 | }
76 | es.shutdown();
77 | }
78 |
79 | public static void tryCastTo(Object o, Class toCast) {
80 | if (toCast.isInstance(o)) {
81 | toCast.cast(o);
82 | }
83 | }
84 |
85 | public static boolean foo(I3 i) {
86 | return I1.class.isInstance(i);
87 | }
88 |
89 | public static boolean goo(I3 i) {
90 | // same line of code!!
91 | if (i instanceof I2 && I3.class.isAssignableFrom(i.getClass())) {
92 | return true;
93 | }
94 | return false;
95 | }
96 |
97 | public static void consumeAsI2(Consumer innocentConsumer, I2 i) {
98 | innocentConsumer.accept(i);
99 | }
100 |
101 | public static void castToI1(Object o) {
102 | I1.class.cast(o).do1();
103 | }
104 |
105 | public static void castToI2(Object o) {
106 | ((I2) o).do2();
107 | }
108 |
109 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | io.forked.franz
7 | type-pollution-parent
8 | 0.1-SNAPSHOT
9 | pom
10 |
11 |
12 | UTF-8
13 |
14 |
15 |
16 | example
17 | agent
18 | benchmarks
19 |
20 |
21 |
22 |
23 |
24 |
25 | maven-compiler-plugin
26 | 3.10.1
27 |
28 | 1.8
29 | 1.8
30 |
31 |
32 |
33 | maven-source-plugin
34 | 3.2.1
35 |
36 |
37 |
38 | jar-no-fork
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------