Skip to content

Commit 7e8a280

Browse files
authored
Omit instrumenting classes version pre Java 5 (#320)
1 parent e59fbe1 commit 7e8a280

File tree

7 files changed

+130
-15
lines changed

7 files changed

+130
-15
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
## Bug Fixes
66
* Update dsl-json which fixes a memory leak.
7-
See [ngs-doo/dsl-json#102](https://github.com/ngs-doo/dsl-json/pull/102) for details.
7+
See [ngs-doo/dsl-json#102](https://github.com/ngs-doo/dsl-json/pull/102) for details.
8+
* Avoid `VerifyError`s by non instrumenting classes compiled for Java 4 or earlier
89

910
# 1.0.1
1011

apm-agent-core/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,12 @@
132132
<artifactId>guava</artifactId>
133133
<version>27.0-android</version>
134134
</dependency>
135+
<!--Used to test excluding old class file versions-->
136+
<dependency>
137+
<groupId>commons-math</groupId>
138+
<artifactId>commons-math</artifactId>
139+
<version>1.0</version>
140+
<scope>test</scope>
141+
</dependency>
135142
</dependencies>
136143
</project>

apm-agent-core/src/main/java/co/elastic/apm/bci/ElasticApmAgent.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import co.elastic.apm.bci.bytebuddy.AnnotationValueOffsetMappingFactory;
2323
import co.elastic.apm.bci.bytebuddy.ErrorLoggingListener;
2424
import co.elastic.apm.bci.bytebuddy.MatcherTimer;
25+
import co.elastic.apm.bci.bytebuddy.MinimumClassFileVersionValidator;
2526
import co.elastic.apm.bci.bytebuddy.SimpleMethodSignatureOffsetMappingFactory;
2627
import co.elastic.apm.bci.bytebuddy.SoftlyReferencingTypePoolCache;
2728
import co.elastic.apm.configuration.CoreConfiguration;
@@ -34,6 +35,7 @@
3435
import net.bytebuddy.asm.Advice;
3536
import net.bytebuddy.description.method.MethodDescription;
3637
import net.bytebuddy.description.type.TypeDescription;
38+
import net.bytebuddy.dynamic.DynamicType;
3739
import net.bytebuddy.dynamic.scaffold.MethodGraph;
3840
import net.bytebuddy.dynamic.scaffold.TypeValidation;
3941
import net.bytebuddy.matcher.ElementMatcher;
@@ -196,7 +198,14 @@ public boolean matches(MethodDescription target) {
196198
}
197199
}, advice.getAdviceClass().getName())
198200
.include(ClassLoader.getSystemClassLoader())
199-
.withExceptionHandler(PRINTING));
201+
.withExceptionHandler(PRINTING))
202+
.transform(new AgentBuilder.Transformer() {
203+
@Override
204+
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
205+
ClassLoader classLoader, JavaModule module) {
206+
return builder.visit(MinimumClassFileVersionValidator.INSTANCE);
207+
}
208+
});
200209
}
201210

202211
private static MatcherTimer getOrCreateTimer(Class<? extends ElasticApmInstrumentation> adviceClass) {

apm-agent-core/src/main/java/co/elastic/apm/bci/bytebuddy/ErrorLoggingListener.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* Licensed under the Apache License, Version 2.0 (the "License");
88
* you may not use this file except in compliance with the License.
99
* You may obtain a copy of the License at
10-
*
10+
*
1111
* http://www.apache.org/licenses/LICENSE-2.0
12-
*
12+
*
1313
* Unless required by applicable law or agreed to in writing, software
1414
* distributed under the License is distributed on an "AS IS" BASIS,
1515
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -26,10 +26,15 @@
2626

2727
public class ErrorLoggingListener extends AgentBuilder.Listener.Adapter {
2828

29-
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingListener.class);
29+
private static final Logger logger = LoggerFactory.getLogger(ErrorLoggingListener.class);
3030

31-
@Override
32-
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
33-
logger.warn("ERROR on transformation " + typeName, throwable);
34-
}
31+
@Override
32+
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
33+
if (throwable instanceof MinimumClassFileVersionValidator.UnsupportedClassFileVersionException) {
34+
logger.warn("{} uses an unsupported class file version (pre Java 5) and can't be instrumented. " +
35+
"Consider updating to a newer version of that library.", typeName);
36+
} else {
37+
logger.warn("ERROR on transformation " + typeName, throwable);
38+
}
39+
}
3540
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package co.elastic.apm.bci.bytebuddy;
2+
3+
import net.bytebuddy.ClassFileVersion;
4+
import net.bytebuddy.asm.AsmVisitorWrapper;
5+
import net.bytebuddy.description.field.FieldDescription;
6+
import net.bytebuddy.description.field.FieldList;
7+
import net.bytebuddy.description.method.MethodList;
8+
import net.bytebuddy.description.type.TypeDescription;
9+
import net.bytebuddy.implementation.Implementation;
10+
import net.bytebuddy.jar.asm.ClassVisitor;
11+
import net.bytebuddy.pool.TypePool;
12+
import net.bytebuddy.utility.OpenedClassReader;
13+
14+
public enum MinimumClassFileVersionValidator implements AsmVisitorWrapper {
15+
16+
INSTANCE;
17+
18+
private static final ClassFileVersion MINIMUM_CLASS_FILE_VERSION = ClassFileVersion.JAVA_V5;
19+
20+
@Override
21+
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor, Implementation.Context implementationContext,
22+
TypePool typePool, FieldList<FieldDescription.InDefinedShape> fields, MethodList<?> methods, int writerFlags, int readerFlags) {
23+
return new ClassVisitor(OpenedClassReader.ASM_API, classVisitor) {
24+
@Override
25+
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
26+
final ClassFileVersion classFileVersion = ClassFileVersion.ofMinorMajor(version);
27+
if (!classFileVersion.isAtLeast(MINIMUM_CLASS_FILE_VERSION)) {
28+
throw UnsupportedClassFileVersionException.INSTANCE;
29+
}
30+
super.visit(version, access, name, signature, superName, interfaces);
31+
}
32+
};
33+
}
34+
35+
@Override
36+
public int mergeWriter(int flags) {
37+
return flags;
38+
}
39+
40+
@Override
41+
public int mergeReader(int flags) {
42+
return flags;
43+
}
44+
45+
public static class UnsupportedClassFileVersionException extends RuntimeException {
46+
static final UnsupportedClassFileVersionException INSTANCE = new UnsupportedClassFileVersionException();
47+
48+
private UnsupportedClassFileVersionException() {
49+
// singleton
50+
}
51+
52+
/*
53+
* avoids the expensive creation of the stack trace which is not needed
54+
*/
55+
@Override
56+
public synchronized Throwable fillInStackTrace()
57+
{
58+
return this;
59+
}
60+
}
61+
}

apm-agent-core/src/test/java/co/elastic/apm/bci/InstrumentationTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020
package co.elastic.apm.bci;
2121

22+
import co.elastic.apm.MockTracer;
2223
import co.elastic.apm.configuration.CoreConfiguration;
2324
import co.elastic.apm.configuration.SpyConfiguration;
2425
import co.elastic.apm.impl.ElasticApmTracerBuilder;
@@ -28,6 +29,7 @@
2829
import net.bytebuddy.description.type.TypeDescription;
2930
import net.bytebuddy.matcher.ElementMatcher;
3031
import net.bytebuddy.matcher.ElementMatchers;
32+
import org.apache.commons.math.util.MathUtils;
3133
import org.junit.jupiter.api.AfterEach;
3234
import org.junit.jupiter.api.Test;
3335
import org.stagemonitor.configuration.ConfigurationRegistry;
@@ -59,6 +61,17 @@ void testDisabled() {
5961
assertThat(interceptMe()).isEmpty();
6062
}
6163

64+
@Test
65+
void testDontInstrumentOldClassFileVersions() {
66+
ElasticApmAgent.initInstrumentation(MockTracer.create(),
67+
ByteBuddyAgent.install(),
68+
Collections.singletonList(new MathInstrumentation()));
69+
// if the instrumentation applied, it would return 42
70+
// but instrumenting old class file versions could lead to VerifyErrors in some cases and possibly some more shenanigans
71+
// so we we are better off not touching Java 1.4 code at all
72+
assertThat(MathUtils.sign(-42)).isEqualTo(-1);
73+
}
74+
6275
private void init(ConfigurationRegistry config) {
6376
ElasticApmAgent.initInstrumentation(new ElasticApmTracerBuilder()
6477
.configurationRegistry(config)
@@ -92,4 +105,26 @@ public Collection<String> getInstrumentationGroupNames() {
92105
return Collections.singleton("test");
93106
}
94107
}
108+
109+
public static class MathInstrumentation extends ElasticApmInstrumentation {
110+
@Advice.OnMethodExit
111+
public static void onMethodExit(@Advice.Return(readOnly = false) int returnValue) {
112+
returnValue = 42;
113+
}
114+
115+
@Override
116+
public ElementMatcher<? super TypeDescription> getTypeMatcher() {
117+
return ElementMatchers.named("org.apache.commons.math.util.MathUtils");
118+
}
119+
120+
@Override
121+
public ElementMatcher<? super MethodDescription> getMethodMatcher() {
122+
return ElementMatchers.named("sign").and(ElementMatchers.takesArguments(int.class));
123+
}
124+
125+
@Override
126+
public Collection<String> getInstrumentationGroupNames() {
127+
return Collections.emptyList();
128+
}
129+
}
95130
}

docs/troubleshooting.asciidoc

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,12 @@ Also make sure to configure your firewalls so that the host the agent runs on ca
150150
[[trouble-shooting-old-jdbc-drivers]]
151151
==== Libraries compiled against old Java versions
152152

153-
If you are seeing exceptions like these in your application,
153+
If you are seeing warning like these in your application,
154154
it means that you are using a library which has been compiled for a very old version of Java:
155155

156156
----
157-
java.lang.VerifyError: (class: org/apache/commons/dbcp/DelegatingStatement, method: executeQuery signature: (Ljava/lang/String;)Ljava/sql/ResultSet;) Illegal type in constant pool
158-
----
159-
160-
----
161-
java.lang.IllegalStateException: Cannot write type to constant pool for class file version Java 2
157+
org.apache.commons.dbcp.DelegatingStatement uses an unsupported class file version (pre Java 5) and can't be instrumented.
158+
Consider updating to a newer version of that library.
162159
----
163160

164161
That mostly concerns JDBC drivers.

0 commit comments

Comments
 (0)