Skip to content
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

Merged
merged 19 commits into from
Nov 27, 2017
Merged

Byte code instrumentation fix #449

merged 19 commits into from
Nov 27, 2017

Conversation

dhaval24
Copy link
Contributor

@dhaval24 dhaval24 commented Oct 20, 2017

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:

  • Design discussion issue #
  • Changes in public surface reviewed
  • CHANGELOG.md updated

@@ -153,6 +153,8 @@ public static String getAgentJarLocation() throws UnsupportedEncodingException {
}

String path = AgentImplementation.class.getProtectionDomain().getCodeSource().getLocation().getPath();
int index = path.lastIndexOf('/');
Copy link
Member

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 '\'?

Copy link
Contributor Author

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)));
Copy link
Member

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...)?

Copy link
Contributor Author

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?

Copy link
Contributor

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");
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Member

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
@dhaval24
Copy link
Contributor Author

@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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -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);
Copy link
Contributor

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 {
Copy link
Contributor

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)
Copy link
Contributor

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";
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor

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).

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@@ -40,8 +40,8 @@ if (java6JreDir) {
bootClasspath += "$archivePath;"
}
tasks.withType(JavaCompile) {
sourceCompatibility = 1.6
targetCompatibility = 1.6
sourceCompatibility = 1.8
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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;
}
}
/*
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@littleaj this file was edited by @grlima when prepared statements for sqlserver where not working (i.e not capturing the query data) we can no revert this changes back because we no longer need to loose if condition in the sendSQLTelemetry() method.

Copy link
Contributor

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

@dhaval24
Copy link
Contributor Author

@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);
Copy link
Contributor

@littleaj littleaj Oct 25, 2017

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?

Copy link
Contributor

@littleaj littleaj left a 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;
}
}
/*
Copy link
Contributor

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.

@@ -40,8 +40,8 @@ if (java6JreDir) {
bootClasspath += "$archivePath;"
}
tasks.withType(JavaCompile) {
sourceCompatibility = 1.6
Copy link
Contributor

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.

Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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)));
Copy link
Contributor

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";
Copy link
Contributor

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");
Copy link
Contributor

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
@dhaval24
Copy link
Contributor Author

@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.

Copy link
Contributor

@littleaj littleaj left a 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");
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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";
Copy link
Contributor

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).

@@ -40,8 +40,8 @@ if (java6JreDir) {
bootClasspath += "$archivePath;"
}
tasks.withType(JavaCompile) {
sourceCompatibility = 1.6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.

@dhaval24 dhaval24 added this to the 1.0.11 milestone Nov 17, 2017
@dhaval24 dhaval24 self-assigned this Nov 17, 2017
…in one test case, refractoring name, removing unnecessary null check
@dhaval24
Copy link
Contributor Author

@littleaj I have addressed your concerns in the previous review except the diff in CoreAgentNotification Handler. In my IDE git diff shows no difference so will have to figure it out. @grlima had pushed those changes so if you can have a look @grlima would be great.

@dhaval24
Copy link
Contributor Author

Thanks Sergey for your comment. @littleaj and @grlima I am waiting for your approval to merge this into master. Please have a look.

@dhaval24
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Agent cannot capture oracle dependency calls to Insert statements
6 participants