-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Byte code instrumentation fix #449
Conversation
…soft/ApplicationInsights-Java into ByteCodeInstrumentationFix
…cle and sqlserver instrumentation
@@ -153,6 +153,8 @@ public static String getAgentJarLocation() throws UnsupportedEncodingException { | |||
} | |||
|
|||
String path = AgentImplementation.class.getProtectionDomain().getCodeSource().getLocation().getPath(); | |||
int index = path.lastIndexOf('/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it always in this notation across the platforms or sometimes can be '\'?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Dmitry-Matveev It's a good question. I have an idea for 2 platforms I have tested so far. Java 8 has by default class loader of URLClassLoader type so it won't hit this part of the class .Java 9 has default classloader called "app" which is not of the type URLClassLoader and hence hits this part and only works for this change. I am not sure about other JVM's though.
do { | ||
class1 = class1.getSuperclass(); | ||
} | ||
while (!(class1.isAssignableFrom(class2))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any possible issue with endless cycle or exception here if class1 has no more superClass and class2 happens to be not assignable (e.g. null or something...)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Dmitry-Matveev to be specific about this, this is the exact implementation as how ASM does by default. Only reason to override this method was because ASM uses Class.getClassLoader() method to load the current class and therefore it gets the ClassLoader which doesn't have all the required classes loaded in it. In this override I pass the ClassLoader from the Callee in order to avoid the lookup and have the classloader which has all the required classes loaded. Therefore I believe that since there is minimal change done from ASM implementation this possibility is very narrow. Is there a way we can test this? How can we know if there is a null being passed or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 To test this method, you could refactor it into a public static
utility method which takes the ClassLoader along with the other parameters and write tests against that.
@@ -243,7 +251,8 @@ private ClassVisitorFactory classFactoryForSqlServer() { | |||
@Override | |||
public ClassVisitor create(ClassInstrumentationData classInstrumentationData, ClassWriter classWriter) { | |||
HashSet<String> ctorSignatures = new HashSet<String>(); | |||
ctorSignatures.add("(Lcom/microsoft/sqlserver/jdbc/SQLServerConnection;Ljava/lang/String;II)V"); | |||
ctorSignatures.add("(Lcom/microsoft/sqlserver/jdbc/SQLServerConnection;Ljava/lang/String;" + | |||
"IILcom/microsoft/sqlserver/jdbc/SQLServerStatementColumnEncryptionSetting;)V"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it the same parameter across all SQL calls we'd need to monitor or specific to some version? The previous signature looked more generic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the default signature of the constructor for JDBC driver version 6.2 is the one I changed. With the previous signature we were missing the constructor instrumentation. Now with the older drivers it might well be possible that signature was one mentioned before but nonetheless I believe enterprise can upgrade to latest JDBC drivers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 Does this limit the supported driver versions?
byte[] result = tested.transform(mockArray, "mock"); | ||
assertSame(result, mockArray); | ||
} | ||
// @Test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we delete the test if no longer relevant or uncomment and fix if it still is?
…thout assert in ClassInstrumentationDataTest and removing unused imports
@Dmitry-Matveev I have fixed the test for DefaultByteCodeTransformerTest and commented the tests which do not have any asserts. They just add to build time as they are not verifying anything for now. |
@@ -153,6 +153,8 @@ public static String getAgentJarLocation() throws UnsupportedEncodingException { | |||
} | |||
|
|||
String path = AgentImplementation.class.getProtectionDomain().getCodeSource().getLocation().getPath(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use https://docs.oracle.com/javase/8/docs/api/java/nio/file/Paths.html instead of string?
@@ -153,6 +153,8 @@ public static String getAgentJarLocation() throws UnsupportedEncodingException { | |||
} | |||
|
|||
String path = AgentImplementation.class.getProtectionDomain().getCodeSource().getLocation().getPath(); | |||
int index = path.lastIndexOf('/'); | |||
path = path.substring(0, index + 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check that index is not -1
. Just in case so code will not throw. Also see above - using Path instead of string will help
|
||
import org.objectweb.asm.ClassWriter; | ||
|
||
public class CustomClassWriter extends ClassWriter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add comment. And make sure you need it to be public
this.classLoader = loader; | ||
} | ||
|
||
protected String getCommonSuperClass(String className1, String className2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment for this method.
private final static String MOCK_SIGNATURE_1 = "Signature1"; | ||
private final static String MOCK_SIGNATURE_2 = "Signature2"; | ||
private final static String MOCK_SIGNATURE_3 = "Signature3"; | ||
// private final static String MOCK_METHOD = "Method"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove instead of commenting. BTW, why commenting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commenting because this signature was used in tests which were relying on getDecisionMethod() however it turns out that there is no such method in ClassInstrumentationData class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be if there is an idea to get this tests running in future, that was the reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if you uncomment these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please address these tests. If they are failing, they should be updated to reflect the new functionality. If they are no longer valid, they should be removed (and explain why this is the case).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@littleaj all the asserts within this tests are commented because there is a method which they intended to write is missing. Therefore there is no use to create unnecessary objects during test run time. It will add overhead only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 as discussed, let's just revert this to the master copy and we'll come back to it later. Then, we'll figure out if it needs to be removed or modified so it works.
gradle/common-java.gradle
Outdated
@@ -40,8 +40,8 @@ if (java6JreDir) { | |||
bootClasspath += "$archivePath;" | |||
} | |||
tasks.withType(JavaCompile) { | |||
sourceCompatibility = 1.6 | |||
targetCompatibility = 1.6 | |||
sourceCompatibility = 1.8 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you dropped 1.7 support?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it so because sometimes in some situations it the 1.7 target code would not produce the desired exceptions because these changes were mandated from JDK 1.8. Also, we need to decide till what point do we need to support backward compatibility in the SDK. We might not be able to use all the new features of JDK 8 like automatic inference of types in <> operator, passing of functions using lambda expressions etc which is modern java.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If required no harm in reverting it back if things work fine. However at one point we need to make this decision.
return explainSB; | ||
} | ||
} | ||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what changed in this file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@grlima to comment more on this. At-least my intuition here was that while sending telemetry for SqlServer there are only two arguments that come in the methoddata object whereas previously there was a strict condition which checked arguments should be more than or equal to 3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 can you fix the diff here? It will be better for the history to have only the changes in the diff. Glancing at the first 100 lines, I couldn't see any changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can see the difference here: git diff master -- CoreAgentNotificationsHandler.java > CoreAgentNotificationsHandler.java.diff
(assuming PWD is the file's location). It's the line endings. Your IDE should be set to preserve the existing line ending characters. There may be a way to fix this in IntelliJ, but you may have to load it in Notepad++ an do a find/replace.
If you want to just checkout a clean copy and make the changes again, add use this command to see the "actual" changes: git diff master -w -- CoreAgentNotificationsHandler.java > CoreAgentNotificationsHandler.java.diff
to ignore all whitespace changes
@SergeyKanzhelev I have addressed most of the concerns including using the Paths class for locating the agent jar, adding comments and also making CustomClassWriter package private instead of public. |
if (stringPath.charAt(0) == '/') { | ||
stringPath = stringPath.substring(1); | ||
} | ||
Path path = Paths.get(stringPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The java.nio.*
classes were introduced in 1.7. It's my understanding we still want compatibility with 1.6. If that's still the case, this needs to be reworked. @grlima, can you confirm?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see tests which validate your changes.
return explainSB; | ||
} | ||
} | ||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 can you fix the diff here? It will be better for the history to have only the changes in the diff. Glancing at the first 100 lines, I couldn't see any changes.
gradle/common-java.gradle
Outdated
@@ -40,8 +40,8 @@ if (java6JreDir) { | |||
bootClasspath += "$archivePath;" | |||
} | |||
tasks.withType(JavaCompile) { | |||
sourceCompatibility = 1.6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be a breaking change for some users. I think we should wait until we have a more comprehensive plan before changing compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.
@@ -27,10 +27,10 @@ | |||
|
|||
public final class DefaultByteCodeTransformerTest { | |||
@Test | |||
public void noClassInstrumentationDataTest() { | |||
public void noClassInstrumentationDataTest() throws ClassNotFoundException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't look like you need throws
here. transform
doesn't throw anything. Test methods only need throws
declarations to satisfy the compiler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure this is by mistake. I think there is any need of throws too.
do { | ||
class1 = class1.getSuperclass(); | ||
} | ||
while (!(class1.isAssignableFrom(class2))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 To test this method, you could refactor it into a public static
utility method which takes the ClassLoader along with the other parameters and write tests against that.
private final static String MOCK_SIGNATURE_1 = "Signature1"; | ||
private final static String MOCK_SIGNATURE_2 = "Signature2"; | ||
private final static String MOCK_SIGNATURE_3 = "Signature3"; | ||
// private final static String MOCK_METHOD = "Method"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if you uncomment these tests?
@@ -243,7 +251,8 @@ private ClassVisitorFactory classFactoryForSqlServer() { | |||
@Override | |||
public ClassVisitor create(ClassInstrumentationData classInstrumentationData, ClassWriter classWriter) { | |||
HashSet<String> ctorSignatures = new HashSet<String>(); | |||
ctorSignatures.add("(Lcom/microsoft/sqlserver/jdbc/SQLServerConnection;Ljava/lang/String;II)V"); | |||
ctorSignatures.add("(Lcom/microsoft/sqlserver/jdbc/SQLServerConnection;Ljava/lang/String;" + | |||
"IILcom/microsoft/sqlserver/jdbc/SQLServerStatementColumnEncryptionSetting;)V"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhaval24 Does this limit the supported driver versions?
…me, reverting Path API use in Agent Impl, adding public parameterized constructor for spring boot support, updating build script for web allows shadow jar
@SergeyKanzhelev @littleaj @Dmitry-Matveev @grlima I have pushed changes in this PR which are now addressing all the possible bytecode concerns we have seen till the date. As far as unit tests are concerned I still do not have a way to perform tests and our agent code does not have ones till the date, however this is a blocking issue and we need to get it fixed. I have done end to end testing in several test application and changes work fine. The CustomClass Loader now performs the commonSuperClass lookup without actually loading the class. The PR also introduces changes in the Application-Insights web gradle file to include shadow jar plugin hence eliminating class path hell issues and also introduces a public constructor in the WebRequestTracking filter to address SpringBoot solutions as discussed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't seen anything addressed from my last review. Let me know if you have any questions about my comments.
} | ||
} | ||
} catch (Throwable throwable) { | ||
InternalAgentLogger.INSTANCE.logAlways(InternalAgentLogger.LoggingLevel.ERROR,"Unable to find the Agent Jar"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove this extra log message. It's going to the same logger, same level and there's no new information here. Feel free to update the wording in the log message on the next line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay thanks will do this.
* @return The String for the common super class of both the classes | ||
*/ | ||
@Override | ||
protected String getCommonSuperClass(final String type1, final String type2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the very least this method should have a unit test, if not the whole class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I will get couple of them.
* provided by DefaultByteCode transformer (This loader essentially has all | ||
* the required classes already loaded) | ||
*/ | ||
public class CustomClassWriterv2 extends org.objectweb.asm.ClassWriter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of SomeClassV2 naming schemes. If the modifications can't be described in one or two words, the go with "branding" it; e.g. AppInsightsCustomClassWriter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be just refactored as CustomClassWriter I will do that.
@@ -51,7 +52,7 @@ public void visit(int version, int access, String name, String signature, String | |||
@Override | |||
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { | |||
MethodVisitor originalMV = super.visitMethod(access, name, desc, signature, exceptions); | |||
|
|||
originalMV = new JSRInlinerAdapter(originalMV, access, name, desc, signature, exceptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The next line checks for null
, which will now always be false. Either that part of the condition is no longer valid and should be removed, or it should be relocated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well yes I think can be moved one step above may be.
private final static String MOCK_SIGNATURE_1 = "Signature1"; | ||
private final static String MOCK_SIGNATURE_2 = "Signature2"; | ||
private final static String MOCK_SIGNATURE_3 = "Signature3"; | ||
// private final static String MOCK_METHOD = "Method"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please address these tests. If they are failing, they should be updated to reflect the new functionality. If they are no longer valid, they should be removed (and explain why this is the case).
gradle/common-java.gradle
Outdated
@@ -40,8 +40,8 @@ if (java6JreDir) { | |||
bootClasspath += "$archivePath;" | |||
} | |||
tasks.withType(JavaCompile) { | |||
sourceCompatibility = 1.6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.
…in one test case, refractoring name, removing unnecessary null check
Folks I have updated the last missing piece on my end which was updating changelog file. If there is anything else which is left let me know so we that we can then merge this. |
Fix #428, #418, #286, #277, #276 , #365
This pull request fixes the java.lang.Verify error which was being caused in JDK version 1.7 onwards because of improper instrumentation. Changes here facilitate the creation of StackMap frame (a datastructure introduced in JDK 1.7 and mandated in 1.8), fixes constructor instrumentation issue along with improper constructor signature for SQLServer.
#276 mentions issues with HTTPCLIENT 42, however on current testing things work fine for HTTPCLIENT 43 and since its more secure it is advisable to upgrade to this client.
This pull request also upgrades the ASM version to ASM 5.2
For significant contributions please make sure you have completed the following items: