From 91c5107b1aa23bae58f9df2c6fbb3017716c4d9f Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Wed, 23 Aug 2023 16:22:32 +0100 Subject: [PATCH 01/15] chore(tests): add unit tests for agent --- .idea/misc.xml | 1 - DEVELOPMENT.md | 14 ++ LICENSING.md | 22 ++ README.md | 8 +- .../deep/agent/api/auth/AuthProvider.java | 18 +- .../agent/api/reflection/IReflection.java | 8 + .../agent/api/reflection/ReflectionUtils.java | 51 +++++ .../api/resource/ResourceAttributes.java | 15 +- .../deep/agent/api/settings/ISettings.java | 10 + .../deep/agent/api/auth/AuthProviderTest.java | 69 ++++++ .../agent/api/auth/BasicAuthProviderTest.java | 67 ++++++ .../agent/api/utils/ArrayIteratorTest.java | 38 ++++ .../api/utils/ArrayObjectIteratorTest.java | 38 ++++ .../agent/api/utils/CompoundIteratorTest.java | 47 ++++ agent/pom.xml | 6 + .../com/intergral/deep/agent/AgentImpl.java | 9 + .../com/intergral/deep/agent/DeepAgent.java | 2 +- .../com/intergral/deep/agent/IDUtils.java | 20 +- .../intergral/deep/agent/ReflectionUtils.java | 18 +- .../java/com/intergral/deep/agent/Utils.java | 49 ++++- .../deep/agent/grpc/GrpcService.java | 6 +- .../deep/agent/plugins/PluginLoader.java | 9 +- .../deep/agent/poll/DriftAwareThread.java | 10 +- .../deep/agent/poll/LongPollService.java | 7 +- .../intergral/deep/agent/push/PushUtils.java | 8 +- .../agent/resource/JavaResourceDetector.java | 35 +++ .../deep/agent/resource/ResourceDetector.java | 48 ++--- .../deep/agent/resource/SpiUtil.java | 16 +- .../deep/agent/settings/Settings.java | 6 +- .../agent/tracepoint/TracepointUtils.java | 10 +- .../tracepoint/handler/VariableProcessor.java | 2 +- ...ergral.deep.agent.api.spi.ResourceProvider | 18 ++ .../com/intergral/deep/agent/AgentTest.java | 49 +++++ .../com/intergral/deep/agent/IDUtilsTest.java | 31 +++ .../com/intergral/deep/agent/UtilsTest.java | 91 ++++++++ .../deep/agent/grpc/GrpcServiceTest.java | 170 +++++++++++++++ .../deep/agent/plugins/PluginLoaderTest.java | 71 ++++++ .../deep/agent/poll/DriftAwareThreadTest.java | 116 ++++++++++ .../deep/agent/poll/LongPollServiceTest.java | 204 ++++++++++++++++++ .../deep/agent/push/PushServiceTest.java | 94 ++++++++ .../deep/agent/push/PushUtilsTest.java | 105 +++++++++ .../agent/resource/ResourceDetectorTest.java | 59 +++++ .../deep/agent/resource/SpiUtilTest.java | 49 +++++ .../deep/agent/settings/SettingsTest.java | 65 ++++++ .../deep/test/MockEventSnapshot.java | 79 +++++++ agent/src/test/resources/logging.properties | 3 + examples/.gitignore | 2 +- examples/docker/coldfusion/.gitignore | 2 - examples/docker/coldfusion/jvm.config | 29 +++ examples/docker/coldfusion/testFile.cfm | 3 + .../com/intergral/deep/examples/Main.java | 2 +- pom.xml | 10 + .../deep/reflect/ReflectionImpl.java | 33 ++- .../deep/reflect/Java9ReflectionImpl.java | 14 ++ test-utils/pom.xml | 6 +- .../com/intergral/deep/tests/AssertUtils.java | 42 ++++ .../deep/tests/grpc/TestInterceptor.java | 53 +++++ 57 files changed, 1919 insertions(+), 148 deletions(-) create mode 100644 DEVELOPMENT.md create mode 100644 LICENSING.md create mode 100644 agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/auth/BasicAuthProviderTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayIteratorTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayObjectIteratorTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java create mode 100644 agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java create mode 100644 agent/src/main/resources/META-INF/services/com.intergral.deep.agent.api.spi.ResourceProvider create mode 100644 agent/src/test/java/com/intergral/deep/agent/AgentTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/IDUtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/UtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/poll/DriftAwareThreadTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/push/PushUtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/resource/ResourceDetectorTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/resource/SpiUtilTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java create mode 100644 agent/src/test/resources/logging.properties delete mode 100644 examples/docker/coldfusion/.gitignore create mode 100644 examples/docker/coldfusion/jvm.config create mode 100644 examples/docker/coldfusion/testFile.cfm create mode 100644 test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java create mode 100644 test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java diff --git a/.idea/misc.xml b/.idea/misc.xml index c723228..612ec51 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..4528416 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,14 @@ +# Development + +Here we have a few hints on how to develop this module. + +## CF Debugging + +To debug in CF I found the easiest way is to use the docker. We can do this with this command: + +```bash +docker run --name cf_2018 --rm -p 8500:8500 -e acceptEULA=YES -e password:admin -e JAVA_OPTS="-Ddeep.service.url=172.17.0.1:43315 -Ddeep.logging.level=FINE -Ddeep.service.secure=false -Ddeep.transform.path=/opt/dispath" -v ${PWD}/dispath:/opt/dispath -v ${PWD}/agent/target/agent-1.0-SNAPSHOT.jar:/opt/deep/deep.jar ghcr.io/intergral/deep:coldfusion +``` + +Changing the -v paths to point to the locations on your machine. You also need to use the debug listen config ("Listen for Docker debug +connections") that should be available with this project in idea. \ No newline at end of file diff --git a/LICENSING.md b/LICENSING.md new file mode 100644 index 0000000..57164d4 --- /dev/null +++ b/LICENSING.md @@ -0,0 +1,22 @@ +# Licensing + +License names used in this document are as per [SPDX License List](https://spdx.org/licenses/). + +The default license for this project is [AGPL-3.0-only](LICENSE). + +## Apache-2.0 + +The following folders and their subfolders are licensed under Apache-2.0: + +``` + +``` + +The following file or directories and their subdirectories are licensed under their original upstream licenses: + +``` +agent/src/main/java/com/intergral/deep/agent/resource +agent/src/main/java/com/intergral/deep/agent/IDUtils.java +agent-api/src/main/java/com/intergral/deep/agent/api/resource +agent-api/src/main/java/com/intergral/deep/agent/api/spi +``` \ No newline at end of file diff --git a/README.md b/README.md index 8b8295e..c32a043 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,7 @@ Deep.start() There are a couple of examples [available here](./examples/README.md). -## CF Debugging -To debug in CF I found the easiest way is to use the docker. We can do this with this command: -```bash -docker run --name cf_2018 --rm -p 8500:8500 -p 8088:8088 -p 5005:5005 -e STRIP_STD=true -e FR_ENABLED=false -e NV_ENABLED=false -e JAVA_OPTS="-javaagent:/opt/deep/deep.jar -Ddeep.service.url=172.17.0.1:43315 -Ddeep.logging.level=FINE -Ddeep.service.secure=false -Ddeep.transform.path=/opt/dispath -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005" -v ${PWD}/dispath:/opt/dispath -v ${PWD}/agent/target/agent-1.0-SNAPSHOT.jar:/opt/deep/deep.jar registry.gitlab.com/intergral/docker/servers/coldfusion:2018 -``` +## Licensing -Changing the -v paths to point to the locations on your machine. You also need to use the debug listen config ("Listen for Docker debug connections") that should be available with this project in idea. \ No newline at end of file +For licensing info please see [LICENSING.md](./LICENSING.md) diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java index 4a4227d..f0abd0d 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java @@ -19,9 +19,10 @@ import com.intergral.deep.agent.api.DeepRuntimeException; import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.reflection.IReflection; +import com.intergral.deep.agent.api.reflection.ReflectionUtils; import com.intergral.deep.agent.api.settings.ISettings; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.Map; @@ -29,7 +30,7 @@ public class AuthProvider { private static final NoopProvider NOOP_PROVIDER = new NoopProvider(); - public static IAuthProvider provider(final ISettings settings) { + public static IAuthProvider provider(final ISettings settings, final IReflection reflection) { final String serviceAuthProvider = settings.getSettingAs("service.auth.provider", String.class); if (serviceAuthProvider == null || serviceAuthProvider.trim().isEmpty()) { return NOOP_PROVIDER; @@ -48,19 +49,14 @@ public static IAuthProvider provider(final ISettings settings) { try { final Class aClass = Class.forName(serviceAuthProvider); - final Constructor constructor = aClass.getConstructor(ISettings.class); - final Object newInstance = constructor.newInstance(settings); - return (IAuthProvider) newInstance; - } catch (ClassNotFoundException - | NoSuchMethodException - | InvocationTargetException - | InstantiationException - | IllegalAccessException e) { + final Constructor constructor = ReflectionUtils.findConstructor(aClass, reflection); + return ReflectionUtils.callConstructor(constructor, settings, reflection); + } catch (ClassNotFoundException e) { throw new RuntimeException(String.format("Cannot load auth provider %s", serviceAuthProvider), e); } } - private static class NoopProvider implements IAuthProvider { + static class NoopProvider implements IAuthProvider { @Override public Map provide() { diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/IReflection.java b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/IReflection.java index f92ebfc..3debea1 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/IReflection.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/IReflection.java @@ -17,6 +17,8 @@ package com.intergral.deep.agent.api.reflection; +import com.intergral.deep.agent.api.DeepRuntimeException; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Iterator; @@ -32,6 +34,8 @@ public interface IReflection { boolean setAccessible(final Class clazz, final Method method); + boolean setAccessible(final Class clazz, final Constructor constructor); + T callMethod(Object target, String methodName, Object... args); @@ -57,4 +61,8 @@ public interface IReflection { T callField(Object target, Field field); Set getModifiers(Field field); + + Constructor findConstructor(final Class clazz, final Class... args); + + T callConstructor(final Constructor constructor, final Object... args) throws DeepRuntimeException; } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java new file mode 100644 index 0000000..cd6af61 --- /dev/null +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.reflection; + +import com.intergral.deep.agent.api.DeepRuntimeException; +import com.intergral.deep.agent.api.settings.ISettings; +import java.lang.reflect.Constructor; + +public class ReflectionUtils { + + public static T callConstructor(final Constructor constructor, final ISettings settings, final IReflection reflection) { + if (constructor.getParameterTypes().length == 0) { + return reflection.callConstructor(constructor); + } + return reflection.callConstructor(constructor, settings); + } + + public static Constructor findConstructor(final Class aClass, final IReflection reflection) { + + final Constructor constructor = reflection.findConstructor(aClass, ISettings.class); + if (constructor != null) { + return constructor; + } + + final Constructor defaultConstructor = reflection.findConstructor(aClass); + if (defaultConstructor != null) { + return defaultConstructor; + } + final String simpleName = aClass.getSimpleName(); + throw new DeepRuntimeException( + String.format("Cannot create auth provider of type: %s. Class is missing constructor %s(%s) or %s().", aClass.getName(), + simpleName, ISettings.class.getName(), simpleName)); + + } + +} diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java index 98872d9..c519721 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java @@ -1,17 +1,6 @@ /* - * Copyright 2023 Intergral GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 */ package com.intergral.deep.agent.api.resource; diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java index 8fa00c0..f0d791a 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java @@ -38,6 +38,16 @@ public interface ISettings { */ String KEY_SERVICE_URL = "service.url"; + /** + * This is the setting key for the service secure setting + */ + String KEY_SERVICE_SECURE = "service.secure"; + + /** + * This is the setting key for the plugin list + */ + String PLUGINS = "plugins"; + T getSettingAs(String key, Class clazz); Map getMap(String attributeProperty); diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java new file mode 100644 index 0000000..fc232bc --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.auth; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.intergral.deep.agent.api.auth.AuthProvider.NoopProvider; +import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; +import com.intergral.deep.agent.api.reflection.IReflection; +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import java.util.Collections; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class AuthProviderTest { + + @Test + void canProvide() { + final ISettings settings = Mockito.mock(ISettings.class); + final IReflection reflection = Mockito.mock(IReflection.class); + final IAuthProvider provider = AuthProvider.provider(settings, reflection); + + assertEquals(provider.getClass(), NoopProvider.class); + } + + @Test + void canLoadProviderByName() { + final ISettings settings = Mockito.mock(ISettings.class); + Mockito.doReturn(MockAuthProviderPlugin.class.getName()).when(settings).getSettingAs("service.auth.provider", String.class); + final IReflection reflection = Mockito.mock(IReflection.class); + Mockito.when(reflection.findConstructor(Mockito.any(), Mockito.any())) + .thenAnswer(invocationOnMock -> MockAuthProviderPlugin.class.getConstructor()); + Mockito.when(reflection.callConstructor(Mockito.any())).thenReturn(new MockAuthProviderPlugin()); + final IAuthProvider provider = AuthProvider.provider(settings, reflection); + + assertEquals(MockAuthProviderPlugin.class, provider.getClass()); + } + + public static class MockAuthProviderPlugin implements IPlugin, IAuthProvider { + + @Override + public Resource decorate(final ISettings settings, final ISnapshotContext snapshot) { + return Resource.create(Collections.singletonMap("test", "provider")); + } + + @Override + public Map provide() { + return Collections.singletonMap("test", "provider"); + } + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/auth/BasicAuthProviderTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/BasicAuthProviderTest.java new file mode 100644 index 0000000..5acedc1 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/BasicAuthProviderTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.auth; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.intergral.deep.agent.api.settings.ISettings; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class BasicAuthProviderTest { + + @Test + void canProvide() { + final ISettings settings = Mockito.mock(ISettings.class); + final BasicAuthProvider basicAuthProvider = new BasicAuthProvider(settings); + + assertEquals(0, basicAuthProvider.provide().size()); + } + + @Test + void canProvide_settings() { + final ISettings settings = Mockito.mock(ISettings.class); + Mockito.doReturn("username").doReturn("password").when(settings).getSettingAs(Mockito.anyString(), Mockito.any()); + final BasicAuthProvider basicAuthProvider = new BasicAuthProvider(settings); + + final Map provide = basicAuthProvider.provide(); + assertEquals(1, provide.size()); + assertEquals("basic%20dXNlcm5hbWU6cGFzc3dvcmQ=", provide.get("authorization")); + } + + @Test + void canProvide_missingUsername() { + final ISettings settings = Mockito.mock(ISettings.class); + Mockito.doReturn(null).doReturn("password").when(settings).getSettingAs(Mockito.anyString(), Mockito.any()); + final BasicAuthProvider basicAuthProvider = new BasicAuthProvider(settings); + + final Map provide = basicAuthProvider.provide(); + assertEquals(0, provide.size()); + } + + @Test + void canProvide_missingPassword() { + final ISettings settings = Mockito.mock(ISettings.class); + Mockito.doReturn("username").doReturn(null).when(settings).getSettingAs(Mockito.anyString(), Mockito.any()); + final BasicAuthProvider basicAuthProvider = new BasicAuthProvider(settings); + + final Map provide = basicAuthProvider.provide(); + assertEquals(0, provide.size()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayIteratorTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayIteratorTest.java new file mode 100644 index 0000000..6046624 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayIteratorTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.utils; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ArrayIteratorTest { + + @Test + void next() { + final ArrayIterator iterator = new ArrayIterator<>(new String[]{"one", "two", "three"}); + + assertTrue(iterator.hasNext()); + assertEquals("one", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("two", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("three", iterator.next()); + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayObjectIteratorTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayObjectIteratorTest.java new file mode 100644 index 0000000..29bb34c --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/ArrayObjectIteratorTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.utils; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ArrayObjectIteratorTest { + + @Test + void next() { + final ArrayObjectIterator iterator = new ArrayObjectIterator(new String[]{"one", "two", "three"}); + + assertTrue(iterator.hasNext()); + assertEquals("one", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("two", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("three", iterator.next()); + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java new file mode 100644 index 0000000..f74e138 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class CompoundIteratorTest { + + @Test + void next() { + final CompoundIterator iterator = new CompoundIterator<>(new ArrayIterator<>(new String[]{"one", "two", "three"}), + new ArrayIterator<>(new String[]{"one_1", "two_2", "three_3"})); + + assertTrue(iterator.hasNext()); + assertEquals("one", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("two", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("three", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("one_1", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("two_2", iterator.next()); + assertTrue(iterator.hasNext()); + assertEquals("three_3", iterator.next()); + assertFalse(iterator.hasNext()); + } +} \ No newline at end of file diff --git a/agent/pom.xml b/agent/pom.xml index 9303b4a..970a9ec 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -150,6 +150,12 @@ asm-tree compile + + com.intergral.deep.tests + test-utils + 1.0-SNAPSHOT + test + diff --git a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java index 91d6358..390fc85 100644 --- a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java +++ b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java @@ -27,11 +27,13 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; +@SuppressWarnings("unused") public class AgentImpl { private static final CountDownLatch LATCH = new CountDownLatch(1); private static DeepAgent deepAgent; + // called via reflection public static void startup(final Instrumentation inst, final Map args) { final Settings settings = Settings.build(args); Logger.configureLogging(settings); @@ -47,6 +49,13 @@ public static void startup(final Instrumentation inst, final Map LATCH.countDown(); } + /** + * await the load of the dep api + * + * @return the loaded api object + * @throws InterruptedException if interupted + */ + // called via reflection public static Object awaitLoadAPI() throws InterruptedException { LATCH.await(); return loadDeepAPI(); diff --git a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java index 48126b1..8a26099 100644 --- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java +++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java @@ -63,7 +63,7 @@ public DeepAgent(final Settings settings, public void start() { final Resource resource = ResourceDetector.configureResource(settings, DeepAgent.class.getClassLoader()); - final List iLoadedPlugins = PluginLoader.loadPlugins(settings); + final List iLoadedPlugins = PluginLoader.loadPlugins(settings, ReflectionUtils.getReflection()); this.settings.setPlugins(iLoadedPlugins); this.settings.setResource(Resource.DEFAULT.merge(resource)); this.grpcService.start(); diff --git a/agent/src/main/java/com/intergral/deep/agent/IDUtils.java b/agent/src/main/java/com/intergral/deep/agent/IDUtils.java index 24a2f22..22be2ef 100644 --- a/agent/src/main/java/com/intergral/deep/agent/IDUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/IDUtils.java @@ -1,18 +1,6 @@ /* - * Copyright (C) 2023 Intergral GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 */ package com.intergral.deep.agent; @@ -49,7 +37,7 @@ private static char[] buildEncodingArray() { * @param dest the destination char array. * @param destOffset the starting offset in the destination char array. */ - public static void longToBase16String(long value, char[] dest, int destOffset) { + static void longToBase16String(long value, char[] dest, int destOffset) { byteToBase16((byte) (value >> 56 & 0xFFL), dest, destOffset); byteToBase16((byte) (value >> 48 & 0xFFL), dest, destOffset + BYTE_BASE16); byteToBase16((byte) (value >> 40 & 0xFFL), dest, destOffset + 2 * BYTE_BASE16); @@ -67,7 +55,7 @@ public static void longToBase16String(long value, char[] dest, int destOffset) { * @param dest the destination char array. * @param destOffset the starting offset in the destination char array. */ - public static void byteToBase16(byte value, char[] dest, int destOffset) { + static void byteToBase16(byte value, char[] dest, int destOffset) { int b = value & 0xFF; dest[destOffset] = ENCODING[b]; dest[destOffset + 1] = ENCODING[b | 0x100]; diff --git a/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java b/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java index 4233568..7a85e35 100644 --- a/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java @@ -29,7 +29,7 @@ public class ReflectionUtils { private static final IReflection reflection; static { - if (Utils.getVersion() >= 9) { + if (Utils.getJavaVersion() >= 9) { reflection = new com.intergral.deep.reflect.Java9ReflectionImpl(); } else { reflection = new ReflectionImpl(); @@ -40,30 +40,14 @@ public static IReflection getReflection() { return reflection; } - public static boolean setAccessible(final Class clazz, final Field field) { - return getReflection().setAccessible(clazz, field); - } - - - public static boolean setAccessible(final Class clazz, final Method method) { - return getReflection().setAccessible(clazz, method); - } - - public static T callMethod(Object target, String methodName, Object... args) { return getReflection().callMethod(target, methodName, args); } - public static Method findMethod(Class clazz, String methodName, Class... argTypes) { return getReflection().findMethod(clazz, methodName, argTypes); } - - public static Field getField(Object target, String fieldName) { - return getReflection().getField(target, fieldName); - } - public static T getFieldValue(Object target, String fieldName) { return getReflection().getFieldValue(target, fieldName); } diff --git a/agent/src/main/java/com/intergral/deep/agent/Utils.java b/agent/src/main/java/com/intergral/deep/agent/Utils.java index 208076b..5e2a916 100644 --- a/agent/src/main/java/com/intergral/deep/agent/Utils.java +++ b/agent/src/main/java/com/intergral/deep/agent/Utils.java @@ -24,7 +24,12 @@ public class Utils { - public static int getVersion() { + /** + * Get the current version of Java running in this JVM + * + * @return the java version number + */ + public static int getJavaVersion() { String version = System.getProperty("java.version"); return extractVersion(version); } @@ -55,6 +60,13 @@ public static long[] currentTimeNanos() { return new long[]{now.toEpochMilli(), Long.parseLong(format)}; } + /** + * Create a new map from the input + * + * @param map the input map + * @param the key type + * @return a new map with the same values are the input, or a new empty map + */ public static Map newMap(final Map map) { if (map == null) { return Collections.emptyMap(); @@ -64,7 +76,7 @@ public static Map newMap(final Map map) { /** - * FROM: https://stackoverflow.com/a/38947571 + * FROM: view source * * @param str the string to search * @param suffix the value to serch for @@ -75,18 +87,21 @@ public static boolean endsWithIgnoreCase(String str, String suffix) { return str.regionMatches(true, str.length() - suffixLength, suffix, 0, suffixLength); } + /** + * This will create a string representation of the object passed in. + * + * @param obj the value to create a string from + * @return the string form of the object + */ public static String valueOf(final Object obj) { if (obj == null) { return "null"; } String hash; + // sometimes (on bad objects) .toString will fail. So we need to protect against that. try { - final String tmp = String.valueOf(obj); - // FR-5298 - Protected again NullPointerException when stepping in - //Stringbuilder. - tmp.length(); - return tmp; + return String.valueOf(obj); } catch (final Throwable e1) { // From Object.toString(); hash = String.valueOf(System.identityHashCode(obj)); @@ -95,15 +110,29 @@ public static String valueOf(final Object obj) { } - public static String trim(String str, final String trimStr) { - while (str.startsWith(trimStr)) { + /** + * Trim a string from another string + * + * @param str the target string + * @param prefix the value to remove from the string + * @return the new string + */ + public static String trimPrefix(String str, final String prefix) { + while (str.startsWith(prefix)) { str = str.substring(1); } return str; } - public static ITrimResult trim(final String str, final int maxLength) { + /** + * Trim a string to a specified length + * + * @param str the target string + * @param maxLength the max length to make the string + * @return a {@link ITrimResult}, so we can know if the string was trimmed + */ + public static ITrimResult truncate(final String str, final int maxLength) { if (str.length() > maxLength) { return new ITrimResult() { @Override diff --git a/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java b/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java index e9bb9ac..f7bf002 100644 --- a/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java +++ b/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java @@ -17,8 +17,10 @@ package com.intergral.deep.agent.grpc; +import com.intergral.deep.agent.ReflectionUtils; import com.intergral.deep.agent.api.auth.AuthProvider; import com.intergral.deep.agent.api.auth.IAuthProvider; +import com.intergral.deep.agent.api.settings.ISettings; import com.intergral.deep.agent.settings.Settings; import com.intergral.deep.proto.poll.v1.PollConfigGrpc; import com.intergral.deep.proto.tracepoint.v1.SnapshotServiceGrpc; @@ -94,7 +96,7 @@ private void setupChannel() { } // Select secure or not - if (this.settings.getSettingAs("service.secure", Boolean.class)) { + if (this.settings.getSettingAs(ISettings.KEY_SERVICE_SECURE, Boolean.class)) { ncBuilder.useTransportSecurity(); } else { ncBuilder.usePlaintext(); @@ -134,7 +136,7 @@ public SnapshotServiceGrpc.SnapshotServiceStub snapshotService() { } private Metadata buildMetaData() { - final IAuthProvider provider = AuthProvider.provider(this.settings); + final IAuthProvider provider = AuthProvider.provider(this.settings, ReflectionUtils.getReflection()); final Map headers = provider.provide(); final Metadata metadata = new Metadata(); diff --git a/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java b/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java index 453d000..be90fea 100644 --- a/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java +++ b/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java @@ -18,6 +18,8 @@ package com.intergral.deep.agent.plugins; import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.reflection.IReflection; +import com.intergral.deep.agent.api.reflection.ReflectionUtils; import com.intergral.deep.agent.settings.Settings; import java.lang.reflect.Constructor; import java.util.ArrayList; @@ -29,15 +31,14 @@ public class PluginLoader { private static final Logger LOGGER = LoggerFactory.getLogger(PluginLoader.class); - public static List loadPlugins(final Settings settings) { + public static List loadPlugins(final Settings settings, final IReflection reflection) { final List plugins = settings.getAsList("plugins"); final List loadedPlugins = new ArrayList<>(); for (String plugin : plugins) { try { final Class aClass = Class.forName(plugin); - final Constructor constructor = aClass.getConstructor(); - final Object newInstance = constructor.newInstance(); - final IPlugin asPlugin = (IPlugin) newInstance; + final Constructor constructor = ReflectionUtils.findConstructor(aClass, reflection); + final IPlugin asPlugin = ReflectionUtils.callConstructor(constructor, settings, reflection); if (asPlugin.isActive(settings)) { loadedPlugins.add(asPlugin); } diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java b/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java index d3d5112..deb5275 100644 --- a/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java +++ b/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java @@ -17,6 +17,7 @@ package com.intergral.deep.agent.poll; +import com.intergral.deep.agent.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,7 +73,9 @@ public void run() { try { // calculate if we woke up early - long now = System.currentTimeMillis(); + long[] nowTuple = Utils.currentTimeNanos(); + // the drift aware only calculates at ms accuracy - but we want to use ns for the time later + long now = nowTuple[0]; long startDelay = checkForEarlyWake(now, this.nextExecutionTime); // if we woke early @@ -96,7 +99,8 @@ public void run() { samplerLock.wait(startDelay); } } - now = System.currentTimeMillis(); + nowTuple = Utils.currentTimeNanos(); + now = nowTuple[0]; startDelay = checkForEarlyWake(now, this.nextExecutionTime); // quick exit if we have been stopped @@ -109,7 +113,7 @@ public void run() { try { debug("Running task."); - this.runnable.run(now); + this.runnable.run(nowTuple[1]); } catch (final Exception e) { error("Exception during task execution: " + e.getMessage(), e); } diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java index bb7ed72..538ae6d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java +++ b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java @@ -17,7 +17,6 @@ package com.intergral.deep.agent.poll; -import com.intergral.deep.agent.Utils; import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.grpc.GrpcService; import com.intergral.deep.agent.settings.Settings; @@ -49,6 +48,10 @@ public LongPollService(final Settings settings, final GrpcService grpcService) { settings.getSettingAs("poll.timer", Integer.class)); } + void setTracepointConfig(final ITracepointConfig tracepointConfig) { + this.tracepointConfig = tracepointConfig; + } + public void start(final ITracepointConfig tracepointConfig) { this.tracepointConfig = tracepointConfig; thread.start(0); @@ -69,7 +72,7 @@ public void run(long now) { } final PollRequest pollRequest = builder - .setTsNanos(Utils.currentTimeNanos()[1]) + .setTsNanos(now) .setResource(buildResource()) .build(); diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java index cc9e359..b82d0cf 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java @@ -114,14 +114,16 @@ private static Iterable convertFrames( .setMethodName(stackFrame.getMethodName()) .setLineNumber(stackFrame.getLineNumber()) .setClassName(stackFrame.getClassName()) -// .setIsAsync( false ) -// .setColumnNumber( 0 ) - //todo update for JSP/CFM + // Java does not have async frames or column Numbers + //.setIsAsync( false ) + //.setColumnNumber( 0 ) + //todo update for JSP // .setTranspiledFileName( "" ) // .setTranspiledLineNumber( 0 ) // .setTranspiledColumnNumber( 0 ) .addAllVariables(covertVariables(stackFrame.getFrameVariables())) .setAppFrame(stackFrame.isAppFrame()) + .setNativeFrame(stackFrame.isNativeFrame()) .build()).collect(Collectors.toList()); } diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java b/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java new file mode 100644 index 0000000..15474a9 --- /dev/null +++ b/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.resource; + +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.api.spi.ResourceProvider; +import java.util.Collections; + +public class JavaResourceDetector implements ResourceProvider { + + @Override + public Resource createResource(final ISettings settings) { + final String property = System.getProperty("java.version"); + if (property == null) { + return null; + } + return Resource.create(Collections.singletonMap("java_version", property)); + } +} diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java index 8f13691..f361ee0 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java @@ -1,18 +1,6 @@ /* - * Copyright (C) 2023 Intergral GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 */ package com.intergral.deep.agent.resource; @@ -38,16 +26,16 @@ public class ResourceDetector { static final String ATTRIBUTE_PROPERTY = "deep.resource.attributes"; static final String SERVICE_NAME_PROPERTY = "deep.service.name"; static final String DISABLED_ATTRIBUTE_KEYS = "deep.resource.disabled.keys"; + static final String ENABLED_PROVIDERS_KEY = "deep.java.enabled.resource.providers"; + static final String DISABLED_PROVIDERS_KEY = "deep.java.disabled.resource.providers"; - public static Resource configureResource( - Settings config, - ClassLoader serviceClassLoader) { + public static Resource configureResource(Settings settings, ClassLoader serviceClassLoader) { Resource result = Resource.create(Collections.emptyMap()); Set enabledProviders = - new HashSet<>(config.getAsList("deep.java.enabled.resource.providers")); + new HashSet<>(settings.getAsList(ENABLED_PROVIDERS_KEY)); Set disabledProviders = - new HashSet<>(config.getAsList("deep.java.disabled.resource.providers")); + new HashSet<>(settings.getAsList(DISABLED_PROVIDERS_KEY)); for (ResourceProvider resourceProvider : SpiUtil.loadOrdered(ResourceProvider.class, serviceClassLoader)) { @@ -59,29 +47,29 @@ public static Resource configureResource( continue; } if (resourceProvider instanceof ConditionalResourceProvider - && !((ConditionalResourceProvider) resourceProvider).shouldApply(config, result)) { + && !((ConditionalResourceProvider) resourceProvider).shouldApply(settings, result)) { continue; } - result = result.merge(resourceProvider.createResource(config)); + result = result.merge(resourceProvider.createResource(settings)); } - result = result.merge(createEnvironmentResource(config)); + result = result.merge(createEnvironmentResource(settings)); - result = filterAttributes(result, config); + result = filterAttributes(result, settings); return result; } - private static Resource createEnvironmentResource(Settings config) { - return Resource.create(getAttributes(config), null); + private static Resource createEnvironmentResource(Settings settings) { + return Resource.create(getAttributes(settings), null); } // visible for testing - static Map getAttributes(Settings configProperties) { + static Map getAttributes(Settings settings) { Map resourceAttributes = new HashMap<>(); try { for (Map.Entry entry : - configProperties.getMap(ATTRIBUTE_PROPERTY).entrySet()) { + settings.getMap(ATTRIBUTE_PROPERTY).entrySet()) { resourceAttributes.put( entry.getKey(), // Attributes specified via deep.resource.attributes follow the W3C Baggage spec and @@ -93,7 +81,7 @@ static Map getAttributes(Settings configProperties) { // Should not happen since always using standard charset throw new ConfigurationException("Unable to decode resource attributes.", e); } - String serviceName = configProperties.getSettingAs(SERVICE_NAME_PROPERTY, String.class); + String serviceName = settings.getSettingAs(SERVICE_NAME_PROPERTY, String.class); if (serviceName != null) { resourceAttributes.put(ResourceAttributes.SERVICE_NAME, serviceName); } @@ -101,8 +89,8 @@ static Map getAttributes(Settings configProperties) { } // visible for testing - static Resource filterAttributes(Resource resource, Settings configProperties) { - Set disabledKeys = new HashSet<>(configProperties.getAsList( + static Resource filterAttributes(Resource resource, Settings settings) { + Set disabledKeys = new HashSet<>(settings.getAsList( DISABLED_ATTRIBUTE_KEYS)); final Map attributes = resource.getAttributes(); diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java b/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java index 733153d..e632d86 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java @@ -1,18 +1,6 @@ /* - * Copyright (C) 2023 Intergral GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 */ package com.intergral.deep.agent.resource; diff --git a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java index 90e7866..679ad8b 100644 --- a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java +++ b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java @@ -44,7 +44,7 @@ public class Settings implements ISettings { private static final AtomicBoolean IS_ACTIVE = new AtomicBoolean(true); private final Properties properties; private Resource resource; - private Collection plugins; + private Collection plugins = Collections.emptyList(); private final Collection customPlugins = new ArrayList<>(); private Settings(Properties properties) { @@ -128,9 +128,9 @@ public static T coerc(final String str, final Class type) { if (type == Boolean.class || type == boolean.class) { return (T) Boolean.valueOf(str); } else if (type == Integer.class || type == int.class) { - return (T) Integer.valueOf(str); + return (T) Integer.valueOf(Double.valueOf(str).intValue()); } else if (type == Long.class || type == long.class) { - return (T) Long.valueOf(str); + return (T) Long.valueOf(Double.valueOf(str).longValue()); } else if (type == String.class) { return (T) str; } else if (type == Double.class || type == double.class) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java index 91d0641..9bdaaa8 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java @@ -60,23 +60,23 @@ static String parseFullClassName(final String rawRelPath, final String srcRootAr } if (srcRootArg != null) { if (relPath.startsWith(srcRootArg)) { - return Utils.trim(relPath.substring(srcRootArg.length()), "/"); + return Utils.trimPrefix(relPath.substring(srcRootArg.length()), "/"); } } else if (relPath.contains("/src/main/")) { final String mainDir = relPath.substring(relPath.indexOf("/src/main/") + 11); final int i = mainDir.indexOf('/'); - return Utils.trim(mainDir.substring(i), "/"); + return Utils.trimPrefix(mainDir.substring(i), "/"); } else if (relPath.contains("/src/test/")) { final String mainDir = relPath.substring(relPath.indexOf("/src/test/") + 11); final int i = mainDir.indexOf('/'); - return Utils.trim(mainDir.substring(i), "/"); + return Utils.trimPrefix(mainDir.substring(i), "/"); } - final String trim = Utils.trim(relPath, "/"); + final String trim = Utils.trimPrefix(relPath, "/"); // this is just to ensure the file name is never empty // this only happens on non class files such as '.gitignore' // rather then return empty name we return the raw path if (trim.isEmpty()) { - return Utils.trim(rawRelPath, "/"); + return Utils.trimPrefix(rawRelPath, "/"); } return trim; } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java index ab95aa6..da6bfa2 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java @@ -172,7 +172,7 @@ protected VariableResponse processVariable(final Node.NodeValue value) { varType = objValue.getClass().getName(); } - final Utils.ITrimResult iTrimResult = Utils.trim(this.valueToString(objValue), + final Utils.ITrimResult iTrimResult = Utils.truncate(this.valueToString(objValue), this.frameConfig.maxStringLength()); final Variable variable = new Variable(varType, iTrimResult.value(), identityCode, diff --git a/agent/src/main/resources/META-INF/services/com.intergral.deep.agent.api.spi.ResourceProvider b/agent/src/main/resources/META-INF/services/com.intergral.deep.agent.api.spi.ResourceProvider new file mode 100644 index 0000000..b0503ad --- /dev/null +++ b/agent/src/main/resources/META-INF/services/com.intergral.deep.agent.api.spi.ResourceProvider @@ -0,0 +1,18 @@ +# +# Copyright (C) 2023 Intergral GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + +com.intergral.deep.agent.resource.JavaResourceDetector \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/AgentTest.java b/agent/src/test/java/com/intergral/deep/agent/AgentTest.java new file mode 100644 index 0000000..41169f6 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/AgentTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class AgentTest { + + + @Test + void parseArgs() + { + final Map stringStringMap = Agent.parseArgs( "me=you" ); + assertEquals( stringStringMap, new HashMap() + {{ + put( "me", "you" ); + }} ); + } + + + @Test + void parseArgsWithTags() + { + final Map stringStringMap = Agent.parseArgs( "tags=testKey=testVal;testkey2=testval2" ); + assertEquals( stringStringMap, new HashMap() + {{ + put( "tags", "testKey=testVal;testkey2=testval2" ); + }} ); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/IDUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/IDUtilsTest.java new file mode 100644 index 0000000..5fd1e4a --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/IDUtilsTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class IDUtilsTest { + + @Test + void randomId() { + assertNotNull(IDUtils.randomId()); + assertEquals(16, IDUtils.randomId().length()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java new file mode 100644 index 0000000..9a8e9ee --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.HashMap; +import org.junit.jupiter.api.Test; + +class UtilsTest { + + @Test + void javaVersion() { + assertTrue(Utils.getJavaVersion() > 7); + } + + @Test + void currentTimeNanos() throws InterruptedException { + final long[] start = Utils.currentTimeNanos(); + Thread.sleep(1000); + final long[] after = Utils.currentTimeNanos(); + + assertTrue(after[0] > start[0]); + assertTrue(after[1] > start[1]); + } + + @Test + void newMap() { + assertEquals(Collections.emptyMap(), Utils.newMap(new HashMap<>())); + + assertEquals("value", Utils.newMap(Collections.singletonMap("key", "value")).get("key")); + } + + @Test + void endsWithIgnoreCase() { + assertTrue(Utils.endsWithIgnoreCase("someString", "string")); + assertTrue(Utils.endsWithIgnoreCase("someString", "String")); + assertFalse(Utils.endsWithIgnoreCase("someString", "qtring")); + } + + @Test + void valueOf() { + class badtostring { + + @Override + public String toString() { + throw new NullPointerException(); + } + + } + assertEquals("some value", Utils.valueOf("some value")); + assertEquals("1", Utils.valueOf("1")); + assertEquals("null", Utils.valueOf(null)); + final String valueOf = Utils.valueOf(new badtostring()); + assertNotNull(valueOf); + assertTrue(valueOf.startsWith(badtostring.class.getName())); + assertTrue(valueOf.endsWith("toString() failed")); + } + + @Test + void trim() { + assertEquals("value", Utils.trimPrefix("//value", "/")); + } + + @Test + void truncate() { + assertTrue(Utils.truncate("somelongstring", 10).truncated()); + assertEquals("somelongst", Utils.truncate("somelongstring", 10).value()); + assertFalse(Utils.truncate("somelongstring", 20).truncated()); + assertEquals("somelongstring", Utils.truncate("somelongstring", 20).value()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java new file mode 100644 index 0000000..03b2949 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.grpc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import com.intergral.deep.agent.api.auth.IAuthProvider; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.logging.Logger; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.proto.poll.v1.PollRequest; +import com.intergral.deep.proto.poll.v1.PollResponse; +import com.intergral.deep.proto.tracepoint.v1.Snapshot; +import com.intergral.deep.proto.tracepoint.v1.SnapshotResponse; +import com.intergral.deep.tests.grpc.TestInterceptor; +import com.intergral.deep.tests.grpc.TestPollService; +import com.intergral.deep.tests.grpc.TestSnapshotService; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.ServerInterceptors; +import io.grpc.stub.StreamObserver; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GrpcServiceTest { + + private Server server; + private CountDownLatch pollLatch; + private final AtomicReference pollRequest = new AtomicReference<>(); + private CountDownLatch snapshotLatch; + private final AtomicReference snapshotRequest = new AtomicReference<>(); + + private final AtomicReference header = new AtomicReference<>(); + + @BeforeAll + static void beforeAll() { + System.setProperty("java.util.logging.config.file", ClassLoader.getSystemResource("logging.properties").getPath()); + Logger.configureLogging(Settings.build(Collections.singletonMap("logging.level", "DEBUG"))); + } + + @BeforeEach + void setUp() throws Exception { + + final TestInterceptor testInterceptor = new TestInterceptor("test_key"); + + pollLatch = new CountDownLatch(1); + snapshotLatch = new CountDownLatch(1); + final TestSnapshotService testSnapshotService = new TestSnapshotService((snapshot, responseObserver) -> { + final String headerVal = testInterceptor.contextKey().get(); + header.set(headerVal); + snapshotRequest.set(snapshot); + snapshotLatch.countDown(); + responseObserver.onNext(SnapshotResponse.newBuilder().build()); + responseObserver.onCompleted(); + }); + + final TestPollService testPollService = new TestPollService((request, responseObserver) -> { + final String headerVal = testInterceptor.contextKey().get(); + header.set(headerVal); + pollRequest.set(request); + pollLatch.countDown(); + responseObserver.onNext(PollResponse.newBuilder().setTsNanos(202020L).build()); + responseObserver.onCompleted(); + }); + + server = ServerBuilder.forPort(9999).addService(ServerInterceptors.intercept(testPollService.bindService(), testInterceptor)) + .addService(ServerInterceptors.intercept(testSnapshotService.bindService(), testInterceptor)).build(); + + server.start(); + } + + @AfterEach + void tearDown() { + this.server.shutdownNow(); + } + + @Test + void serverCanConnect_poll() throws InterruptedException { + + final HashMap map = new HashMap<>(); + map.put(ISettings.KEY_SERVICE_URL, "localhost:9999"); + map.put(ISettings.KEY_SERVICE_SECURE, "false"); + map.put(ISettings.KEY_AUTH_PROVIDER, MockAuthProvider.class.getName()); + + final GrpcService grpcService = new GrpcService(Settings.build(map)); + + new Thread(grpcService::start).start(); + + final PollResponse pollResponse = grpcService.pollService().poll(PollRequest.newBuilder().setTsNanos(101010L).build()); + assertEquals(202020L, pollResponse.getTsNanos()); + pollLatch.await(5, TimeUnit.SECONDS); + + final PollRequest pollRequest = this.pollRequest.get(); + assertNotNull(pollRequest); + + assertEquals("test_header", header.get()); + } + + @Test + void serverCanConnect_snapshot() throws InterruptedException { + + final HashMap map = new HashMap<>(); + map.put(ISettings.KEY_SERVICE_URL, "localhost:9999"); + map.put(ISettings.KEY_SERVICE_SECURE, "false"); + map.put(ISettings.KEY_AUTH_PROVIDER, MockAuthProvider.class.getName()); + + final GrpcService grpcService = new GrpcService(Settings.build(map)); + + new Thread(grpcService::start).start(); + final CountDownLatch responseLatch = new CountDownLatch(1); + final AtomicReference responseAtomicReference = new AtomicReference<>(); + grpcService.snapshotService().send(Snapshot.newBuilder().build(), + new StreamObserver() { + @Override + public void onNext(final SnapshotResponse value) { + responseAtomicReference.set(value); + responseLatch.countDown(); + } + + @Override + public void onError(final Throwable t) { + t.printStackTrace(); + fail("Cannot connect snapshot service"); + } + + @Override + public void onCompleted() { + + } + }); + responseLatch.await(5, TimeUnit.SECONDS); + final SnapshotResponse snapshotResponse = responseAtomicReference.get(); + assertNotNull(snapshotResponse); + assertEquals("test_header", header.get()); + } + + + public static class MockAuthProvider implements IAuthProvider { + + @Override + public Map provide() { + return Collections.singletonMap("test_key", "test_header"); + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java b/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java new file mode 100644 index 0000000..8c27177 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.plugins; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.intergral.deep.agent.ReflectionUtils; +import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.plugin.JavaPlugin; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import org.junit.jupiter.api.Test; + +class PluginLoaderTest { + + @Test + void canLoadNoPlugins() { + final List iPlugins = PluginLoader.loadPlugins(Settings.build(Collections.emptyMap()), ReflectionUtils.getReflection()); + assertEquals(1, iPlugins.size()); + assertEquals(iPlugins.get(0).name(), JavaPlugin.class.getName()); + } + + @Test + void canLoadBadPlugin() { + final HashMap agentArgs = new HashMap<>(); + agentArgs.put(ISettings.PLUGINS, BadPlugin.class.getName()); + final List iPlugins = PluginLoader.loadPlugins(Settings.build(agentArgs), ReflectionUtils.getReflection()); + assertEquals(0, iPlugins.size()); + } + + @Test + void canLoadGoodPlugin() { + final HashMap agentArgs = new HashMap<>(); + agentArgs.put(ISettings.PLUGINS, GoodPlugin.class.getName()); + final List iPlugins = PluginLoader.loadPlugins(Settings.build(agentArgs), ReflectionUtils.getReflection()); + assertEquals(1, iPlugins.size()); + assertEquals(iPlugins.get(0).name(), GoodPlugin.class.getName()); + } + + public static class BadPlugin { + + } + + public static class GoodPlugin implements IPlugin { + + @Override + public Resource decorate(final ISettings settings, final ISnapshotContext snapshot) { + return null; + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/poll/DriftAwareThreadTest.java b/agent/src/test/java/com/intergral/deep/agent/poll/DriftAwareThreadTest.java new file mode 100644 index 0000000..1fe6b1c --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/poll/DriftAwareThreadTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.poll; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DriftAwareThreadTest { + + private DriftAwareThread task; + + + @BeforeEach + public void setUp() { + this.task = new DriftAwareThread("Test", null, 1000); + } + + + @Test + public void delay2() throws Exception { + class lwrap { + + long n; + } + final lwrap lwrap = new lwrap(); + final CountDownLatch countDownLatch = new CountDownLatch(1); + final long currentTimeMillis = System.currentTimeMillis(); + final DriftAwareThread driftAwareThread = new DriftAwareThread("Test", new ITimerTask() { + @Override + public void run(final long now) { + assertTrue((now + 10000) >= currentTimeMillis); + lwrap.n = now; + countDownLatch.countDown(); + } + + + @Override + public long callback(final long duration, final long next) { + return next; + } + }, 5000); + driftAwareThread.start(10000); + countDownLatch.await(); + driftAwareThread.stopTask(); + assertTrue((lwrap.n + 10000) >= currentTimeMillis); + } + + + @Test + public void testCheckForEarlyWakeUp() throws Exception { + assertEquals(1000, task.checkForEarlyWake(0, 1000)); + assertEquals(999, task.checkForEarlyWake(1, 1000)); + assertEquals(100, task.checkForEarlyWake(900, 1000)); + assertEquals(1, task.checkForEarlyWake(999, 1000)); + assertEquals(-1, task.checkForEarlyWake(1000, 1000)); + assertEquals(-1, task.checkForEarlyWake(1001, 1000)); + assertEquals(-1, task.checkForEarlyWake(1100, 1000)); + assertEquals(-1, task.checkForEarlyWake(2000, 1000)); + + final long now = System.currentTimeMillis(); + + assertEquals(1000, task.checkForEarlyWake(now, now + 1000)); + + } + + + @Test + public void testWhatIsNextExecutionTime() { + assertEquals(2000, task.whatIsNextExecutionTime(1000, 1001)); + assertEquals(2000, task.whatIsNextExecutionTime(1000, 1499)); + assertEquals(3000, task.whatIsNextExecutionTime(1000, 1500)); + assertEquals(3000, task.whatIsNextExecutionTime(1000, 1501)); + + assertEquals(1000, task.whatIsNextExecutionTime(0, 0)); + assertEquals(1000, task.whatIsNextExecutionTime(0, 100)); + assertEquals(2000, task.whatIsNextExecutionTime(0, 1000)); + + assertEquals(2000, task.whatIsNextExecutionTime(1000, 1010)); + assertEquals(2100, task.whatIsNextExecutionTime(1100, 1)); + assertEquals(2110, task.whatIsNextExecutionTime(1110, 1)); + assertEquals(2111, task.whatIsNextExecutionTime(1111, 1)); + + assertEquals(4000, task.whatIsNextExecutionTime(1000, 3000)); + assertEquals(4000, task.whatIsNextExecutionTime(1000, 2999)); + + final long now = System.currentTimeMillis(); + + assertEquals(now + 1000, task.whatIsNextExecutionTime(now, now)); + assertEquals(now + 1000, task.whatIsNextExecutionTime(now, now - 1000)); + assertEquals(now, task.whatIsNextExecutionTime(now - 1000, now - 1000)); + assertEquals(now + 1000, task.whatIsNextExecutionTime(now - 1000, now)); + + assertEquals(now + 2000, task.whatIsNextExecutionTime(now, now + 1000)); + assertEquals(now + 2000, task.whatIsNextExecutionTime(now + 1000, now)); + assertEquals(now + 2000, task.whatIsNextExecutionTime(now + 1000, now + 1000)); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java new file mode 100644 index 0000000..10fbd1c --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.poll; + +import static com.intergral.deep.agent.types.TracePointConfig.SINGLE_FRAME_TYPE; +import static com.intergral.deep.agent.types.TracePointConfig.STACK; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.grpc.GrpcService; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.tracepoint.TracepointConfigService; +import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService; +import com.intergral.deep.proto.common.v1.AnyValue; +import com.intergral.deep.proto.common.v1.KeyValue; +import com.intergral.deep.proto.poll.v1.PollRequest; +import com.intergral.deep.proto.poll.v1.PollResponse; +import com.intergral.deep.proto.poll.v1.ResponseType; +import com.intergral.deep.proto.tracepoint.v1.TracePointConfig; +import com.intergral.deep.tests.ResettableCountDownLatch; +import com.intergral.deep.tests.grpc.TestPollService; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +class LongPollServiceTest { + + + private Server server; + private LongPollService longPollService; + + private PollRequest request; + private PollResponse response; + private Throwable responseError; + + @BeforeEach + void setUp() throws IOException { + final TestPollService testPollService = new TestPollService((req, responseObserver) -> { + request = req; + if (responseError != null) { + responseObserver.onError(responseError); + } else { + responseObserver.onNext(response); + } + responseObserver.onCompleted(); + }); + + server = ServerBuilder.forPort(9999).addService(testPollService.bindService()).build(); + + server.start(); + + final HashMap agentArgs = new HashMap<>(); + agentArgs.put(ISettings.KEY_SERVICE_URL, "localhost:9999"); + agentArgs.put(ISettings.KEY_SERVICE_SECURE, "false"); + final Settings settings = Settings.build(agentArgs); + settings.setResource(Resource.create(Collections.singletonMap("test", "resource"))); + final GrpcService grpcService = new GrpcService(settings); + longPollService = new LongPollService(settings, grpcService); + } + + @AfterEach + void tearDown() { + server.shutdownNow(); + } + + @Test + void canPollServer() { + final TracepointConfigService configService = mock(TracepointConfigService.class); + + longPollService.setTracepointConfig(configService); + longPollService.run(100); + + verify(configService).noChange(0L); + } + + @Test + void callsNoChangeOnResponse() { + final TracepointConfigService configService = mock(TracepointConfigService.class); + longPollService.setTracepointConfig(configService); + + response = PollResponse.newBuilder().setResponseType(ResponseType.NO_CHANGE).build(); + + longPollService.run(100); + + verify(configService).noChange(0L); + } + + @Test + void callsUpdateOnResponse() { + final TracepointConfigService configService = mock(TracepointConfigService.class); + longPollService.setTracepointConfig(configService); + + response = PollResponse.newBuilder().setResponseType(ResponseType.UPDATE).build(); + + longPollService.run(100); + + verify(configService, never()).noChange(0L); + } + + @Test + void propagateHashOnNextCall() { + final TracepointInstrumentationService instrumentationService = mock(TracepointInstrumentationService.class); + + final TracepointConfigService configService = spy(new TracepointConfigService(instrumentationService)); + doCallRealMethod().when(configService).configUpdate(Mockito.anyLong(), Mockito.eq("123"), Mockito.anyCollection()); + longPollService.setTracepointConfig(configService); + + response = PollResponse.newBuilder().setResponseType(ResponseType.UPDATE).setCurrentHash("123").build(); + + longPollService.run(100); + + assertEquals("", request.getCurrentHash()); + assertEquals(100, request.getTsNanos()); + + response = PollResponse.newBuilder().setResponseType(ResponseType.UPDATE).setCurrentHash("321").build(); + + longPollService.run(101); + + assertEquals("123", request.getCurrentHash()); + assertEquals(101, request.getTsNanos()); + verify(instrumentationService, times(2)).processBreakpoints(Mockito.anyCollection()); + } + + @Test + void canHandleTracepointResponse() { + final TracepointInstrumentationService instrumentationService = mock(TracepointInstrumentationService.class); + + final TracepointConfigService configService = spy(new TracepointConfigService(instrumentationService)); + doCallRealMethod().when(configService).configUpdate(Mockito.anyLong(), Mockito.eq("123"), Mockito.anyCollection()); + longPollService.setTracepointConfig(configService); + + response = PollResponse.newBuilder().setResponseType(ResponseType.UPDATE).setCurrentHash("123").addResponse( + TracePointConfig.newBuilder().setPath("/some/file/path.py").setLineNumber(123).setID("tp-1") + .putAllArgs(Collections.singletonMap("key", "value")).addWatches("i watch").addTargeting( + KeyValue.newBuilder().setKey("key").setValue(AnyValue.newBuilder().setStringValue("some string").build()).build()).build()) + .build(); + + longPollService.run(100); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class); + verify(instrumentationService).processBreakpoints(captor.capture()); + + final Collection value = captor.getValue(); + + assertEquals(1, value.size()); + + final com.intergral.deep.agent.types.TracePointConfig next = value.iterator().next(); + + assertEquals("tp-1", next.getId()); + assertEquals("/some/file/path.py", next.getPath()); + assertEquals(123, next.getLineNo()); + assertArrayEquals(new String[]{"i watch"}, next.getWatches().toArray()); + assertEquals(1, next.getFireCount()); + assertNull(next.getCondition()); + assertEquals(SINGLE_FRAME_TYPE, next.getFrameType()); + assertEquals(STACK, next.getStackType()); + } + + @Test + void doesSendResourceOnRequest() { + final TracepointInstrumentationService instrumentationService = mock(TracepointInstrumentationService.class); + final TracepointConfigService configService = spy(new TracepointConfigService(instrumentationService)); + longPollService.setTracepointConfig(configService); + + longPollService.run(100); + + assertNotNull(request.getResource()); + assertEquals("test", request.getResource().getAttributes(0).getKey()); + assertEquals("resource", request.getResource().getAttributes(0).getValue().getStringValue()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java new file mode 100644 index 0000000..e6bf381 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.push; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.grpc.GrpcService; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.proto.common.v1.KeyValue; +import com.intergral.deep.proto.tracepoint.v1.Snapshot; +import com.intergral.deep.proto.tracepoint.v1.SnapshotServiceGrpc.SnapshotServiceStub; +import com.intergral.deep.test.MockEventSnapshot; +import java.util.Collections; +import java.util.HashMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +class PushServiceTest { + + private PushService pushService; + private SnapshotServiceStub snapshotServiceStub; + private Settings settings; + + @BeforeEach + void setUp() { + final GrpcService grpcService = Mockito.mock(GrpcService.class); + snapshotServiceStub = Mockito.mock(SnapshotServiceStub.class); + when(grpcService.snapshotService()).thenReturn(snapshotServiceStub); + final HashMap map = new HashMap<>(); + + settings = Settings.build(map); + pushService = new PushService(settings, grpcService); + } + + @Test + void canPushSnapshot() { + final ISnapshotContext context = Mockito.mock(ISnapshotContext.class); + pushService.pushSnapshot(new MockEventSnapshot(), context); + + verify(snapshotServiceStub).send(any(), any()); + } + + @Test + void doesDecorate() { + settings.addPlugin(new TestDecorator()); + + final ISnapshotContext context = Mockito.mock(ISnapshotContext.class); + pushService.pushSnapshot(new MockEventSnapshot(), context); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Snapshot.class); + verify(snapshotServiceStub).send(argumentCaptor.capture(), any()); + + final Snapshot value = argumentCaptor.getValue(); + + assertNotNull(value); + + final KeyValue attributes = value.getAttributes(0); + assertEquals("decorated", attributes.getKey()); + assertEquals("value", attributes.getValue().getStringValue()); + } + + public static class TestDecorator implements IPlugin { + + @Override + public Resource decorate(final ISettings settings, final ISnapshotContext snapshot) { + return Resource.create(Collections.singletonMap("decorated", "value")); + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/push/PushUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/push/PushUtilsTest.java new file mode 100644 index 0000000..a388292 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/push/PushUtilsTest.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.push; + +import static com.intergral.deep.tests.AssertUtils.assertContains; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.intergral.deep.proto.common.v1.KeyValue; +import com.intergral.deep.proto.tracepoint.v1.Snapshot; +import com.intergral.deep.test.MockEventSnapshot; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class PushUtilsTest { + + @Test + void canProcessSnapshot() { + final MockEventSnapshot eventSnapshot = new MockEventSnapshot(); + final Snapshot snapshot = PushUtils.convertToGrpc(eventSnapshot); + assertEquals(eventSnapshot.getID(), snapshot.getID().toStringUtf8()); + + assertEquals("tp-1", snapshot.getTracepoint().getID()); + assertEquals("/some/file/path.py", snapshot.getTracepoint().getPath()); + assertEquals(123, snapshot.getTracepoint().getLineNumber()); + } + + @Test + void canHandleFrames() { + final MockEventSnapshot mockEventSnapshot = new MockEventSnapshot().withFrames(); + final Snapshot snapshot = PushUtils.convertToGrpc(mockEventSnapshot); + + assertEquals("class_name_1", snapshot.getFrames(0).getClassName()); + assertEquals("class_name_2", snapshot.getFrames(1).getClassName()); + assertEquals("class_name_3", snapshot.getFrames(2).getClassName()); + assertEquals("class_name_4", snapshot.getFrames(3).getClassName()); + + assertTrue(snapshot.getFrames(0).getNativeFrame()); + assertTrue(snapshot.getFrames(0).getAppFrame()); + + assertFalse(snapshot.getFrames(3).getNativeFrame()); + assertFalse(snapshot.getFrames(3).getAppFrame()); + + assertEquals(3, snapshot.getFrames(3).getVariablesCount()); + + assertEquals("someLocalVar", snapshot.getFrames(3).getVariables(0).getName()); + assertEquals("private", snapshot.getFrames(3).getVariables(1).getModifiers(0)); + assertEquals("someOtherName", snapshot.getFrames(3).getVariables(2).getOriginalName()); + } + + @Test + void canConvertAllTypes() { + final MockEventSnapshot mockEventSnapshot = new MockEventSnapshot().withAttributes(); + final Snapshot snapshot = PushUtils.convertToGrpc(mockEventSnapshot); + + final List attributesList = new ArrayList<>(snapshot.getAttributesList()); + attributesList.remove(assertContains(attributesList, (item) -> item.getValue().getIntValue() == 123L)); + attributesList.remove(assertContains(attributesList, (item) -> item.getValue().getIntValue() == 123)); + attributesList.remove(assertContains(attributesList, (item) -> item.getValue().getDoubleValue() == 3.21F)); + attributesList.remove(assertContains(attributesList, (item) -> item.getValue().getDoubleValue() == 1.23D)); + attributesList.remove(assertContains(attributesList, (item) -> !item.getValue().getBoolValue())); + attributesList.remove(assertContains(attributesList, (item) -> "123l".equals(item.getValue().getStringValue()))); + + assertEquals(0, attributesList.size()); + } + + @Test + void canConvertWatches() { + final MockEventSnapshot mockEventSnapshot = new MockEventSnapshot().withWatches(); + final Snapshot snapshot = PushUtils.convertToGrpc(mockEventSnapshot); + + assertEquals("error", snapshot.getWatches(0).getExpression()); + assertEquals("this is an error", snapshot.getWatches(0).getErrorResult()); + assertEquals("good", snapshot.getWatches(1).getExpression()); + assertEquals("withName", snapshot.getWatches(1).getGoodResult().getName()); + } + + @Test + void canConvertVariables() { + final MockEventSnapshot mockEventSnapshot = new MockEventSnapshot().withVariables(); + final Snapshot snapshot = PushUtils.convertToGrpc(mockEventSnapshot); + + assertEquals("value_1", snapshot.getVarLookupOrThrow("1").getValue()); + assertFalse(snapshot.getVarLookupOrThrow("1").getTruncated()); + assertEquals("value_2", snapshot.getVarLookupOrThrow("2").getValue()); + assertTrue(snapshot.getVarLookupOrThrow("2").getTruncated()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/resource/ResourceDetectorTest.java b/agent/src/test/java/com/intergral/deep/agent/resource/ResourceDetectorTest.java new file mode 100644 index 0000000..4089e05 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/resource/ResourceDetectorTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.resource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.settings.Settings; +import java.util.HashMap; +import org.junit.jupiter.api.Test; + +class ResourceDetectorTest { + + @Test + void loadsResource() { + final HashMap agentArgs = new HashMap<>(); + agentArgs.put(ResourceDetector.ENABLED_PROVIDERS_KEY, ""); + agentArgs.put(ResourceDetector.DISABLED_PROVIDERS_KEY, JavaResourceDetector.class.getName()); + final Settings settings = Settings.build(agentArgs); + final Resource resource = ResourceDetector.configureResource(settings, getClass().getClassLoader()); + + assertNotNull(resource); + assertEquals(0, resource.getAttributes().size()); + } + + @Test + void loadResourceFromSettings() { + final Settings settings = Settings.build(new HashMap<>()); + final Resource resource = ResourceDetector.configureResource(settings, getClass().getClassLoader()); + assertEquals(System.getProperty("java.version"), resource.getAttributes().get("java_version")); + } + + @Test + void loadResourceFromConfig() { + final HashMap agentArgs = new HashMap<>(); + agentArgs.put(ResourceDetector.ATTRIBUTE_PROPERTY, "key=value,other=thing"); + final Settings settings = Settings.build(agentArgs); + final Resource resource = ResourceDetector.configureResource(settings, getClass().getClassLoader()); + + assertEquals("value", resource.getAttributes().get("key")); + assertEquals("thing", resource.getAttributes().get("other")); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/resource/SpiUtilTest.java b/agent/src/test/java/com/intergral/deep/agent/resource/SpiUtilTest.java new file mode 100644 index 0000000..8d5062c --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/resource/SpiUtilTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.resource; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.intergral.deep.agent.api.spi.ResourceProvider; +import java.util.List; +import org.junit.jupiter.api.Test; + +class SpiUtilTest { + + @Test + void loadsOrderedSpi() { + ResourceProvider spi1 = mock(ResourceProvider.class); + ResourceProvider spi2 = mock(ResourceProvider.class); + ResourceProvider spi3 = mock(ResourceProvider.class); + + when(spi1.order()).thenReturn(2); + when(spi2.order()).thenReturn(0); + when(spi3.order()).thenReturn(1); + + SpiUtil.ServiceLoaderFinder mockFinder = mock(SpiUtil.ServiceLoaderFinder.class); + when(mockFinder.load(ResourceProvider.class, SpiUtil.class.getClassLoader())) + .thenReturn(asList(spi1, spi2, spi3)); + + List loadedSpi = SpiUtil.loadOrdered(ResourceProvider.class, getClass().getClassLoader(), mockFinder); + + assertEquals(3, loadedSpi.size()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java b/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java new file mode 100644 index 0000000..0dfb4c7 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.settings; + +import static org.junit.jupiter.api.Assertions.*; + +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.regex.Pattern; +import org.junit.jupiter.api.Test; + +class SettingsTest { + + @Test + void canHandleTypes() { + final HashMap agentArgs = new HashMap<>(); + agentArgs.put("string", "string"); + agentArgs.put("number", "1.2"); + agentArgs.put("list", "one,two"); + agentArgs.put("map", "key=one,other=two"); + agentArgs.put("regex", "intergral(s)"); + agentArgs.put("url", "https://google.com"); + agentArgs.put("debug", "DEBUG"); + agentArgs.put("debug_2", "debug"); + agentArgs.put("level", "FINE"); + + final Settings settings = Settings.build(agentArgs); + + assertEquals("string", settings.getSettingAs("string", String.class)); + assertEquals(1, settings.getSettingAs("number", int.class)); + assertEquals(1L, settings.getSettingAs("number", long.class)); + assertEquals(1.2f, settings.getSettingAs("number", float.class)); + assertEquals(1.2d, settings.getSettingAs("number", double.class)); + + assertEquals("one", settings.getSettingAs("list", List.class).get(0)); + assertEquals("one", settings.getAsList("list").get(0)); + + assertEquals("two", settings.getSettingAs("map", Map.class).get("other")); + assertEquals("two", settings.getMap("map").get("other")); + + assertEquals("intergral(s)", settings.getSettingAs("regex", Pattern.class).pattern()); + assertEquals("https://google.com", settings.getSettingAs("url", URL.class).toString()); + assertEquals(Level.FINEST, settings.getSettingAs("debug", Level.class)); + assertEquals(Level.FINEST, settings.getSettingAs("debug_2", Level.class)); + assertEquals(Level.FINE, settings.getSettingAs("level", Level.class)); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java b/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java new file mode 100644 index 0000000..f1ee678 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.test; + +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.types.TracePointConfig; +import com.intergral.deep.agent.types.snapshot.EventSnapshot; +import com.intergral.deep.agent.types.snapshot.StackFrame; +import com.intergral.deep.agent.types.snapshot.Variable; +import com.intergral.deep.agent.types.snapshot.VariableID; +import com.intergral.deep.agent.types.snapshot.WatchResult; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class MockEventSnapshot extends EventSnapshot { + + public MockEventSnapshot() { + super(new TracePointConfig("tp-1", "/some/file/path.py", 123, new HashMap<>(), new ArrayList<>()), 1011L, Resource.DEFAULT, + new ArrayList<>(), new HashMap<>()); + } + + public MockEventSnapshot withFrames() { + this.getFrames().add(new StackFrame("file_name_1", 4321, "class_name_1", "methodName_1", true, true, new ArrayList<>())); + this.getFrames().add(new StackFrame("file_name_2", 321, "class_name_2", "methodName_2", false, true, new ArrayList<>())); + this.getFrames().add(new StackFrame("file_name_3", 21, "class_name_3", "methodName_3", true, false, new ArrayList<>())); + + final VariableID someLocalVar = new VariableID("var-id-1", "someLocalVar", new HashSet<>(), null); + final HashSet modifiers = new HashSet<>(); + modifiers.add("private"); + final VariableID someLocalVar2 = new VariableID("var-id-2", "someLocalVar2", modifiers, null); + final VariableID someLocalVar3 = new VariableID("var-id-3", "someLocalVar3", new HashSet<>(), "someOtherName"); + final ArrayList frameVariables = new ArrayList<>(); + frameVariables.add(someLocalVar); + frameVariables.add(someLocalVar2); + frameVariables.add(someLocalVar3); + this.getFrames().add(new StackFrame("file_name_4", 1, "class_name_4", "methodName_4", false, false, frameVariables)); + return this; + } + + public MockEventSnapshot withAttributes() { + final HashMap attributes = new HashMap<>(); + attributes.put("long", 123L); + attributes.put("int", 123); + attributes.put("float", 3.21F); + attributes.put("double", 1.23D); + attributes.put("boolean", false); + attributes.put("string", "123l"); + mergeAttributes(Resource.create(attributes)); + return this; + } + + public MockEventSnapshot withWatches() { + getWatches().add(new WatchResult("error", "this is an error")); + getWatches().add(new WatchResult("good", new VariableID("some-var", "withName", new HashSet<>(), null))); + return this; + } + + public MockEventSnapshot withVariables() { + getVarLookup().put("1", new Variable("string", "value_1", "123124", false)); + getVarLookup().put("2", new Variable("string", "value_2", "123124", true)); + return this; + } +} diff --git a/agent/src/test/resources/logging.properties b/agent/src/test/resources/logging.properties new file mode 100644 index 0000000..68285a1 --- /dev/null +++ b/agent/src/test/resources/logging.properties @@ -0,0 +1,3 @@ +.level=FINEST +handlers=java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level=FINEST diff --git a/examples/.gitignore b/examples/.gitignore index 5ff6309..af665ab 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -35,4 +35,4 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/examples/docker/coldfusion/.gitignore b/examples/docker/coldfusion/.gitignore deleted file mode 100644 index bd3f22b..0000000 --- a/examples/docker/coldfusion/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -jvm.config -testFile.cfm \ No newline at end of file diff --git a/examples/docker/coldfusion/jvm.config b/examples/docker/coldfusion/jvm.config new file mode 100644 index 0000000..442c3ff --- /dev/null +++ b/examples/docker/coldfusion/jvm.config @@ -0,0 +1,29 @@ +# +# VM configuration +# +# Where to find JVM, if {java.home}/jre exists then that JVM is used +# if not then it must be the path to the JRE itself + +java.home=/opt/coldfusion/jre + +# +# If no java.home is specified a VM is located by looking in these places in this +# order: +# +# 1) ../runtime/jre +# 2) registry (windows only) +# 3) JAVA_HOME env var plus jre (ie $JAVA_HOME/jre) +# 4) java.exe in path +# + +# Arguments to VM + +java.args=-javaagent:/opt/deep/deep.jar -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -server --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/sun.util.cldr=ALL-UNNAMED --add-opens=java.base/sun.util.locale.provider=ALL-UNNAMED -Xms256m -Xmx1024m -XX:MaxMetaspaceSize=192m -XX:+UseParallelGC -Djdk.attach.allowAttachSelf=true -Dcoldfusion.home={application.home} -Djava.security.egd=/dev/urandom -Djava.awt.headless=true -Duser.language=en -Dcoldfusion.rootDir={application.home} -Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize=true -Djava.security.policy={application.home}/lib/coldfusion.policy -Djava.security.auth.policy={application.home}/lib/neo_jaas.policy -Dcoldfusion.classPath={application.home}/lib/updates,{application.home}/lib,{application.home}/lib/axis2,{application.home}/gateway/lib/,{application.home}/wwwroot/WEB-INF/cfform/jars,{application.home}/wwwroot/WEB-INF/flex/jars,{application.home}/lib/oosdk/lib,{application.home}/lib/oosdk/classes -Dcoldfusion.libPath={application.home}/lib -Dorg.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER=true -Dcoldfusion.jsafe.defaultalgo=FIPS186Random -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog -Djava.util.logging.config.file={application.home}/lib/logging.properties -Djava.locale.providers=COMPAT,SPI -Dsun.font.layoutengine=icu + +# Comma separated list of shared library path +java.library.path={application.home}/lib/international + +# Comma separated list of shared library path for non-windows +java.nixlibrary.path={application.home}/lib + +java.class.path={application.home}/lib/oosdk/lib,{application.home}/lib/oosdk/classes diff --git a/examples/docker/coldfusion/testFile.cfm b/examples/docker/coldfusion/testFile.cfm new file mode 100644 index 0000000..a4a683d --- /dev/null +++ b/examples/docker/coldfusion/testFile.cfm @@ -0,0 +1,3 @@ + + +#i# diff --git a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java index a336fec..bc313b1 100644 --- a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java +++ b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java @@ -53,7 +53,7 @@ public static void main(String[] args) throws Throwable { Deep.config() .setJarPath(jarPath.toAbsolutePath().toString()) .setValue(ISettings.KEY_SERVICE_URL, "localhost:43315") - .setValue("service.secure", false) + .setValue(ISettings.KEY_SERVICE_SECURE, false) .start(); // different ways to get the API instance diff --git a/pom.xml b/pom.xml index 42da979..4009e29 100644 --- a/pom.xml +++ b/pom.xml @@ -434,6 +434,11 @@ junit-jupiter 5.9.3 + + org.mockito + mockito-core + 5.4.0 + @@ -443,6 +448,11 @@ junit-jupiter test + + org.mockito + mockito-core + test + diff --git a/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java b/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java index b9a882f..ba7206a 100644 --- a/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java +++ b/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java @@ -17,9 +17,11 @@ package com.intergral.deep.reflect; +import com.intergral.deep.agent.api.DeepRuntimeException; import com.intergral.deep.agent.api.reflection.IReflection; import com.intergral.deep.agent.api.utils.ArrayIterator; import com.intergral.deep.agent.api.utils.CompoundIterator; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -50,7 +52,17 @@ public boolean setAccessible(final Class clazz, final Method method) { } catch (final Exception e) { return false; } + } + + @Override + public boolean setAccessible(final Class clazz, final Constructor constructor) { + try { + constructor.setAccessible(true); + return true; + } catch (final Exception e) { + return false; + } } @@ -141,7 +153,6 @@ public Field next() { } final Field[] fields = clazz.getFields(); final Field[] declaredFields = clazz.getDeclaredFields(); - //noinspection unchecked return new CompoundIterator<>(new ArrayIterator<>(fields), new ArrayIterator<>(declaredFields)); } @@ -165,4 +176,24 @@ public Set getModifiers(final Field field) { final String string = Modifier.toString(modifiers); return Arrays.stream(string.split(" ")).collect(Collectors.toSet()); } + + @Override + public Constructor findConstructor(final Class clazz, final Class... args) { + try { + return clazz.getConstructor(args); + } catch (NoSuchMethodException e) { + return null; + } + } + + @Override + public T callConstructor(final Constructor constructor, final Object... args) { + try { + setAccessible(constructor.getDeclaringClass(), constructor); + //noinspection unchecked + return (T) constructor.newInstance(args); + } catch (InstantiationException | InvocationTargetException | IllegalAccessException e) { + throw new DeepRuntimeException("Cannot call constructor: " + constructor, e); + } + } } diff --git a/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java b/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java index bba84a1..ae0e4d5 100644 --- a/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java +++ b/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java @@ -17,6 +17,7 @@ package com.intergral.deep.reflect; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -48,6 +49,19 @@ public boolean setAccessible(final Class clazz, final Method method) { } + @Override + public boolean setAccessible(final Class clazz, final Constructor constructor) { + try { + openModule(clazz); + + constructor.setAccessible(true); + return true; + } catch (final Exception e) { + return false; + } + } + + private void openModule(final Class clazz) { final Module m = clazz.getModule(); if (m.isNamed()) { diff --git a/test-utils/pom.xml b/test-utils/pom.xml index 98c63ec..0f963a4 100644 --- a/test-utils/pom.xml +++ b/test-utils/pom.xml @@ -52,6 +52,10 @@ grpc-protobuf provided - + + org.junit.jupiter + junit-jupiter + compile + \ No newline at end of file diff --git a/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java b/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java new file mode 100644 index 0000000..d9082cc --- /dev/null +++ b/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.tests; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Collection; + +public class AssertUtils { + + public static int assertContains(final Collection list, final ICompareFunction compareFunction) { + int index = -1; + for (final T listItem : list) { + index++; + if (compareFunction.compare(listItem)) { + return index; + } + } + fail(String.format("Cannot find %s in list %s", compareFunction, list)); + return -1; + } + + public interface ICompareFunction { + + boolean compare(T item); + } +} diff --git a/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java b/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java new file mode 100644 index 0000000..fd7c274 --- /dev/null +++ b/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.tests.grpc; + +import io.grpc.Context; +import io.grpc.Contexts; +import io.grpc.Metadata; +import io.grpc.Metadata.Key; +import io.grpc.ServerCall; +import io.grpc.ServerCall.Listener; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +public class TestInterceptor implements ServerInterceptor { + + private final String key; + private Context.Key contextKey; + + public TestInterceptor(final String key) { + this.key = key; + } + + public Context.Key contextKey() { + if(contextKey == null){ + contextKey = Context.key(key); + } + return contextKey; + } + + @Override + public Listener interceptCall(final ServerCall call, final Metadata headers, + final ServerCallHandler next) { + final Key grpcKey = Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + final String val = headers.get(grpcKey); + final Context context = Context.current().withValue(contextKey(), val); + return Contexts.interceptCall(context, call, headers, next); + } +} From c39b7c23e0d49870706f6025c59c91e525431076 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 24 Aug 2023 11:38:38 +0100 Subject: [PATCH 02/15] chore(tests): add make file to simplify running stages locally add .sdkman file to set java version via sdkamn --- .github/workflows/cf_tests.yml | 4 ++-- .github/workflows/on_push.yml | 8 ++++---- .sdkmanrc | 3 +++ Makefile | 25 +++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 .sdkmanrc create mode 100644 Makefile diff --git a/.github/workflows/cf_tests.yml b/.github/workflows/cf_tests.yml index 0983675..cc362a2 100644 --- a/.github/workflows/cf_tests.yml +++ b/.github/workflows/cf_tests.yml @@ -15,6 +15,6 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Build agent - run: mvn package -U -B -pl agent --also-make -DskipTests + run: make package - name: Run CF Tests - run: mvn verify -U -B -P cf-it-tests -pl it-tests/cf-tests --also-make \ No newline at end of file + run: make cf-tests \ No newline at end of file diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml index 2bee699..1fb5a7b 100644 --- a/.github/workflows/on_push.yml +++ b/.github/workflows/on_push.yml @@ -21,7 +21,7 @@ jobs: cache: 'maven' - name: Run tests - run: mvn -U -B clean verify + run: make test # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive - name: Update dependency graph @@ -39,7 +39,7 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Check Formatting - run: mvn -U -B validate -Plint,examples,cf-it-tests + run: make check-formatting pmd: runs-on: ubuntu-latest @@ -53,7 +53,7 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Run PMD check - run: mvn -U -B verify -Ppmd + run: make pmd docs: @@ -68,5 +68,5 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Run docs check - run: mvn -s .ci-settings.xml clean package javadoc:jar -DskipTests -P release-ossrh -B -U -pl agent,deep --also-make + run: make docs diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..1d93138 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=13.0.2-open diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b3093f --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ + + +.PHONY: pmd +pmd: + mvn -U -B verify -Ppmd -DskipTests + +,PHONY: check-formatting +check-formatting: + mvn -U -B validate -Plint,examples,cf-it-tests + +.PHONY: test +test: + mvn -U -B clean verify + +.PHONY: docs +docs: + mvn -s .ci-settings.xml clean package javadoc:jar -DskipTests -P release-ossrh -B -U -pl agent,deep --also-make + +.PHONY: package +package: + mvn package -U -B -pl agent --also-make -DskipTests + +.PHONY: cf-tests +cf-tests: + mvn verify -U -B -P cf-it-tests -pl it-tests/cf-tests --also-make From fddf2d8a98602212e215ee972444d7946196160b Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 24 Aug 2023 11:42:28 +0100 Subject: [PATCH 03/15] chore(tests): update the docs for building --- DEVELOPMENT.md | 12 ++++++++++++ README.md | 2 ++ 2 files changed, 14 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 4528416..c077b4b 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,6 +2,18 @@ Here we have a few hints on how to develop this module. +## Environment + +### Java SDK + +To set up the java environment we recommend using [SDKMAN](https://sdkman.io/). Once sdkman is installed simply run `sdk env` in the project +root to set the JDK. + +### Building + +To simplify the building the maven build is split into a few profiles to reduce the build time. There are a few commands in +the [`Makefile`](./Makefile) that can help with the builds. + ## CF Debugging To debug in CF I found the easiest way is to use the docker. We can do this with this command: diff --git a/README.md b/README.md index c32a043..d91cd5d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,9 @@ Deep.start() There are a couple of examples [available here](./examples/README.md). +## Developing +For help developing this see [DEVELOPMENT.md](./DEVELOPMENT.md) ## Licensing From 3fe143594120950dfbbb251f3a9ef940bde99a24 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 24 Aug 2023 11:46:45 +0100 Subject: [PATCH 04/15] chore(tests): fix conflict with make file --- .github/workflows/cf_tests.yml | 2 +- Makefile | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cf_tests.yml b/.github/workflows/cf_tests.yml index cc362a2..9778ff0 100644 --- a/.github/workflows/cf_tests.yml +++ b/.github/workflows/cf_tests.yml @@ -15,6 +15,6 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Build agent - run: make package + run: make package-agent - name: Run CF Tests run: make cf-tests \ No newline at end of file diff --git a/Makefile b/Makefile index 30021a0..1137b3a 100644 --- a/Makefile +++ b/Makefile @@ -2,27 +2,27 @@ .PHONY: pmd pmd: - mvn -U -B verify -Ppmd -DskipTests + mvn -U -B verify -Ppmd -DskipTests $(MVN_ARGS) ,PHONY: check-formatting check-formatting: - mvn -U -B validate -Plint,examples,cf-it-tests + mvn -U -B validate -Plint,examples,cf-it-tests $(MVN_ARGS) .PHONY: test test: - mvn -U -B clean verify + mvn -U -B clean verify $(MVN_ARGS) .PHONY: docs docs: - mvn -s .ci-settings.xml clean package javadoc:jar -DskipTests -P release-ossrh -B -U -pl agent,deep --also-make + mvn -s .ci-settings.xml clean package javadoc:jar -DskipTests -P release-ossrh -B -U -pl agent,deep --also-make $(MVN_ARGS) -.PHONY: package +.PHONY: package-agent package: - mvn package -U -B -pl agent --also-make -DskipTests + mvn package -U -B -pl agent --also-make -DskipTests $(MVN_ARGS) .PHONY: cf-tests cf-tests: - mvn verify -U -B -P cf-it-tests -pl it-tests/cf-tests --also-make + mvn verify -U -B -P cf-it-tests -pl it-tests/cf-tests --also-make $(MVN_ARGS) # This file just contains shortcuts for dev, as there are a lot of options for different builds .PHONY: build From a198a7e11d2ee0740a02e8e4c8c0ecfdb7936cd1 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 24 Aug 2023 12:59:07 +0100 Subject: [PATCH 05/15] chore(tests): add more tests for api and coverage goal --- Makefile | 4 ++ .../intergral/deep/agent/api/DeepVersion.java | 2 +- .../deep/agent/api/plugin/LazyEvaluator.java | 8 ++- .../api/resource/ConfigurationException.java | 31 --------- .../api/resource/ResourceAttributes.java | 10 +-- .../agent/api/tracepoint/ITracepoint.java | 14 +--- .../agent/api/utils/CompoundIterator.java | 3 +- .../agent/api/DeepRuntimeExceptionTest.java | 31 +++++++++ .../deep/agent/api/auth/AuthProviderTest.java | 17 +++++ .../api/plugin/AbstractEvaluatorTest.java | 61 +++++++++++++++++ .../api/plugin/EvaluationExceptionTest.java | 30 +++++++++ .../deep/agent/api/plugin/IPluginTest.java | 53 +++++++++++++++ .../agent/api/plugin/LazyEvaluatorTest.java | 64 ++++++++++++++++++ .../api/reflection/ReflectionUtilsTest.java | 32 +++++++++ .../deep/agent/api/resource/ResourceTest.java | 48 +++++++++++++ .../deep/agent/api/spi/OrderedTest.java | 31 +++++++++ .../agent/api/utils/CompoundIteratorTest.java | 4 ++ .../deep/agent/resource/ResourceDetector.java | 4 +- pom.xml | 67 +++++++++++++++++++ 19 files changed, 462 insertions(+), 52 deletions(-) delete mode 100644 agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/DeepRuntimeExceptionTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/plugin/AbstractEvaluatorTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/plugin/EvaluationExceptionTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/plugin/IPluginTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/plugin/LazyEvaluatorTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/resource/ResourceTest.java create mode 100644 agent-api/src/test/java/com/intergral/deep/agent/api/spi/OrderedTest.java diff --git a/Makefile b/Makefile index 1137b3a..9cd04d3 100644 --- a/Makefile +++ b/Makefile @@ -33,3 +33,7 @@ build: .PHONY: install install: mvn clean install -U -B -pl agent,deep --also-make $(MVN_ARGS) + +.PHONY: coverage +coverage: + mvn clean verify -U -B -P coverage -pl '!it-tests/java-tests,!it-tests' diff --git a/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java b/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java index f2ab9eb..2817720 100644 --- a/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java +++ b/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java @@ -17,7 +17,7 @@ package com.intergral.deep.agent.api; -public class DeepVersion { +public interface DeepVersion { public static final String VERSION = "${project.version}"; } \ No newline at end of file diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java index c40c40d..e4d41ab 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java @@ -18,6 +18,8 @@ package com.intergral.deep.agent.api.plugin; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This type allows the evaluator to be loaded only if it is needed. ie. if we have no watches or conditions there is no need for an @@ -25,6 +27,7 @@ */ public class LazyEvaluator extends AbstractEvaluator { + private final static Logger LOGGER = LoggerFactory.getLogger(LazyEvaluator.class); private static final Exception NO_EVALUATOR_EXCEPTION = new RuntimeException( "No evaluator available."); private final IEvaluatorLoader loader; @@ -39,13 +42,16 @@ private IEvaluator load() { try { this.evaluator = this.loader.load(); } catch (Exception e) { + LOGGER.error("Could not load evaluator", e); + } + if (this.evaluator == null) { this.evaluator = new AbstractEvaluator() { @Override public Object evaluateExpression(final String expression, final Map values) throws Throwable { throw NO_EVALUATOR_EXCEPTION; } }; - } + } } return this.evaluator; } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java deleted file mode 100644 index c560a81..0000000 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ConfigurationException.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.intergral.deep.agent.api.resource; - -/** - * An exception that is thrown if the user-provided configuration is invalid. - */ -public final class ConfigurationException extends RuntimeException { - - /** - * Create a new configuration exception with specified {@code message} and without a cause. - * - * @param message The exception message - */ - public ConfigurationException(String message) { - super(message); - } - - /** - * Create a new configuration exception with specified {@code message} and {@code cause}. - * - * @param message The exception message - * @param cause The root cause of this exception - */ - public ConfigurationException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java index c519721..8f04842 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java @@ -5,7 +5,7 @@ package com.intergral.deep.agent.api.resource; -public class ResourceAttributes { +public interface ResourceAttributes { /** * Logical name of the service. @@ -20,20 +20,20 @@ public class ResourceAttributes { * MUST be set to {@code unknown_service}. * */ - public static final String SERVICE_NAME = "service.name"; + String SERVICE_NAME = "service.name"; /** * The name of the telemetry SDK as defined above. */ - public static final String TELEMETRY_SDK_NAME = "telemetry.sdk.name"; + String TELEMETRY_SDK_NAME = "telemetry.sdk.name"; /** * The language of the telemetry SDK. */ - public static final String TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language"; + String TELEMETRY_SDK_LANGUAGE = "telemetry.sdk.language"; /** * The version string of the telemetry SDK. */ - public static final String TELEMETRY_SDK_VERSION = "telemetry.sdk.version"; + String TELEMETRY_SDK_VERSION = "telemetry.sdk.version"; } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java index 4216275..3f3cce0 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java @@ -19,9 +19,7 @@ import com.intergral.deep.agent.api.IRegistration; import java.util.Collection; -import java.util.Collections; import java.util.Map; -import java.util.UUID; public interface ITracepoint { @@ -29,17 +27,11 @@ public interface ITracepoint { int line(); - default Map args() { - return Collections.emptyMap(); - } + Map args(); - default Collection watches() { - return Collections.emptyList(); - } + Collection watches(); - default String id() { - return UUID.randomUUID().toString(); - } + String id(); interface ITracepointRegistration extends IRegistration { diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java index d74354f..c261a08 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java @@ -18,6 +18,7 @@ package com.intergral.deep.agent.api.utils; import java.util.Iterator; +import java.util.NoSuchElementException; public class CompoundIterator implements Iterator { @@ -47,7 +48,7 @@ public boolean hasNext() { @Override public T next() { - return null; + throw new NoSuchElementException("end of compound iterator"); } }; } diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/DeepRuntimeExceptionTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/DeepRuntimeExceptionTest.java new file mode 100644 index 0000000..a56c216 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/DeepRuntimeExceptionTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class DeepRuntimeExceptionTest { + + @Test + void runtimeException() { + assertEquals("Some message", new DeepRuntimeException("Some message").getMessage()); + assertEquals("Some message", new DeepRuntimeException("Some message", new RuntimeException()).getMessage()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java index fc232bc..c43255d 100644 --- a/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java @@ -18,6 +18,7 @@ package com.intergral.deep.agent.api.auth; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import com.intergral.deep.agent.api.auth.AuthProvider.NoopProvider; import com.intergral.deep.agent.api.plugin.IPlugin; @@ -32,6 +33,13 @@ class AuthProviderTest { + @Test + void coverage() { + // for coverage + //noinspection ObviousNullCheck,InstantiationOfUtilityClass + assertNotNull(new AuthProvider()); + } + @Test void canProvide() { final ISettings settings = Mockito.mock(ISettings.class); @@ -54,6 +62,15 @@ void canLoadProviderByName() { assertEquals(MockAuthProviderPlugin.class, provider.getClass()); } + @Test + void willUseNoopProvider() { + final ISettings settings = Mockito.mock(ISettings.class); + final IReflection reflection = Mockito.mock(IReflection.class); + final IAuthProvider provider = AuthProvider.provider(settings, reflection); + assertEquals(NoopProvider.class, provider.getClass()); + assertEquals(Collections.emptyMap(), provider.provide()); + } + public static class MockAuthProviderPlugin implements IPlugin, IAuthProvider { @Override diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/AbstractEvaluatorTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/AbstractEvaluatorTest.java new file mode 100644 index 0000000..09693c6 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/AbstractEvaluatorTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.plugin; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AbstractEvaluatorTest { + + + private AbstractEvaluator abstractEvaluator; + + @BeforeEach + void setUp() { + abstractEvaluator = new AbstractEvaluator() { + @Override + public Object evaluateExpression(final String expression, final Map values) { + return null; + } + }; + } + + @Test + void evaluate() throws Throwable { + assertNull(abstractEvaluator.evaluateExpression("anything", new HashMap<>())); + assertFalse(abstractEvaluator.evaluate("anything", new HashMap<>())); + } + + @Test + void objectToBoolean() { + assertFalse(LazyEvaluator.objectToBoolean(null)); + assertFalse(LazyEvaluator.objectToBoolean(0)); + assertFalse(LazyEvaluator.objectToBoolean(false)); + assertFalse(LazyEvaluator.objectToBoolean("false")); + + assertTrue(LazyEvaluator.objectToBoolean(1)); + assertTrue(LazyEvaluator.objectToBoolean(true)); + assertTrue(LazyEvaluator.objectToBoolean("true")); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/EvaluationExceptionTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/EvaluationExceptionTest.java new file mode 100644 index 0000000..644119d --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/EvaluationExceptionTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.plugin; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class EvaluationExceptionTest { + + @Test + void getExpression() { + assertEquals("some expression", new EvaluationException("some expression", new RuntimeException()).getExpression()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/IPluginTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/IPluginTest.java new file mode 100644 index 0000000..06de505 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/IPluginTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.plugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class IPluginTest { + + @Test + void name() { + assertEquals("com.intergral.deep.agent.api.plugin.IPluginTest$MockPlugin", new MockPlugin().name()); + } + + @Test + void isActive() { + final ISettings settings = Mockito.mock(ISettings.class); + Mockito.when(settings.getSettingAs("com.intergral.deep.agent.api.plugin.IPluginTest$MockPlugin.active", Boolean.class)).thenReturn(null) + .thenReturn(true).thenReturn(false); + assertTrue(new MockPlugin().isActive(settings)); + assertTrue(new MockPlugin().isActive(settings)); + assertFalse(new MockPlugin().isActive(settings)); + } + + static class MockPlugin implements IPlugin { + + @Override + public Resource decorate(final ISettings settings, final ISnapshotContext snapshot) { + return null; + } + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/LazyEvaluatorTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/LazyEvaluatorTest.java new file mode 100644 index 0000000..d46e611 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/LazyEvaluatorTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.plugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.intergral.deep.agent.api.plugin.LazyEvaluator.IEvaluatorLoader; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class LazyEvaluatorTest { + + private IEvaluatorLoader loader; + private LazyEvaluator lazyEvaluator; + + @BeforeEach + void setUp() { + loader = Mockito.mock(IEvaluatorLoader.class); + Mockito.when(loader.load()).thenReturn(new AbstractEvaluator() { + @Override + public Object evaluateExpression(final String expression, final Map values) { + return null; + } + }); + lazyEvaluator = new LazyEvaluator(loader); + } + + @Test + void evaluateExpression() throws Throwable { + assertNull(lazyEvaluator.evaluateExpression("anything", new HashMap<>())); + assertFalse(lazyEvaluator.evaluate("anything", new HashMap<>())); + + Mockito.verify(loader, Mockito.times(1)).load(); + } + + @Test + void canHandleFailure() { + Mockito.when(loader.load()).thenReturn(null); + final RuntimeException something = assertThrows(RuntimeException.class, + () -> lazyEvaluator.evaluateExpression("something", new HashMap<>())); + assertEquals("No evaluator available.", something.getMessage()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java new file mode 100644 index 0000000..e28b612 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.reflection; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ReflectionUtilsTest { + + @Test + void coverage() { + // for coverage + //noinspection ObviousNullCheck,InstantiationOfUtilityClass + assertNotNull(new ReflectionUtils()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/resource/ResourceTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/resource/ResourceTest.java new file mode 100644 index 0000000..746d8b2 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/resource/ResourceTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.resource; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import org.junit.jupiter.api.Test; + +class ResourceTest { + + @Test + void canCreateDefault() { + assertNotNull(Resource.DEFAULT); + assertNotNull(Resource.DEFAULT.getAttributes().get(ResourceAttributes.TELEMETRY_SDK_NAME)); + assertNotNull(Resource.DEFAULT.getAttributes().get(ResourceAttributes.TELEMETRY_SDK_LANGUAGE)); + assertNotNull(Resource.DEFAULT.getAttributes().get(ResourceAttributes.TELEMETRY_SDK_VERSION)); + assertNull(Resource.DEFAULT.getSchemaUrl()); + } + + @Test + void canMerge() { + assertNotNull(Resource.DEFAULT.merge(null)); + assertNotNull(Resource.DEFAULT.merge(Resource.create(new HashMap<>()))); + + final HashMap attributes = new HashMap<>(); + attributes.put("merged", "thing"); + attributes.put(ResourceAttributes.TELEMETRY_SDK_VERSION, "test_override"); + final Resource merged = Resource.DEFAULT.merge(Resource.create(attributes)); + assertEquals("thing", merged.getAttributes().get("merged")); + assertEquals("test_override", merged.getAttributes().get(ResourceAttributes.TELEMETRY_SDK_VERSION)); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/spi/OrderedTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/spi/OrderedTest.java new file mode 100644 index 0000000..d3ec096 --- /dev/null +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/spi/OrderedTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.api.spi; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class OrderedTest { + + @Test + void order() { + assertEquals(0, new Ordered() { + }.order()); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java index f74e138..ff4e7a9 100644 --- a/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/utils/CompoundIteratorTest.java @@ -19,8 +19,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.NoSuchElementException; import org.junit.jupiter.api.Test; class CompoundIteratorTest { @@ -43,5 +45,7 @@ void next() { assertTrue(iterator.hasNext()); assertEquals("three_3", iterator.next()); assertFalse(iterator.hasNext()); + final NoSuchElementException noSuchElementException = assertThrows(NoSuchElementException.class, iterator::next); + assertEquals("end of compound iterator", noSuchElementException.getMessage()); } } \ No newline at end of file diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java index f361ee0..4754667 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java @@ -5,7 +5,7 @@ package com.intergral.deep.agent.resource; -import com.intergral.deep.agent.api.resource.ConfigurationException; +import com.intergral.deep.agent.api.DeepRuntimeException; import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.api.resource.ResourceAttributes; import com.intergral.deep.agent.api.spi.ConditionalResourceProvider; @@ -79,7 +79,7 @@ static Map getAttributes(Settings settings) { } } catch (UnsupportedEncodingException e) { // Should not happen since always using standard charset - throw new ConfigurationException("Unable to decode resource attributes.", e); + throw new DeepRuntimeException("Unable to decode resource attributes.", e); } String serviceName = settings.getSettingAs(SERVICE_NAME_PROPERTY, String.class); if (serviceName != null) { diff --git a/pom.xml b/pom.xml index 4009e29..9279da7 100644 --- a/pom.xml +++ b/pom.xml @@ -161,6 +161,7 @@ sign + ${env.GPG_PASSPHRASE} @@ -343,6 +344,72 @@ + + coverage + + false + + + + + org.jacoco + jacoco-maven-plugin + 0.8.10 + + + + prepare-agent + + + + + report + test + + report + report-aggregate + + + + default-check + + check + + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0 + + + BRANCH + COVEREDRATIO + 0 + + + CLASS + MISSEDCOUNT + 0 + + + METHOD + MISSEDCOUNT + 0 + + + + + + + + + + + From f40ce5a304e8e5d76e44b668fbaccf7498c6485b Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 24 Aug 2023 23:46:26 +0100 Subject: [PATCH 06/15] chore(tests): add more tests for instrumentation --- .idea/google-java-format.xml | 6 + .idea/runConfigurations/VisitorTest.xml | 19 ++ agent/pom.xml | 23 +- .../java/com/intergral/deep/agent/Utils.java | 2 +- .../agent/tracepoint/ITracepointConfig.java | 28 ++ .../tracepoint/TracepointConfigService.java | 9 +- .../agent/tracepoint/TracepointUtils.java | 14 +- .../deep/agent/tracepoint/cf/CFUtils.java | 51 ++++ .../agent/tracepoint/handler/Callback.java | 3 +- .../tracepoint/handler/FrameProcessor.java | 2 +- .../tracepoint/handler/VariableProcessor.java | 21 +- .../agent/tracepoint/inst/CFClassScanner.java | 45 ++- .../inst/CompositeClassScanner.java | 14 + .../agent/tracepoint/inst/IClassScanner.java | 2 + .../tracepoint/inst/JSPClassScanner.java | 32 +- .../tracepoint/inst/SetClassScanner.java | 5 + .../TracepointInstrumentationService.java | 231 +++++++-------- .../agent/tracepoint/inst/asm/Visitor.java | 6 +- .../agent/tracepoint/inst/jsp/JSPUtils.java | 32 ++ .../inst/jsp/{ => sourcemap}/FileSection.java | 2 +- .../jsp/{ => sourcemap}/FileSectionEntry.java | 2 +- .../inst/jsp/{ => sourcemap}/LineSection.java | 2 +- .../jsp/{ => sourcemap}/LineSectionEntry.java | 2 +- .../inst/jsp/{ => sourcemap}/SmapUtils.java | 2 +- .../inst/jsp/{ => sourcemap}/SourceMap.java | 2 +- .../SourceMapLineStartEnd.java | 2 +- .../jsp/{ => sourcemap}/SourceMapLookup.java | 2 +- .../jsp/{ => sourcemap}/SourceMapParser.java | 2 +- .../jsp/{ => sourcemap}/StratumSection.java | 2 +- .../com/intergral/deep/ProxyCallback.java | 8 + .../main/resources/deep_settings.properties | 9 +- .../runtime/ArgumentCollection.java | 23 ++ .../test/java/coldfusion/runtime/CFPage.java | 23 ++ .../java/coldfusion/runtime/TestScope.java | 25 ++ .../java/coldfusion/runtime/UDFMethod.java | 30 ++ .../java/coldfusion/runtime/package-info.java | 4 + .../TracepointConfigServiceTest.java | 176 +++++++++++ .../agent/tracepoint/TracepointUtilsTest.java | 47 +++ .../TracepointInstrumentationServiceTest.java | 173 +++++++++++ .../tracepoint/inst/asm/VisitorTest.java | 278 ++++++++++++++++++ .../deep/test/MockTracepointConfig.java | 43 +++ .../deep/test/target/BPSuperClass.java | 12 + .../deep/test/target/BPTestTarget.java | 194 ++++++++++++ .../resources/cftestList2ecfm1060358347.class | Bin 0 -> 16208 bytes .../org/apache/jsp/tests/string_jsp.class | Bin 0 -> 5696 bytes pom.xml | 10 + .../deep/tests/inst/ByteClassLoader.java | 53 ++++ .../deep/tests/snapshot/SnapshotUtils.java | 11 + 48 files changed, 1516 insertions(+), 168 deletions(-) create mode 100644 .idea/google-java-format.xml create mode 100644 .idea/runConfigurations/VisitorTest.xml rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/FileSection.java (95%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/FileSectionEntry.java (95%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/LineSection.java (94%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/LineSectionEntry.java (96%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/SmapUtils.java (98%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/SourceMap.java (98%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/SourceMapLineStartEnd.java (94%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/SourceMapLookup.java (94%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/SourceMapParser.java (98%) rename agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/{ => sourcemap}/StratumSection.java (95%) create mode 100644 agent/src/test/java/coldfusion/runtime/ArgumentCollection.java create mode 100644 agent/src/test/java/coldfusion/runtime/CFPage.java create mode 100644 agent/src/test/java/coldfusion/runtime/TestScope.java create mode 100644 agent/src/test/java/coldfusion/runtime/UDFMethod.java create mode 100644 agent/src/test/java/coldfusion/runtime/package-info.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointUtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java create mode 100644 agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java create mode 100644 agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java create mode 100644 agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java create mode 100644 agent/src/test/resources/cftestList2ecfm1060358347.class create mode 100755 agent/src/test/resources/org/apache/jsp/tests/string_jsp.class create mode 100644 test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 0000000..8b57f45 --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/VisitorTest.xml b/.idea/runConfigurations/VisitorTest.xml new file mode 100644 index 0000000..fdc3e62 --- /dev/null +++ b/.idea/runConfigurations/VisitorTest.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/agent/pom.xml b/agent/pom.xml index 970a9ec..3e935b6 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -150,12 +150,23 @@ asm-tree compile + + com.intergral.deep.tests test-utils 1.0-SNAPSHOT test + + + + + org.apache.tomcat + tomcat-jasper + 9.0.6 + test + @@ -469,17 +480,9 @@ false - - - - - - - - - deep.logging.level - INFO + deep.callback.class + com.intergral.deep.agent.tracepoint.handler.Callback diff --git a/agent/src/main/java/com/intergral/deep/agent/Utils.java b/agent/src/main/java/com/intergral/deep/agent/Utils.java index 5e2a916..3dad02d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/Utils.java +++ b/agent/src/main/java/com/intergral/deep/agent/Utils.java @@ -69,7 +69,7 @@ public static long[] currentTimeNanos() { */ public static Map newMap(final Map map) { if (map == null) { - return Collections.emptyMap(); + return new HashMap<>(); } return new HashMap<>(map); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java index 5e2603b..a7faf2b 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java @@ -20,13 +20,41 @@ import com.intergral.deep.agent.types.TracePointConfig; import java.util.Collection; +/** + * This is the interface to the config services. The implementation of this service should act as the manager between incoming tracepoint + * configs and instrumentation. To help reduce the time spent in the instrumentor. + */ public interface ITracepointConfig { + /** + * Called when there is no change to the config so just update last seen. + * + * @param tsNano the time of the config update + */ void noChange(final long tsNano); + /** + * This indicates that the config from the servers has changed, and we should inform the instrumentation services. + * + * @param tsNano the time of the update + * @param hash the new config hash + * @param tracepoints the new config + */ void configUpdate(final long tsNano, final String hash, final Collection tracepoints); + /** + * Get the hash of the config last used to update the config. This hash should be sent with the calls for new configs, so the server knows + * what the clients config is and can detect changes. + * + * @return the current hash. + */ String currentHash(); + /** + * Load the full configs for the given tracepoints ids + * + * @param tracepointId the tracepoint ids + * @return a collection of all the matched tracepoints + */ Collection loadTracepointConfigs(final Collection tracepointId); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java index 38ff7fb..30e6795 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java @@ -85,6 +85,13 @@ public TracePointConfig addCustom(final String path, final int line, final Map current.getId().equals(tracePointConfig.getId())); + final boolean removed = this.customTracepoints.removeIf(current -> current.getId().equals(tracePointConfig.getId())); + if (removed) { + this.processChange(); + } + } + + public long lastUpdate() { + return this.lastUpdate; } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java index 9bdaaa8..87d953e 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java @@ -23,8 +23,15 @@ public class TracepointUtils { + /** + * We normally get set the source file name, we need to convert this to a Java class name. + * + * @param tp the tracepoint to process + * @return the internal class name to install the tracepoint in, or {@code cfm} or {@code jsp} if the computed class is a CFM or JSP + * class. + */ public static String estimatedClassRoot(final TracePointConfig tp) { - // the arg class_name is sent from ATP as it has the full class name already + // we allow the class name to be sent for specific cases final String className = tp.getArg("class_name", String.class, null); if (className != null) { return InstUtils.internalClass(className); @@ -39,11 +46,12 @@ private static String asFullClassName(TracePointConfig tp) { } - static String parseFullClassName(final String rawRelPath, final String srcRootArg) { + private static String parseFullClassName(final String rawRelPath, final String srcRootArg) { + // cf classes are handled specially if (rawRelPath.endsWith(".cfm") || rawRelPath.endsWith(".cfc")) { return "cfm"; } - + // jsp classes are handled specially if (rawRelPath.endsWith(".jsp")) { return "jsp"; } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java index 1ef9d72..f000bc7 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java @@ -18,9 +18,16 @@ package com.intergral.deep.agent.tracepoint.cf; import com.intergral.deep.agent.ReflectionUtils; +import com.intergral.deep.agent.Utils; import com.intergral.deep.agent.api.plugin.IEvaluator; +import com.intergral.deep.agent.types.TracePointConfig; import java.lang.reflect.Method; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Set; public class CFUtils { @@ -146,7 +153,14 @@ public static boolean isLucee(final Object that) { } + /** + * When running on Lucee servers we can guess the source from the class name + * + * @param classname the class we are processing + * @return the source file name, or {@code null} + */ public static String guessSource(final String classname) { + // if the classname isn't a lucee class then skip it if (!classname.endsWith("$cf")) { return null; } @@ -155,4 +169,41 @@ public static String guessSource(final String classname) { .substring(0, classname.length() - 3) .replace("_", "."); } + + + public static Set loadCfBreakpoints(final URL location, + final Map values) { + final Set iBreakpoints = new HashSet<>(); + final Collection breakpoints = values.values(); + for (TracePointConfig breakpoint : breakpoints) { + final String srcRoot = breakpoint.getArgs().get("src_root"); + final String relPathFromNv = breakpoint.getPath(); + final String locationString = location.toString(); + if (srcRoot != null && locationString.endsWith(relPathFromNv.substring(srcRoot.length())) + || locationString.endsWith(relPathFromNv) + || relPathFromNv.startsWith("/src/main/cfml") + && locationString.endsWith(relPathFromNv.substring("/src/main/cfml".length())) + ) { + iBreakpoints.add(breakpoint); + } + } + return iBreakpoints; + } + + public static Set loadCfBreakpoints(final String location, + final Map values) { + if (location == null) { + return Collections.emptySet(); + } + final Set iBreakpoints = new HashSet<>(); + final Collection breakpoints = values.values(); + for (TracePointConfig breakpoint : breakpoints) { + final String relPathFromNv = breakpoint.getPath(); + // some versions of lucee use lowercase file names + if (Utils.endsWithIgnoreCase(relPathFromNv, location)) { + iBreakpoints.add(breakpoint); + } + } + return iBreakpoints; + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java index 3b913d9..e6472f3 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java @@ -62,7 +62,8 @@ protected Boolean initialValue() { private static PushService PUSH_SERVICE; private static int offset; - public static void init(final Settings settings, + public static void init( + final Settings settings, final TracepointConfigService breakpointService, final PushService pushService) { Callback.SETTINGS = settings; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java index d9636c6..ff54248 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java @@ -53,7 +53,7 @@ public boolean canCollect() { && this.conditionPasses(tracePointConfig)) .collect(Collectors.toList()); - return this.filteredTracepoints.size() != 0; + return !this.filteredTracepoints.isEmpty(); } private boolean conditionPasses(final TracePointConfig tracePointConfig) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java index da6bfa2..bdee14c 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java @@ -320,6 +320,7 @@ public Set modifiers() { private static class NamesFieldIterator extends NamedIterable { private final Object target; + private final HashSet seenNames = new HashSet<>(); public NamesFieldIterator(final Object target, final Iterator iterator) { super(iterator); @@ -329,10 +330,12 @@ public NamesFieldIterator(final Object target, final Iterator iterator) { @Override public INamedItem next() { final Field next = this.iterator.next(); + final String name = getFieldName(next); + seenNames.add(next.getName()); return new INamedItem() { @Override public String name() { - return next.getName(); + return name; } @Override @@ -351,6 +354,22 @@ public Set modifiers() { } }; } + + private String getFieldName(final Field next) { + final String name; + // it is possible to have a field that is declared as a private value on a super type. + // here we will prefix the declaring class name of the field if we have already seen the simple name + if (seenNames.contains(next.getName())) { + if (next.getDeclaringClass() == this.target.getClass()) { + name = next.getName(); + } else { + name = String.format("%s.%s", next.getDeclaringClass().getSimpleName(), next.getName()); + } + } else { + name = next.getName(); + } + return name; + } } private static class FieldIterator implements Iterator { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java index ca20e67..be0843d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java @@ -29,28 +29,32 @@ public class CFClassScanner implements IClassScanner { private static final Logger LOGGER = LoggerFactory.getLogger(CFClassScanner.class); - private final Map removedBreakpoints; + protected final Map tracePointConfigMap; - public CFClassScanner(final Map removedBreakpoints) { - this.removedBreakpoints = removedBreakpoints; + public CFClassScanner(final Map tracePointConfigMap) { + this.tracePointConfigMap = tracePointConfigMap; } @Override public boolean scanClass(final Class loadedClass) { - if (removedBreakpoints.isEmpty()) { + // if the map is empty we have already found all our tracepoints + if (tracePointConfigMap.isEmpty()) { // stop as soon as we run out of classes return false; } + // if this is a CF class then process it if (CFUtils.isCfClass(loadedClass.getName())) { try { - final Set breakpoints = loadCfBreakpoints(loadedClass, - removedBreakpoints); + // try to load CF tracepoint - we try-catch as sometimes cfm won't give us a location we can use + final Set breakpoints = loadCfTracepoint(loadedClass, tracePointConfigMap); + // if we found some tracepoints if (!breakpoints.isEmpty()) { + // remove them from our config so we can end fast for (TracePointConfig breakpoint : breakpoints) { - removedBreakpoints.remove(breakpoint.getId()); + tracePointConfigMap.remove(breakpoint.getId()); } return true; } @@ -63,15 +67,29 @@ public boolean scanClass(final Class loadedClass) { } - private Set loadCfBreakpoints(final Class loadedClass, - final Map values) { + /** + * We need to convert the class name from CF which is normally some hashed version of the file path e.g. cftestList2ecfm1060358347, into a + * file path to source. + * + * @param loadedClass the class we are processing + * @param values the current tracepoint configs we are looking to match + * @return the matched tracepoints + */ + private Set loadCfTracepoint( + final Class loadedClass, + final Map values + ) { final URL location = getLocation(loadedClass); + // Lucee will not give us the code location using protection domain if (location == null) { - return TracepointInstrumentationService.loadCfBreakpoints( + // if we cannot get the code source then we guess the source using the provided path name + return CFUtils.loadCfBreakpoints( CFUtils.guessSource(loadedClass.getName()), values); } - return TracepointInstrumentationService.loadCfBreakpoints(location, values); + // Adobe CF should provide the location to the source, so we can use that. + // todo it is possible to run precompiled CF code which would possibly have a different source location + return CFUtils.loadCfBreakpoints(location, values); } @@ -83,4 +101,9 @@ URL getLocation(final Class loadedClass) { URL getLocation(final ProtectionDomain protectionDomain) { return protectionDomain.getCodeSource().getLocation(); } + + @Override + public boolean isComplete() { + return tracePointConfigMap.isEmpty(); + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java index 9895321..9c81e91 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java @@ -55,7 +55,21 @@ public Class[] scanAll(final Instrumentation inst) { && scanClass(allLoadedClass)) { classes.add(allLoadedClass); } + // stop iteration as soon as scanners are complete + if (isComplete()) { + break; + } } return classes.toArray(new Class[0]); } + + @Override + public boolean isComplete() { + for (IClassScanner iClassScanner : scanner) { + if (!iClassScanner.isComplete()) { + return false; + } + } + return true; + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java index db9a213..dc5d893 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java @@ -20,4 +20,6 @@ public interface IClassScanner { boolean scanClass(final Class clazz); + + boolean isComplete(); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java index 0a1174b..dc76750 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java @@ -18,8 +18,6 @@ package com.intergral.deep.agent.tracepoint.inst; -import static com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService.loadJspBreakpoints; - import com.intergral.deep.agent.tracepoint.inst.jsp.JSPUtils; import com.intergral.deep.agent.types.TracePointConfig; import java.util.List; @@ -27,17 +25,20 @@ import java.util.Set; +/** + * This scanner is meant to find JSP classes that have tracepoints. + */ public class JSPClassScanner implements IClassScanner { - private final Map removedBreakpoints; + private final Map tracePointConfigMap; private final String jspSuffix; private final List jspPackages; - public JSPClassScanner(final Map removedBreakpoints, + public JSPClassScanner(final Map tracepoints, final String jspSuffix, final List jspPackages) { - this.removedBreakpoints = removedBreakpoints; + this.tracePointConfigMap = tracepoints; this.jspSuffix = jspSuffix; this.jspPackages = jspPackages; } @@ -45,19 +46,30 @@ public JSPClassScanner(final Map removedBreakpoints, @Override public boolean scanClass(final Class loadedClass) { - if (removedBreakpoints.isEmpty()) { + // if the map is empty we have already found all our tracepoints + if (tracePointConfigMap.isEmpty()) { // stop as soon as we run out of classes return false; } + // if this class is a jsp class then we should process it if (JSPUtils.isJspClass(this.jspSuffix, this.jspPackages, loadedClass.getName())) { - final Set breakpoints = loadJspBreakpoints(loadedClass, removedBreakpoints); - if (!breakpoints.isEmpty()) { - for (TracePointConfig breakpoint : breakpoints) { - removedBreakpoints.remove(breakpoint.getId()); + // load (from our config) the JSP version of a tracepoint. + final Set tracePointConfigs = JSPUtils.loadJSPTracepoints(loadedClass, tracePointConfigMap); + // if we have some tracepoints + if (!tracePointConfigs.isEmpty()) { + // remove them from our config so we can end fast + for (TracePointConfig tracePointConfig : tracePointConfigs) { + tracePointConfigMap.remove(tracePointConfig.getId()); } + // this class is a JSP class with at least on tracepoints return true; } } return false; } + + @Override + public boolean isComplete() { + return tracePointConfigMap.isEmpty(); + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java index 438652b..c303953 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java @@ -37,4 +37,9 @@ public boolean scanClass(final Class allLoadedClass) { || allLoadedClass.getName().contains("$") && this.classNames.contains(InstUtils.internalClassStripInner(allLoadedClass)); } + + @Override + public boolean isComplete() { + return classNames.isEmpty(); + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java index 1cafb51..a9f5a1a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java @@ -27,8 +27,8 @@ import com.intergral.deep.agent.tracepoint.inst.asm.Visitor; import com.intergral.deep.agent.tracepoint.inst.jsp.JSPMappedBreakpoint; import com.intergral.deep.agent.tracepoint.inst.jsp.JSPUtils; -import com.intergral.deep.agent.tracepoint.inst.jsp.SourceMap; -import com.intergral.deep.agent.tracepoint.inst.jsp.SourceMapLineStartEnd; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMap; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMapLineStartEnd; import com.intergral.deep.agent.types.TracePointConfig; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; @@ -59,7 +59,17 @@ public class TracepointInstrumentationService implements ClassFileTransformer { private final String jspSuffix; private final List jspPackages; - private Map> classPrefixBreakpoints = new ConcurrentHashMap<>(); + /** + * We process JSP classes specially, so we collect them all under this class key + */ + private final String JSP_CLASS_KEY = "jsp"; + + /** + * We process CFM classes specially, so we collect them all under this class key + */ + private final String CFM_CLASS_KEY = "cfm"; + + private Map> classPrefixTracepoints = new ConcurrentHashMap<>(); public TracepointInstrumentationService(final Instrumentation inst, final Settings settings) { @@ -70,65 +80,6 @@ public TracepointInstrumentationService(final Instrumentation inst, final Settin this.jspSuffix = settings.getSettingAs("jsp.suffix", String.class); } - static Set loadJspBreakpoints(final Class loadedClass, - final Map jsp) { - return loadJspBreakpoints(JSPUtils.getSourceMap(loadedClass), jsp); - } - - static Set loadJspBreakpoints(final SourceMap sourceMap, - final Map jsp) { - if (sourceMap == null) { - return Collections.emptySet(); - } - - final Set matchedJsp = new HashSet<>(); - final List filenames = sourceMap.getFilenames(); - for (Map.Entry entry : jsp.entrySet()) { - final TracePointConfig value = entry.getValue(); - final String fileName = InstUtils.fileName(value.getPath()); - if (filenames.contains(fileName)) { - matchedJsp.add(value); - } - } - return matchedJsp; - } - - static Set loadCfBreakpoints(final URL location, - final Map values) { - final Set iBreakpoints = new HashSet<>(); - final Collection breakpoints = values.values(); - for (TracePointConfig breakpoint : breakpoints) { - final String srcRoot = breakpoint.getArgs().get("src_root"); - final String relPathFromNv = breakpoint.getPath(); - final String locationString = location.toString(); - if (srcRoot != null && locationString.endsWith(relPathFromNv.substring(srcRoot.length())) - || locationString.endsWith(relPathFromNv) - || relPathFromNv.startsWith("/src/main/cfml") - && locationString.endsWith(relPathFromNv.substring("/src/main/cfml".length())) - ) { - iBreakpoints.add(breakpoint); - } - } - return iBreakpoints; - } - - static Set loadCfBreakpoints(final String location, - final Map values) { - if (location == null) { - return Collections.emptySet(); - } - final Set iBreakpoints = new HashSet<>(); - final Collection breakpoints = values.values(); - for (TracePointConfig breakpoint : breakpoints) { - final String relPathFromNv = breakpoint.getPath(); - // some versions of lucee use lowercase file names - if (Utils.endsWithIgnoreCase(relPathFromNv, location)) { - iBreakpoints.add(breakpoint); - } - } - return iBreakpoints; - } - public static TracepointInstrumentationService init(final Instrumentation inst, final Settings settings) { final TracepointInstrumentationService tracepointInstrumentationService = new TracepointInstrumentationService( @@ -148,8 +99,9 @@ public static TracepointInstrumentationService init(final Instrumentation inst, */ public synchronized void processBreakpoints( final Collection breakpointResponse) { - final Map> existingBreakpoints = this.classPrefixBreakpoints; - final Set newBreakpointOnExistingClasses = new HashSet<>(); + // keep record of existing tracepoints + final Map> existingTracepoints = this.classPrefixTracepoints; + final Set newTracepointOnExistingClasses = new HashSet<>(); final Map> newBreakpoints = new HashMap<>(); // process new breakpoints mapping to new breakpoints map { className -> { breakpoint id -> breakpoint } } @@ -163,49 +115,49 @@ public synchronized void processBreakpoints( newBreakpoints.put(fullClass, value); } - final Map existingConfig = existingBreakpoints.get(fullClass); + // track new tracepoints added to class that already has tracepoints on it + final Map existingConfig = existingTracepoints.get(fullClass); if (existingConfig != null && !existingConfig.containsKey(tracePointConfig.getId())) { - newBreakpointOnExistingClasses.add(fullClass); + newTracepointOnExistingClasses.add(fullClass); } } - this.classPrefixBreakpoints = new ConcurrentHashMap<>(newBreakpoints); + // set the new config of the tracepoints + this.classPrefixTracepoints = new ConcurrentHashMap<>(newBreakpoints); // build class scanners final CompositeClassScanner compositeClassScanner = new CompositeClassScanner(); // scanner to handle classes that no longer have classes and need transformed final IClassScanner removedTracepoints = reTransformClassesThatNoLongerHaveTracePoints( - new HashSet<>(existingBreakpoints.keySet()), new HashSet<>(newBreakpoints.keySet())); + new HashSet<>(existingTracepoints.keySet()), new HashSet<>(newBreakpoints.keySet())); compositeClassScanner.addScanner(removedTracepoints); // scanner to handle classes that now have tracepoints and need transformed final IClassScanner newClasses = reTransformClassesThatAreNew( - new HashSet<>(existingBreakpoints.keySet()), + new HashSet<>(existingTracepoints.keySet()), new HashSet<>(newBreakpoints.keySet())); compositeClassScanner.addScanner(newClasses); // scanner to handle classes that have tracepoints already, but the configs have changed - final SetClassScanner modifiedClasses = new SetClassScanner(newBreakpointOnExistingClasses); + final SetClassScanner modifiedClasses = new SetClassScanner(newTracepointOnExistingClasses); compositeClassScanner.addScanner(modifiedClasses); // scanner to handle JSP classes - if (this.classPrefixBreakpoints.containsKey("jsp") || existingBreakpoints.containsKey("jsp")) { - final Map jsp = this.classPrefixBreakpoints.get("jsp"); - final IClassScanner jspScanner = reTransFormJSPClasses(new HashMap<>( - jsp == null ? Collections.emptyMap() : jsp), - Utils.newMap(existingBreakpoints.get("jsp"))); + if (this.classPrefixTracepoints.containsKey(JSP_CLASS_KEY) || existingTracepoints.containsKey(JSP_CLASS_KEY)) { + final Map jsp = this.classPrefixTracepoints.get(this.JSP_CLASS_KEY); + final IClassScanner jspScanner = reTransFormJSPClasses(Utils.newMap(jsp), + Utils.newMap(existingTracepoints.get(this.JSP_CLASS_KEY))); compositeClassScanner.addScanner(jspScanner); } // scanner to handle CFM classes - if (this.classPrefixBreakpoints.containsKey("cfm") || existingBreakpoints.containsKey("cfm")) { - final Map cfm = this.classPrefixBreakpoints.get("cfm"); - final IClassScanner cfmScanner = reTransFormCfClasses(new HashMap<>( - cfm == null ? Collections.emptyMap() : cfm), - Utils.newMap(existingBreakpoints.get("cfm"))); + if (this.classPrefixTracepoints.containsKey(CFM_CLASS_KEY) || existingTracepoints.containsKey(CFM_CLASS_KEY)) { + final Map cfm = this.classPrefixTracepoints.get(CFM_CLASS_KEY); + final IClassScanner cfmScanner = reTransFormCfClasses(Utils.newMap(cfm), + Utils.newMap(existingTracepoints.get(CFM_CLASS_KEY))); compositeClassScanner.addScanner(cfmScanner); } - LOGGER.debug("New breakpoint config {}", this.classPrefixBreakpoints); + LOGGER.debug("New breakpoint config {}", this.classPrefixTracepoints); try { // scan loaded classes and transform @@ -219,48 +171,97 @@ public synchronized void processBreakpoints( } } - private IClassScanner reTransFormJSPClasses(final Map jsp, - final Map oldJsp) { - final Map removedBreakpoints = withRemoved(jsp, oldJsp); - return new JSPClassScanner(removedBreakpoints, this.jspSuffix, this.jspPackages); + /** + * Calculate the classes to scan for JSP + * + * @param newJSPState the new JSP state + * @param previousJSPState the previous JSP state + * @return the JSP class scanner + */ + private IClassScanner reTransFormJSPClasses( + final Map newJSPState, + final Map previousJSPState) { + final Map allTracepoints = withRemoved(newJSPState, previousJSPState); + return new JSPClassScanner(allTracepoints, this.jspSuffix, this.jspPackages); } - Map withRemoved(final Map jsp, - final Map oldJsp) { - final Set newIds = jsp.keySet(); - final Set oldIds = oldJsp.keySet(); + /** + * Add the removed tracepoints info the new state. + * + * @param newState the new state of tracepoints + * @param previousState the previous state of tracepoints + * @return the newState plus any previous that have been removed + */ + private Map withRemoved( + final Map newState, + final Map previousState) { + final Set newIds = newState.keySet(); + final Set oldIds = previousState.keySet(); oldIds.removeAll(newIds); for (String oldId : oldIds) { - jsp.put(oldId, oldJsp.get(oldId)); + newState.put(oldId, previousState.get(oldId)); } - return jsp; + return newState; } - IClassScanner reTransFormCfClasses(final Map cfm, - final Map oldCfm) { - final Map removedBreakpoints = withRemoved(cfm, oldCfm); + /** + * Calculate the classes to scan for CFM + * + * @param newCFMState the new CFM state + * @param previousCFMState the previous CFM state + * @return the CFM class scanner + */ + //exposed for testing + protected CFClassScanner reTransFormCfClasses( + final Map newCFMState, + final Map previousCFMState) { + final Map allTracepoints = withRemoved(newCFMState, previousCFMState); - return new CFClassScanner(removedBreakpoints); + return new CFClassScanner(allTracepoints); } - public URL getLocation(final ProtectionDomain protectionDomain) { + private URL getLocation(final ProtectionDomain protectionDomain) { return protectionDomain.getCodeSource().getLocation(); } - private IClassScanner reTransformClassesThatAreNew(final Set existingClasses, - final Set newClasses) { - newClasses.removeAll(existingClasses); - return new SetClassScanner(newClasses); + /** + * Calculate the classes that now have tracepoints but did not before. + *

+ * e.g. If the classes {a, b, c} are the previous set and {a, d, e} is the new set. The result would be a set of {d,e}. + * + * @param previousState the list of classes that had tracepoints on them + * @param newState the list of classes that now have tracepoints on them + * @return the set difference between the new and new previous + */ + private IClassScanner reTransformClassesThatAreNew(final Set previousState, + final Set newState) { + newState.removeAll(previousState); + // jsp and cfm are processed special so ignore them here + previousState.remove(JSP_CLASS_KEY); + previousState.remove(CFM_CLASS_KEY); + return new SetClassScanner(newState); } + /** + * Calculate the classes that did have tracepoints but no longer do. + *

+ * e.g. If the classes {a, b, c} are the previous set and {a, d, e} is the new set. The result would be a set of {b,c}. + * + * @param previousState the list of classes that had tracepoints on them + * @param newState the list of classes that now have tracepoints on them + * @return the set difference between the previous and new state + */ private IClassScanner reTransformClassesThatNoLongerHaveTracePoints( - final Set existingClasses, - final Set newClasses) { - existingClasses.removeAll(newClasses); - return new SetClassScanner(existingClasses); + final Set previousState, + final Set newState) { + previousState.removeAll(newState); + // jsp and cfm are processed special so ignore them here + previousState.remove(JSP_CLASS_KEY); + previousState.remove(CFM_CLASS_KEY); + return new SetClassScanner(previousState); } @Override @@ -279,15 +280,15 @@ public byte[] transform(final ClassLoader loader, final Collection matchedTracepoints = matchTracepoints(className, shortClassName); // no breakpoints for this class or any CF classes - if (matchedTracepoints.isEmpty() && !this.classPrefixBreakpoints.containsKey("cfm") - && !this.classPrefixBreakpoints.containsKey("jsp")) { + if (matchedTracepoints.isEmpty() && !this.classPrefixTracepoints.containsKey(CFM_CLASS_KEY) + && !this.classPrefixTracepoints.containsKey(JSP_CLASS_KEY)) { return null; } // no breakpoints for this class, but we have a cfm breakpoints, and this is a cfm class else if (matchedTracepoints.isEmpty() - && this.classPrefixBreakpoints.containsKey("cfm") + && this.classPrefixTracepoints.containsKey(CFM_CLASS_KEY) && CFUtils.isCfClass(classNameP)) { - final Map cfm = this.classPrefixBreakpoints.get("cfm"); + final Map cfm = this.classPrefixTracepoints.get(CFM_CLASS_KEY); final URL location = getLocation(protectionDomain); if (location == null) { reader = new ClassReader(classfileBuffer); @@ -295,9 +296,9 @@ else if (matchedTracepoints.isEmpty() // no need to expand frames here as we only need the version and source file reader.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE); final String sourceFile = cn.sourceFile; - iBreakpoints = loadCfBreakpoints(sourceFile, cfm); + iBreakpoints = CFUtils.loadCfBreakpoints(sourceFile, cfm); } else { - iBreakpoints = loadCfBreakpoints(location, cfm); + iBreakpoints = CFUtils.loadCfBreakpoints(location, cfm); } if (iBreakpoints.isEmpty()) { return null; @@ -306,7 +307,7 @@ else if (matchedTracepoints.isEmpty() } // no breakpoints for this class, but we have a jsp breakpoints, and this is a jsp class else if (matchedTracepoints.isEmpty() - && this.classPrefixBreakpoints.containsKey("jsp") + && this.classPrefixTracepoints.containsKey(JSP_CLASS_KEY) && JSPUtils.isJspClass(this.jspSuffix, this.jspPackages, InstUtils.externalClassName(className))) { isCf = false; @@ -316,8 +317,8 @@ else if (matchedTracepoints.isEmpty() return null; } - final Collection rawBreakpoints = loadJspBreakpoints(sourceMap, - this.classPrefixBreakpoints.get("jsp")); + final Collection rawBreakpoints = JSPUtils.loadJSPTracepoints(sourceMap, + this.classPrefixTracepoints.get(JSP_CLASS_KEY)); if (rawBreakpoints.isEmpty()) { LOGGER.debug("Cannot load tracepoints for class: {}", className); return null; @@ -382,12 +383,12 @@ else if (matchedTracepoints.isEmpty() private Collection matchTracepoints(final String className, final String shortClassName) { - final Map classMatches = this.classPrefixBreakpoints.get(className); + final Map classMatches = this.classPrefixTracepoints.get(className); if (classMatches != null) { return classMatches.values(); } - final Map shortClassMatches = this.classPrefixBreakpoints.get( + final Map shortClassMatches = this.classPrefixTracepoints.get( shortClassName); if (shortClassMatches != null) { return shortClassMatches.values(); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java index de31ba6..a76392a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java @@ -109,9 +109,9 @@ public class Visitor extends ClassVisitor { static { // this is here to make the tests easier. // we cannot use java. classes in the tests without screwing with the class loaders - // so in the tests we use the 'nv.callback.class' which is the CallBack.class - // at runtime we use the ProxyCallback.class so we can bypass the osgi classloading restrictions - final String property = System.getProperty("nv.callback.class"); + // so in the tests we use the 'deep.callback.class' which is the CallBack.class + // at runtime we use the ProxyCallback.class, so we can bypass the osgi classloading restrictions + final String property = System.getProperty("deep.callback.class"); if (property == null) { CALLBACK_CLASS = ProxyCallback.class; } else { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java index 2ae25da..2419e4b 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java @@ -17,8 +17,17 @@ package com.intergral.deep.agent.tracepoint.inst.jsp; +import com.intergral.deep.agent.tracepoint.inst.InstUtils; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SmapUtils; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMap; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMapParser; +import com.intergral.deep.agent.types.TracePointConfig; import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; public class JSPUtils { @@ -76,4 +85,27 @@ public static SourceMap getSourceMap(byte[] classfileBuffer) { return null; } } + + public static Set loadJSPTracepoints(final Class loadedClass, + final Map jsp) { + return loadJSPTracepoints(getSourceMap(loadedClass), jsp); + } + + public static Set loadJSPTracepoints(final SourceMap sourceMap, + final Map jsp) { + if (sourceMap == null) { + return Collections.emptySet(); + } + + final Set matchedJsp = new HashSet<>(); + final List filenames = sourceMap.getFilenames(); + for (Map.Entry entry : jsp.entrySet()) { + final TracePointConfig value = entry.getValue(); + final String fileName = InstUtils.fileName(value.getPath()); + if (filenames.contains(fileName)) { + matchedJsp.add(value); + } + } + return matchedJsp; + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/FileSection.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSection.java similarity index 95% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/FileSection.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSection.java index 8d171be..323bc1a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/FileSection.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSection.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import java.util.ArrayList; import java.util.HashMap; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/FileSectionEntry.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntry.java similarity index 95% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/FileSectionEntry.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntry.java index cd61b7f..2547687 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/FileSectionEntry.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntry.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; public class FileSectionEntry { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/LineSection.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSection.java similarity index 94% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/LineSection.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSection.java index ec499f1..26f2462 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/LineSection.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSection.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import java.util.ArrayList; import java.util.Iterator; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/LineSectionEntry.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntry.java similarity index 96% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/LineSectionEntry.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntry.java index 5e9fb82..75bcc31 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/LineSectionEntry.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntry.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; public class LineSectionEntry { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SmapUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java similarity index 98% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SmapUtils.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java index 1b33b58..d3203ab 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SmapUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import static org.objectweb.asm.Opcodes.ASM7; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMap.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMap.java similarity index 98% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMap.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMap.java index 574880b..f99eddd 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMap.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMap.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import java.util.ArrayList; import java.util.HashMap; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapLineStartEnd.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapLineStartEnd.java similarity index 94% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapLineStartEnd.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapLineStartEnd.java index 46052b8..37a548d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapLineStartEnd.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapLineStartEnd.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; public class SourceMapLineStartEnd { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapLookup.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapLookup.java similarity index 94% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapLookup.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapLookup.java index ac8a36c..bdd57a3 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapLookup.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapLookup.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; public class SourceMapLookup { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapParser.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParser.java similarity index 98% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapParser.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParser.java index f93a329..9b522d9 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/SourceMapParser.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParser.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import java.io.BufferedReader; import java.io.IOException; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/StratumSection.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/StratumSection.java similarity index 95% rename from agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/StratumSection.java rename to agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/StratumSection.java index 1f63e89..6d73929 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/StratumSection.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/StratumSection.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.tracepoint.inst.jsp; +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; public class StratumSection { diff --git a/agent/src/main/java/java/com/intergral/deep/ProxyCallback.java b/agent/src/main/java/java/com/intergral/deep/ProxyCallback.java index 1d6db10..e342b39 100644 --- a/agent/src/main/java/java/com/intergral/deep/ProxyCallback.java +++ b/agent/src/main/java/java/com/intergral/deep/ProxyCallback.java @@ -22,6 +22,14 @@ import java.util.Map; import java.util.Set; +/** + * This type is here to allow us to access it from anywhere (once it is loaded into the boot class path). + *

+ * This will simply act as a proxy to the {@link Callback} which is where we do the real work. + *

+ * This split is to allow us to support Lucee and other OSGi style environments that use isolated class loaders. + */ +@SuppressWarnings("unused") public class ProxyCallback { /** diff --git a/agent/src/main/resources/deep_settings.properties b/agent/src/main/resources/deep_settings.properties index 8c4b63d..35cbe98 100644 --- a/agent/src/main/resources/deep_settings.properties +++ b/agent/src/main/resources/deep_settings.properties @@ -32,4 +32,11 @@ in.app.exclude= # Use this to tell GRPC to use PreferHeapByteBufAllocator grpc.heap.allocator=false grpc.allocator=pooled -plugins=com.intergral.deep.plugin.JavaPlugin,com.intergral.deep.plugins.cf.CFPlugin \ No newline at end of file +plugins=com.intergral.deep.plugin.JavaPlugin,com.intergral.deep.plugins.cf.CFPlugin + +# Define what the jsp compilation convention is +# Default tomcat take index.jsp and make it into index_jsp.class +# some versions put this in a jsp package, some use org.apache.jsp (newer) +# but you can configure this in jspc, see packageRoot. +jsp.suffix=_jsp +jsp.packages=org.apache.jsp,jsp diff --git a/agent/src/test/java/coldfusion/runtime/ArgumentCollection.java b/agent/src/test/java/coldfusion/runtime/ArgumentCollection.java new file mode 100644 index 0000000..76a3b4b --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/ArgumentCollection.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +@SuppressWarnings("ALL") +public class ArgumentCollection extends TestScope +{ +} diff --git a/agent/src/test/java/coldfusion/runtime/CFPage.java b/agent/src/test/java/coldfusion/runtime/CFPage.java new file mode 100644 index 0000000..89b58cc --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/CFPage.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +@SuppressWarnings("ALL") +public class CFPage +{ +} diff --git a/agent/src/test/java/coldfusion/runtime/TestScope.java b/agent/src/test/java/coldfusion/runtime/TestScope.java new file mode 100644 index 0000000..0a6cd02 --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/TestScope.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +import java.util.HashMap; + +@SuppressWarnings("ALL") +public class TestScope extends HashMap +{ +} diff --git a/agent/src/test/java/coldfusion/runtime/UDFMethod.java b/agent/src/test/java/coldfusion/runtime/UDFMethod.java new file mode 100644 index 0000000..4da5d1c --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/UDFMethod.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +@SuppressWarnings("ALL") +public class UDFMethod +{ + private final String key; + + + public UDFMethod( final String key ) + { + this.key = key; + } +} diff --git a/agent/src/test/java/coldfusion/runtime/package-info.java b/agent/src/test/java/coldfusion/runtime/package-info.java new file mode 100644 index 0000000..123501f --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/package-info.java @@ -0,0 +1,4 @@ +/** + * These packages and classes are intended to act as stubs to let us load Cf classes during testing. + */ +package coldfusion.runtime; \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java new file mode 100644 index 0000000..16e0374 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService; +import com.intergral.deep.agent.types.TracePointConfig; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +class TracepointConfigServiceTest { + + private TracepointInstrumentationService instrumentationService; + private TracepointConfigService tracepointConfigService; + + @BeforeEach + void setUp() { + instrumentationService = Mockito.mock(TracepointInstrumentationService.class); + tracepointConfigService = new TracepointConfigService(instrumentationService); + } + + @Test + void noChange() { + tracepointConfigService.configUpdate(1010110, "hash", Collections.emptyList()); + assertEquals(1010110, tracepointConfigService.lastUpdate()); + assertEquals("hash", tracepointConfigService.currentHash()); + Mockito.verify(instrumentationService, Mockito.times(1)).processBreakpoints(Mockito.anyCollection()); + + tracepointConfigService.noChange(202020); + assertEquals(202020, tracepointConfigService.lastUpdate()); + assertEquals("hash", tracepointConfigService.currentHash()); + Mockito.verify(instrumentationService, Mockito.times(1)).processBreakpoints(Mockito.anyCollection()); + } + + @Test + void customCallsUpdate() { + // add a custom tracepoint will call instrumentation update + final TracePointConfig tracePointConfig = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(), + Collections.emptyList()); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class); + + Mockito.verify(instrumentationService, Mockito.times(1)).processBreakpoints(captor.capture()); + + final Collection value = captor.getValue(); + assertEquals(1, value.size()); + final TracePointConfig next = value.iterator().next(); + + assertEquals(tracePointConfig.getId(), next.getId()); + + // remove a custom tracepoint will call instrumentation update + tracepointConfigService.removeCustom(tracePointConfig); + + Mockito.verify(instrumentationService, Mockito.times(2)).processBreakpoints(captor.capture()); + + final Collection captorValue = captor.getValue(); + assertEquals(0, captorValue.size()); + + // remove a tp that doesn't exist will not call instrumentation update + tracepointConfigService.removeCustom(tracePointConfig); + + Mockito.verify(instrumentationService, Mockito.times(2)).processBreakpoints(Mockito.anyCollection()); + } + + @Test + void canLoadTracepointConfigs() { + + final TracePointConfig tracePointConfig1 = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(), + Collections.emptyList()); + final TracePointConfig tracePointConfig2 = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(), + Collections.emptyList()); + final TracePointConfig tracePointConfig3 = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(), + Collections.emptyList()); + + { + final Collection tracePointConfigs = tracepointConfigService.loadTracepointConfigs( + Collections.singletonList(tracePointConfig1.getId())); + assertEquals(1, tracePointConfigs.size()); + assertEquals(tracePointConfig1.getId(), tracePointConfigs.iterator().next().getId()); + } + + { + final Collection tracePointConfigs = tracepointConfigService.loadTracepointConfigs( + Collections.singletonList(tracePointConfig2.getId())); + assertEquals(1, tracePointConfigs.size()); + assertEquals(tracePointConfig2.getId(), tracePointConfigs.iterator().next().getId()); + } + + { + final Collection tracePointConfigs = tracepointConfigService.loadTracepointConfigs( + Arrays.asList(tracePointConfig2.getId(), tracePointConfig3.getId())); + assertEquals(2, tracePointConfigs.size()); + assertArrayEquals(tracePointConfigs.stream().map(TracePointConfig::getId).toArray(), + new String[]{tracePointConfig2.getId(), tracePointConfig3.getId()}); + } + } + + @Test + void configUpdate() { + // TP config is passed to instrumentation + tracepointConfigService.configUpdate(10101, "hash", + Collections.singletonList(new TracePointConfig("some-id", "path", 123, Collections.emptyMap(), Collections.emptyList()))); + + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class); + + Mockito.verify(instrumentationService, Mockito.times(1)).processBreakpoints(captor.capture()); + + { + final Collection value = captor.getValue(); + assertEquals(1, value.size()); + assertEquals("some-id", value.iterator().next().getId()); + } + + // custom and config are passed to instrumentation + final TracePointConfig customTp = tracepointConfigService.addCustom("path", 123, Collections.emptyMap(), + Collections.emptyList()); + + Mockito.verify(instrumentationService, Mockito.times(2)).processBreakpoints(captor.capture()); + + { + final Collection tracePointConfigs = captor.getValue(); + assertEquals(2, tracePointConfigs.size()); + + assertArrayEquals(tracePointConfigs.stream().map(TracePointConfig::getId).toArray(), + new String[]{"some-id", customTp.getId()}); + } + + //custom remains after update + tracepointConfigService.configUpdate(10101, "hash", + Collections.singletonList(new TracePointConfig("some-id", "path", 123, Collections.emptyMap(), Collections.emptyList()))); + + Mockito.verify(instrumentationService, Mockito.times(3)).processBreakpoints(captor.capture()); + + { + final Collection tracePointConfigs = captor.getValue(); + assertEquals(2, tracePointConfigs.size()); + + assertArrayEquals(tracePointConfigs.stream().map(TracePointConfig::getId).toArray(), + new String[]{"some-id", customTp.getId()}); + } + // config remains after custom removed + tracepointConfigService.removeCustom(customTp); + + Mockito.verify(instrumentationService, Mockito.times(4)).processBreakpoints(captor.capture()); + + { + final Collection tracePointConfigs = captor.getValue(); + assertEquals(1, tracePointConfigs.size()); + + assertArrayEquals(tracePointConfigs.stream().map(TracePointConfig::getId).toArray(), + new String[]{"some-id"}); + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointUtilsTest.java new file mode 100644 index 0000000..07c5551 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointUtilsTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.intergral.deep.test.MockTracepointConfig; +import org.junit.jupiter.api.Test; + +class TracepointUtilsTest { + + @Test + void estimatedClassRoot() { + assertEquals("com/intergral/class/name", + TracepointUtils.estimatedClassRoot(new MockTracepointConfig().withArg("class_name", "com.intergral.class.name"))); + assertEquals("some", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("some.java"))); + assertEquals("src/some", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("/src/some.java"))); + assertEquals("some", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("/src/some.java").withArg("src_root", "/src"))); + assertEquals("some", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("some.java").withArg("src_root", "src/main/java"))); + + assertEquals("cfm", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("some.cfm").withArg("src_root", "src/"))); + assertEquals("cfm", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("some.cfc").withArg("src_root", "src/"))); + + assertEquals("jsp", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("some.jsp").withArg("src_root", "src/"))); + + assertEquals("hello/world/HelloController", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("/src/main/java/hello/world/HelloController.java").withArg("src_root", "/src/main/java"))); + assertEquals("hello/world/HelloController", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("/src/main/java/hello/world/HelloController.java"))); + + assertEquals("docker/Dockerfile", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("/docker/Dockerfile"))); + assertEquals(".gitignore", TracepointUtils.estimatedClassRoot(new MockTracepointConfig("/.gitignore"))); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java new file mode 100644 index 0000000..0d1ca47 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.times; + +import com.intergral.deep.Person; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.types.TracePointConfig; +import com.intergral.deep.test.MockTracepointConfig; +import com.intergral.deep.tests.inst.ByteClassLoader; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +class TracepointInstrumentationServiceTest { + + private Instrumentation instrumentation; + private Settings settings; + private TracepointInstrumentationService tracepointInstrumentationService; + + @BeforeEach + void setUp() throws MalformedURLException { + instrumentation = Mockito.mock(Instrumentation.class); + settings = Mockito.mock(Settings.class); + Mockito.when(settings.getSettingAs("jsp.packages", List.class)).thenReturn(Arrays.asList("org.apache.jsp", "jsp")); + Mockito.when(settings.getSettingAs("jsp.suffix", String.class)).thenReturn("_jsp"); + final URL cfurl = new URL("file:///src/main/cfml/testList.cfm"); + tracepointInstrumentationService = new TracepointInstrumentationService(instrumentation, settings) { + /** + * To let us test Adobe CF classes we need to load the protection domain, to get the code source url. + *

+ * We do this by intercepting the call to {@link TracepointInstrumentationService#reTransFormCfClasses(Map, Map)} + * and mocking the {@link CFClassScanner#getLocation(Class)} method. + * + * @param newCFMState the new CFM state + * @param previousCFMState the previous CFM state + * + * @return the modified {@link CFClassScanner} */ + @Override + protected CFClassScanner reTransFormCfClasses(final Map newCFMState, + final Map previousCFMState) { + final CFClassScanner iClassScanner = super.reTransFormCfClasses(newCFMState, previousCFMState); + return new CFClassScanner(iClassScanner.tracePointConfigMap) { + @Override + URL getLocation(final Class loadedClass) { + if (loadedClass.getName().equals("cftestList2ecfm1060358347")) { + return cfurl; + } + return super.getLocation(loadedClass); + } + }; + } + }; + } + + @Test + void initShouldRegister() { + final TracepointInstrumentationService init = TracepointInstrumentationService.init(instrumentation, settings); + assertNotNull(init); + Mockito.verify(instrumentation).addTransformer(Mockito.any(), Mockito.anyBoolean()); + } + + @Test + void processTracepointsShouldNotReTransform() throws UnmodifiableClassException { + // no matched class do not re-transform + Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{}); + tracepointInstrumentationService.processBreakpoints(Collections.singletonList(new MockTracepointConfig())); + + Mockito.verify(instrumentation, Mockito.never()).retransformClasses(Mockito.any()); + } + + @Test + void matchedTracepointShouldReTranform() throws UnmodifiableClassException { + Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{Person.class}); + Mockito.when(instrumentation.isModifiableClass(Person.class)).thenReturn(true); + tracepointInstrumentationService.processBreakpoints( + Collections.singletonList(new MockTracepointConfig("/com/intergral/deep/Person.java"))); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Class.class); + + Mockito.verify(instrumentation).retransformClasses(argumentCaptor.capture()); + + final Class value = argumentCaptor.getValue(); + assertEquals(value, Person.class); + } + + @Test + void removingTPShouldReTransform() throws UnmodifiableClassException { + Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{Person.class}); + Mockito.when(instrumentation.isModifiableClass(Person.class)).thenReturn(true); + tracepointInstrumentationService.processBreakpoints( + Collections.singletonList(new MockTracepointConfig("/com/intergral/deep/Person.java"))); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Class.class); + + Mockito.verify(instrumentation, times(1)).retransformClasses(argumentCaptor.capture()); + + final Class value = argumentCaptor.getValue(); + assertEquals(value, Person.class); + + tracepointInstrumentationService.processBreakpoints(Collections.emptyList()); + + Mockito.verify(instrumentation, times(2)).retransformClasses(argumentCaptor.capture()); + + final Class value2 = argumentCaptor.getValue(); + assertEquals(value2, Person.class); + } + + @Test + void cfClassesTriggerTransform() throws IOException, ClassNotFoundException, UnmodifiableClassException { + final String cfClassName = "cftestList2ecfm1060358347"; + final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(cfClassName); + final Class cfClass = byteClassLoader.loadClass(cfClassName); + + assertNotNull(cfClass); + + Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{cfClass}); + Mockito.when(instrumentation.isModifiableClass(cfClass)).thenReturn(true); + + tracepointInstrumentationService.processBreakpoints(Collections.singletonList(new MockTracepointConfig("/src/main/cfml/testList.cfm"))); + + Mockito.verify(instrumentation, times(1)).retransformClasses(cfClass); + tracepointInstrumentationService.processBreakpoints(Collections.emptyList()); + + Mockito.verify(instrumentation, times(2)).retransformClasses(cfClass); + } + + @Test + void jspClassesTriggerTransform() throws IOException, ClassNotFoundException, UnmodifiableClassException { + final String jspClassName = "org/apache/jsp/tests/string_jsp"; + final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(jspClassName); + final Class jspClass = byteClassLoader.loadClass(InstUtils.externalClassName(jspClassName)); + + assertNotNull(jspClass); + + Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{jspClass}); + Mockito.when(instrumentation.isModifiableClass(jspClass)).thenReturn(true); + + tracepointInstrumentationService.processBreakpoints(Collections.singletonList(new MockTracepointConfig("/src/main/webapp/tests/string.jsp"))); + + Mockito.verify(instrumentation, times(1)).retransformClasses(jspClass); + tracepointInstrumentationService.processBreakpoints(Collections.emptyList()); + Mockito.verify(instrumentation, times(2)).retransformClasses(jspClass); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java new file mode 100644 index 0000000..0dc8e83 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst.asm; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.logging.Logger; +import com.intergral.deep.agent.push.PushService; +import com.intergral.deep.agent.push.PushUtils; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.tracepoint.TracepointConfigService; +import com.intergral.deep.agent.tracepoint.handler.Callback; +import com.intergral.deep.agent.tracepoint.inst.InstUtils; +import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService; +import com.intergral.deep.agent.types.TracePointConfig; +import com.intergral.deep.agent.types.snapshot.EventSnapshot; +import com.intergral.deep.proto.tracepoint.v1.Snapshot; +import com.intergral.deep.proto.tracepoint.v1.Variable; +import com.intergral.deep.test.MockTracepointConfig; +import com.intergral.deep.test.target.BPTestTarget; +import com.intergral.deep.tests.inst.ByteClassLoader; +import com.intergral.deep.tests.snapshot.SnapshotUtils; +import com.intergral.deep.tests.snapshot.SnapshotUtils.IVariableScan; +import java.lang.instrument.Instrumentation; +import java.lang.reflect.Constructor; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +/** + * This test can be used to play with visitor, it uses the {@link BPTestTarget} class to install tracepoints into + *

+ * To run this use the 'VisitorTest' saved config for idea, or add + * {@code -Ddeep.callback.class=com.intergral.deep.agent.tracepoint.handler.Callback} to the test runner args + *

+ * WARNING: The line numbers used in this test are important, they must match the line numbers on the test target classes!! + */ +class VisitorTest { + + private final Settings settings = Mockito.mock(Settings.class); + private final TracepointConfigService tracepointConfigService = Mockito.mock(TracepointConfigService.class); + private final PushService pushService = Mockito.mock(PushService.class); + private final Instrumentation instrumentation = Mockito.mock(Instrumentation.class); + private final String disPath = Paths.get(Paths.get(".").normalize().toAbsolutePath().getParent().toString(), "dispath").toString(); + + private final AtomicReference tracepointRef = new AtomicReference<>(); + private TracepointInstrumentationService instrumentationService; + + @BeforeEach + void setUp() { + // set up logging to help with debugging tests + Mockito.when(settings.getSettingAs("logging.level", Level.class)).thenReturn(Level.FINEST); + Logger.configureLogging(settings); + + Mockito.when(settings.getResource()).thenReturn(Resource.DEFAULT); + + // for these tests we do not care about these (they are tested in the TracepointInstrumentationServiceTest) + // we just need them to pass, as we manage the instrumentation ourselves. + Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[0]); + Mockito.when(instrumentation.isModifiableClass(Mockito.any())).thenReturn(true); + + // these settings are needed for jsp transforms + Mockito.when(settings.getSettingAs("jsp.packages", List.class)).thenReturn(Arrays.asList("org.apache.jsp", "jsp")); + Mockito.when(settings.getSettingAs("jsp.suffix", String.class)).thenReturn("_jsp"); + + Mockito.when(tracepointConfigService.loadTracepointConfigs(Mockito.any())).thenAnswer(invocationOnMock -> { + final TracePointConfig tracePointConfig = tracepointRef.get(); + if (tracePointConfig == null) { + return Collections.emptyList(); + } + return Collections.singletonList(tracePointConfig); + }); + + instrumentationService = new TracepointInstrumentationService(instrumentation, settings); + + Callback.init(settings, tracepointConfigService, pushService); + } + + // test a line within the constructor + @Test + void constructor() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 21); + tracepointRef.set(tracepointConfig); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("my test", 4); + assertNotNull(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + assertTrue(scanResult.found()); + assertEquals("my test", scanResult.variable().getValue()); + } + + // test the last line of the constructor closing brace + @Test + void constructor_end_line() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 23); + tracepointRef.set(tracepointConfig); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("my test", 4); + assertNotNull(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // the parameter to the constructor should be available + assertTrue(scanResult.found()); + assertEquals("my test", scanResult.variable().getValue()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("my test", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namemy test", superName.variable().getValue()); + } + + // test first line in constructor (super call) + @Test + void constructor_start_line() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 20); + tracepointRef.set(tracepointConfig); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("my test", 4); + assertNotNull(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // the parameter to the constructor should be available + assertTrue(scanResult.found()); + assertEquals("my test", scanResult.variable().getValue()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' but it should not have a value as our TP is before this is set + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("null", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namemy test", superName.variable().getValue()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java new file mode 100644 index 0000000..f323372 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.test; + +import com.intergral.deep.agent.types.TracePointConfig; +import java.util.ArrayList; +import java.util.HashMap; + +public class MockTracepointConfig extends TracePointConfig { + + public MockTracepointConfig() { + super("tp-id", "path", 123, new HashMap<>(), new ArrayList<>()); + } + + public MockTracepointConfig(final String path) { + super("tp-id", path, 123, new HashMap<>(), new ArrayList<>()); + } + + + public MockTracepointConfig(final String path, final int line) { + super("tp-id", path, line, new HashMap<>(), new ArrayList<>()); + } + + public MockTracepointConfig withArg(final String key, final String value) { + this.getArgs().put(key, value); + return this; + } +} diff --git a/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java b/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java new file mode 100644 index 0000000..7b04984 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java @@ -0,0 +1,12 @@ +package com.intergral.deep.test.target; + +@SuppressWarnings({"FieldCanBeLocal", "unused"}) +public class BPSuperClass { + + private final String name; + private final int notOnSubClass = 11; + + public BPSuperClass(final String name) { + this.name = name; + } +} diff --git a/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java b/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java new file mode 100644 index 0000000..7a9eaa2 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java @@ -0,0 +1,194 @@ +package com.intergral.deep.test.target; + +import com.intergral.deep.agent.tracepoint.handler.Callback; +import java.util.Random; + +/** + * This is a target for many test breakpoints - be careful with line numbers, and do not format the code. + *

+ * The methods ending in _visited are what the code will be converted to (only includes the line ends) and is useful when using the asm + * plugin to see the code. + */ +// @formatter:off +@SuppressWarnings({"unused", "ThrowFromFinallyBlock"}) public class BPTestTarget extends BPSuperClass +{ + private String name; + + + public BPTestTarget( final String name, int i ) + { + super("i am a name" + name ); + i += 1; + this.name = name; + } + + + public String getName_visited() + { + if( name == null ) + { + try{return null;}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + return name; + } + + public void setName_visited( final String name ) + { + try{this.name = name;}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + + public String getName() + { + if( name == null ) + { + return null; + } + return name; + } + + public void setName( final String name ) + { + this.name = name; + } + + + public String callSomeThing_visited( final String val ) + { + try{return getName() + val;}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + + + public String callSomeThing( final String val ) + { + return getName() + val; + } + + + public int errorSomething_visited( final String withargs ) + { + try{return withargs.length();}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + + + public int errorSomething( final String withargs ) + { + return withargs.length(); + } + + + public int throwSomething_visited( final String withargs ) + { + try{throw new RuntimeException(withargs);}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + + + public int throwSomething( final String withargs ) + { + throw new RuntimeException( withargs ); + } + + + public String catchSomething_visited( final String withargs ) + { + try + { + throw new RuntimeException( withargs ); + } + catch( RuntimeException re ) + { + try{return re.getMessage();}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + } + + + public String catchSomething( final String withargs ) + { + try + { + throw new RuntimeException( withargs ); + } + catch( RuntimeException re ) + { + return re.getMessage(); + } + } + + + public String finallySomething_visited( final String withargs ) + { + try + { + throw new RuntimeException( withargs ); + } + catch( RuntimeException re ) + { + return re.getMessage(); + } + finally + { + try{System.out.println("something in finally");}catch( Throwable t ){ Callback.callBackException( t ); throw t; }finally {Callback.callBackFinally(null,null);} + } + } + + + public String finallySomething( final String withargs ) + { + try + { + throw new RuntimeException( withargs ); + } + catch( RuntimeException re ) + { + return re.getMessage(); + } + finally + { + System.out.println( "something in finally" ); + } + } + + public String someFunctionWithABody(final String someArg){ + final String name = this.name; + final String newName = name + someArg; + final Random random = new Random(); + final int i = random.nextInt(); + return i + newName; + } + + + public void checkEnd( final int val, final int max ) throws Exception + { + if( val > max ) + { + throw new Exception( "Hit max executions " + val + " " + max ); + } + } + + + public void breakSomething() + { + for( int i = 0; i < 10; i++ ) + { + if( i == 5 ) + { + System.out.println( "do something" ); + break; + } + } + } + + + public void continueSomething() + { + for( int i = 0; i < 10; i++ ) + { + if( i == 5 ) + { + System.out.println( "do something" ); + continue; + } + System.out.println( "something else" ); + } + } +} +//@formatter:on diff --git a/agent/src/test/resources/cftestList2ecfm1060358347.class b/agent/src/test/resources/cftestList2ecfm1060358347.class new file mode 100644 index 0000000000000000000000000000000000000000..977b8f29780fd8c4d6228e7ded3cb1a03077d258 GIT binary patch literal 16208 zcmcIr3t*g8l|DDG`7@a$LsClr(#Q0b$F$9>ZJJEdK%1m(N}9$beM9SH=1)6ik_j`D zK18d6EcnL9$`%n+lqM>QprMEozz43Y?)nz1C@uoKin3M_Q0RW={P&-kq|>^(yTG0A z+}Anho_p@Ok3W3l)h|3mxwJB;O_94lHWm+A8zWImQC)3pG+7&pMZ-g5iAZduHl*>$ zkt6X~ELqz=u)a2FC6e8dM6xC{G_1%B4LJo37E0>t8tYcAX-*T)S$XA_G;re1V4+*yTP&p7w29d%HUaI@tgQE%wl2yKX6r z($l$Xz@Q2bRoG=!n7_`R4ukw2^4mo#(5$Ox<2Hk8JXB+s)hWvF?`-cv!v+sE*oCW^ z-}TpvhwS(2?Sd>!lhEa8vUVVN09-fGW>%$3(d|Ybbu4F)LTsBb^xYpEOU4oi z3WzPQ1B?0#29_4vHSA@q75C5(9bk5tj9dgogAQRW?Z+JQ)GMmYoFLsNi&`BXiqbIi z8G%j3-Uf}LyG-2Dv{9DYUb8e3TiOwc!*sFuF?bN-WCquEp6wPzi_-mS?0z+oSWRX> zgD@ekp;){pIGka^%s`bnXq&Nz4$~1urRkU^2@dO`U~i@+ zY(`xdUKQC^qCE_~XpYkx*<^2;Hqu(%1a6QyQWsqZBkhL=wvWYmQFcd0tezNxPE{9s z#arkG=JgNos?bo_F|9#2VHF2MN$i0Jy$!9S*zgQ`2i$o7;b1fp4kj%OZ+$Wu-w}+C z!G>36j?)SDX;b|TC7G7j-8}RzdN=ENv!X(bWxZ(Tq4)B7zn#&#BAP0pJ8L(gVPU`>$7!Xm)OHDaWCD+M*SQL5{S44O+q&w zoNs-9yFsVyA@!rmLtn6m^dQ4Q{UD-Uc#}0^#UmlKanaY@^cZ~=iiTtT$za@GQ#dSjdFY?%8?5KQz;(m1^*Dva zH^jonJoGIte~i8jt-~=#*1yp=usxo;eQA-9)ju}M8;py->!$C~cc6Ya)*eiT5aH%z z8hhX%wi7;)N%Z~!{g8$Ih&?d2F)|X2Mxnyve2#mJegb2lz@VRko7>$w7Uk{Z2}Mgo z9ICR!H;zC{yJN9YD1_}Z?Tt<#;|4Xlfk97t=ofa-`S)q9onRmVO}BQ@udt>=Ls4)y z=o!S@YsRejF@yf&?^wL&59t{mm)a}dMZac$`weCU{`MLc>!IJy-1qcUNHy#1qUYW8 zU-UaSJwW4~9C~L4I2Sf)nzKJ{C1TOT*7}j~`sq9Eg^%zpdo!%(Ec=Wi$Dk@l<-(vU z4{l{04MoSomZ1ue*>AVt0J0}DjHWsost^H0xo3nJT`wUV zD4g*NW#oJ+GE_N^Y%@o{VQgr~io4XsXxh=$*V#U>t#7xXF5!z!k6X=FCd^Z7s7rZs z86CAoa2Q#Uxwf*5*)Fm>sp z!dc&wR;IGeP&HUx+p%w0GxneJjt?WNQ}ryc0d{Sz4Ysw`LdZ19hFXoe=(c9=56gvH7Xv9 zCJc3@qUF<+XNPmDvpL2-?14CR^p6eN=MIHiLd7%=G9$eIFp>F>p$4JdImO(>P*|UN z1G{@W4K)PU!?DLu*be0qcRqI<@?oa6cFmhkm2YBav2QZmey}!2OSAn{kV_d zpj=hA!cfN)^;B23q1Kns)jWFN%5(JLL8XGobjItWQHN(ecwVb$i{^>b#Q%;R>-dH= zzxukron41DggZpo(a=zDRJ6&~FOV^dkr8ak&hp~*Yxncv)-}8`Wc#|k++Zxt6HENf ziW(|?eB4}DnXahx;np#RqWZeJ%B<`0`mBL^EXoUdF~SXpj~f(~>ubH^QjCVWQBn2@ za++zU&Io32Rn(bvPMt1{8Rob^uz5S&|3bmW;b^FL;_~jc9l5m@4;(m7s(0I4;ZUsR zU~)KWsQ2(GFE$biCOztX*i)2J?^m?=d?!wvo;YU6c{Aft&^d47?4+v=b&E%RKu%5y z_n?fn&S?`TC*9ViJ}76+A#gU-hY&^LRxoU+JKX9vzO5OD^{b|#a6`(Aj10vL^)Uo7 z91opm50AQw4-L1elW1Wx!X=4sQ!K=zK55r}3Y*G)Yj`wyY#=lwqgZ60D5gJC>BA)& zwBVGZKCSo$r0!9hrG-~f`+?>~iZ=<|A_gT1kBZth$0UtB1X{{Y?v(KF=d82LHd1E|i`J$1Lt9`K% zo3@6MzGTeD0SK=#!@-eoB75)7VCW#aLRqMj7>!0ijG$*Y!iPA#6!`c?mk5YxN2-h> zGmbhHyg=}@aJ7svc(s-A9gB^zB0=AXbp)JoRP*s$iAJF&bPgT^XAUSq4!|sq?STkwStDsdTqeS>EJo~D?F(DU zU?lq2BUY-bn@hORykkBvbZOSG%4ld(}AI!l$T=%t+zZye%CJnz2Y zSe)GzM=lmAK6(DBJ{+-*WDE~)if~aY6d8(yd|W51C1k}hi6e4;JP*I=n|>;0-ir`~ z`?!QqL*b>v7=}M|;Havo?A)WSM@_2x@wBJ#_EC|(lc!#Y_!%rRh?#GXMWeQZxzrbM zkgv+T&^hOHJh8ac7v1VXg~t*;wrz!M-dSc|TyzJ7za*jt9*Mip`>m;k_2$n!PV^k$ z7Yg)jkA(poZxj4|2pd7RPXtac`wI-(M`Iz~eqpxu8DEqTk}>!EfdMDP7=KHcF9w_* ze$!tPa5a&w9J^NP$qpYzrhU24!FtA1CdQaQjNzcQ>cTNU*~mX@ag58DvGnTC{(}-aa#jJ$9bn7T(Wh` zd{jtvLtB^K4SLAXM4txxSF~yntr{R8Z7oztW4i%{n44_C7;}@WU>W@K5P1mbD(!^D zD*G3V&;o|TFD$3Uqf}Z-?P}q&S`4&WR9Ov4x+Qu_OX0BE{so`hf&g-F{k(8+F@(HrdSGU3}KiZzL1O~R>3 zI5i2UCQ-gwv}qP;&C;t`jMFTAo275F^lg^D&C<7-wcw*rJ*pOD@y_%K3{K{Z4Ge7U zK;$Ls>mY~$k{BS00Srs!)46~xz4L3|<=#Ai`Kd@xL;dK9@?{OMb_|2X>RqAcNGzT$rJ`u%0) z%U4X&C7&k~^rf1f4|<`d7lB@)>1CiRHC+vQxu#cwuGMrs=vA6t1A48dn?IXU&u6Qk z)q^tXtLkf_azZ_(z7A8)Qvd7>asJCt|7s%d&Hm5P<&(6r8@Q#%U)DWIz01q`CTS-M zs>^my(tiK)vfw0H$gi1}M}av>k+Q2#QLN;aTj}R$cnvqam)?-Oa+2QsFkK(W^Lt&B zbfcM9_SUkum%US+qW4VF`z8AUKR7G{hf{Q$U4Dm@f28bAJA0R8$ICu(iaw2!&rZ_k zrR4sy2kZu4lJ$-DP z%{DeJ&lnqLr;m+GGRDT)hsu8DjLi*|zHYi@Y(+dakTN!)9-D{9=5fYWY`V?j^w>&x zY$a2HQto(qqEt`o^0J@vWZz3qc}wkqJst4+y_ne>O>fz=W&bI&@!TZ+-t>wF-bp&M zyYZqEo)^+KdC^;9q8u~+a-huboy(oeq;=UO{dsp|`3cW)r}gJ(W!WpXTb*TZffFps z`;t<-bIaEDPpbTK^7C@|0S|4a(ITz{#Syey>lrY_^w3x&VSAr&RMPwT>Ctxyws#$vd8R-3iqJ z78@K*s_m{e;|`}35_rwor5l~nX_Jtu-i&HE$BX$UW>V&NmfyRG=XaLOFHDK~z4!#p zm7%9uo3kcW*R5xN!?*=YuS^AIk5l!y`{cO$*XC@;NiQ*HnU_qeEA#@K!%jM9DqwQA z?gAA3iy-u|@6KBg62QX1+ngs?>3X37g;zx|Qt2A^8;`Mou43Li%fd+ZWAe4+w88lhv+31#vswDjtqZ?FDxb--T! z@J-oI5@>U^GmqK5bDmSJd88L;=8~(^3u$Jf)RG$po0GKtZ;&_5*|NJ}jE&L>aa}v^nlcwV6SBngnM=}ZWhqa{(y72Q?v|Wb23d!BAO%l= zIx3d&HtC_yG+%1yuVXE-zKzuSuJB7QvqGi}j*azQnPq)f==BZM>${3|E_iI*Rb*B< zI#-(&W_4O;KkMwD3M@C#t$SiQG=06+!0Rlimjf&OUKsdBbA_mYlN3}~nMH*aS_Pn1 zp+;2b7%#ZVtZ`JRHCLFmX%*^Ng}SLgy@_t!6ZKHxxTAvO$S(&P{N4bo&>$)_hzhH+ zsL-HQ0BRLhvkLi-j2Gmas~r{Am<{HdvuFg%-2PY)Pxo$||%@1=gA9);+NfDqQENP?++C zHmyRNsL&=VT$V+JHmw3st8h81koVwt-dXc?NGlDB;GlMJVrw3Oq$q25T%?PfX{XYq=9b%de39g+STtP~3 z1?u3sk!_Ltnep5+=0?XBo6HV#Q#!bAW?O8Y3UqPz(-U2=#XmUHQIwjFEq-r1+hR)| z9|gC_bX<{TI=1NP0P5-J78O>E=Z>1)jtX1NE#}trbo8(aJyU^gV2tV51{H3yRd~u< ztW|hA(Cha?!yC=s83DdmR6-;~fPc-2oY|?!hpqPiJ;37(BWJ1$2YA(oA-xSlI`?3J zA=#zr^9$O7?S&3>sm*8FX5bu{ydD$QkP}m%-;2^4%|5XajwV>h{aL21Pfr_=r|n`D z7#OD&)41F9cbnl{4jbW9>a!;(U)_@uM=yFW5^=b_;a-H}@QtP= zzKSCne09k2)%r>2;$T|ZSAlF<*y8}(}P_ z>OAA)>Q|C~Rz1g<*OnL0I}SXWcda1j->LJA$MfDM`FG~Mmofj@g5m-*r?sHGU^b)V zt8|{xELc{+SXHo6@M_oXu21B&x<2MQ$ymVo2XvnC6Rt_gKj3A78OK#%&lGihqTvu`fqmvl%h$BaHk%ATRm=A@>aDOI|AZ3nR-bEwSY>vOGqX$H?*oA&-&e2|^wt%VT7D zf{@3^@)%j3Amj<2EiGa!Eh#N$%&CL?jaoiXy?3lzCPk;}+(fg+cY5JdlI>uKFJS4w9+wgEEBr0cs0{ z`XTzdh58Zt776uZ)Ov(^96gGK`U$8Kp-!W=RH&b-pOIInC(!pIp?lP`?7_ON4qF)EuFnL9Hp&v(Rs@Q2&9xmkRZtsGTR&uhC<^ zP`?4SK&av=vyh& z3#hFU>QCrVE!2yk{6f8i+T}vMjJ_*``ZM~j6zVUitr6-K^r#i;RZw+8okeXuQ#pjb z4MHjOT_scwYF7)Diymu)$^+FXR6c6g3RQr|D(W6BuH>g&jicq^w zC=dF!2~~`~mkCvZ+RKG1MUVADc|mOu>LS#(3sr_W>JX|NeLIDkh1!imU5p-^gqjU% zvrw0ywo9lvkh?`F6Me4`YA$NKg}M|ywhA>5RF6>eQM*m31(@Gnp%$X=YlQNFU!PEm zP}VQhV)PggY6-Y(7iuY}9YQTb?M|U8KzZIZsGk@JXx zoMb(1pt(5(WR@mryQDX2=a#lAHAXMmx51~y&(8v qx%ww2)U6My2Y2UwNmbsj9=cCGFq3zkJ_uoN1h*Xgi*SeEmj4FEx{Kcc literal 0 HcmV?d00001 diff --git a/agent/src/test/resources/org/apache/jsp/tests/string_jsp.class b/agent/src/test/resources/org/apache/jsp/tests/string_jsp.class new file mode 100755 index 0000000000000000000000000000000000000000..47736f9b8a38ca49a761d66a7c8f3931fdb37e8b GIT binary patch literal 5696 zcma)A3wRXO75;Btvup;C6_8bs1w~-9W&;S=1ksR$kQkDbjUZS}hs`7z*zBw`vk-hy zUoBc6)oLr$K5d&;`w-ATq?M{Iwzl@&+V{iO*1jLMwxxcp|DD-vW;X#Od~@&IIrp4% z&-u^e?sNZp;zDnUtG&*$$mY)&CbLf-FED_?N~;-ubT@B$^;hpi8MQ- zTX8F!c8mc%zRqznt#)ROX6p(n1Y##9C0WAET1mZ8&*uMt0f)N+9hyy+cs^7HW;^cYe!0V+!Iht?t7iUX)b+tk1J72OBPezJ@Kq#q~hzf z42dQsjJ~wyWG!7_S>e}J-fK!r_r(*gQR;jvZDzW!ZZ$c~^u!pRn%y+#cCAk@5|+>% zlAhxez?BIENQN^C875PjZA*SLy7g4IKA5p|+cwN}@2DYWyIcq3dMe(O|2k%d>5^Ty zk+vNzozw@kG_|v&E(iY^y;sYooV@cQix%g!K7sotL13PWNeC)9hdiVl&u{BAyPw7j zVm=nAI1jS~Dpt8V$~}uzOvPydLe@{R3e6I1?T71JznIaB90oKPV4ERi5K}i z+_80=o^+RWrt-95vZA}qm zp*AzAr8a7oA>ZA_AWE@-rgi!ax_O@8F=RI`Ea{fXu1RGidAOdIGp8Tf%Zt}0fSd2M zIXp|{FHunm)pJ~@2j_ec7E1D9y`VtSiGe+pS~o~E=edPlY;t)Va2eluh)f7v5Z#4<^&Nlo)wRDIkqU+ zEU>WfND@vHN>_0O)(T9Y*cFo#4q?EA>B3dPRRX6=#&I_{cJ*84P7mB?jQX=Eldz1? z3#U#v(y3cj%)rbbdeEzY0aN4Kwym%L6-miu7ZrWzXQ1&cMFawCPHba>rS6UB$fPJw z*}|+SP${R4tecj}lH|TguNg@yzM)u_%o%Rc%sF<0esz{xy{a&j{)>XE89s%c&xy>_OoEN90v@3#g*O^@MswH_SY5j^x{4jx zDNx?DVM7PuUtDtwiHW;ixK_b6ba$cj@keq`uHrge?*&yMr-_29kb*&FW4DU8$>d!@ zLux5oioZ$4&A5eio!(!sJN+iJ`t;HK=9 z@J^n;K9w?e25=_>xVfn-7FpL+-x!N@b|g5@@uC~tthBFtn_=<*4p%0Tz=COFc zGmw(Dcu2)Z@iBoZJfEB6(vCz_QIXAakV$e##V7DdZlUu!*{0-EII))ki4lTIJ)jDjvmtMlKJP@LD&4`QwU` zFGSq-&J}b}#bbD!0O#J0EQ57?F_U6j?aU_2aCECKfI|XjwCg)`E7EMH(^{n2*r7)@ zo7u=^>{mtvL@R`{q-a6#s1%-@$jihGEZu_-#Pf9P)gE_$; zw)t|%d>upvzm`qvixS?yQSn>6%*tfxEt ztX8})xb=h0@hLauB4hNj)jg0)p`r1yLL+7TF*>)1d9nU9yD1?&X9Bu005glok~-M! z(uTabl+&FY?UMwXWI;wVd^(L8oq^`vG}juk-JWXaud~$>Q>+G~7tyWRzNSG(Ps_^` zmay|EMG4Bmt1Qlx7Z2W)gR&9km&u%$U<%iHxyZjy<2$>2-}@TbkCJwcY+>B}0i1zZ zqzLoPA`kg*#bKOz5RoI8zxfc(J&1(|urRcE1XU&2B&SQprpvskIEdx@xw8Z-`J9gc zpVO)D>C|-&H8`8-noF2Pc#e5kPKpa%YEUw7NICg&j)Lpu(L;FC7`qC0ajLsT9+m{C zMIA?hRk(;kz2mQT4^hokNmF(J7eC6;Q_178$j75B&!e8^80FDW%wu6OkJyAf8c)V! zc`=Wg33)VOEj5>p*yp+$SQ&i`&CjAby1DIXZ+cer03y+0w2WZAz%UXc*d*{cnya1} zLPgaxMpy9i_5Xp^~{;{hx~m-p${stg6LR@DCbLQPU#WPyeo62c?(yj zqVzRH6^KR!R~{#0fo{Atr{|q~myn54s`f1A<@DrqN>nO&7|F+>`Bc+9)#efa5+>)m zqhGa)soEt}t)8l_A#^Um0$OEEwP>+wyyp8bVV zs}%b~_sO+=s0zKuo6HYA;7!6~RIzE|{?NV=Jje~AwoKHPi`t4<=tCp;NC_k|s$!yS zS_Eao_;~yIwH0LW@FqS!Egzqej{~)3;j-HD&~U91E(;wV#uH=1QSa?3cL*!Pc!uAO zd5d4pFFseW{yf)*V33>g6uz+!&Eaw>;TRT#sl>PTVoq!X-xJu2O1bdEA*gcv$q>pm zg)5F>a=1cD`uPwhA3F6~lOG2(xMfqc7n=etkdT+=G|!GOiS*A}nUp z@I7LMsHYn|G(AU10~-k61QT5slh0*XjEw~Qj;X}@`p#fckqff<4@MIacUT*&iqu3r3ireport report-aggregate + + + + **/java/com/intergral/deep/** + + default-check @@ -403,6 +409,10 @@ + + + **/java/com/intergral/deep/** + diff --git a/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java b/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java new file mode 100644 index 0000000..b57cf1f --- /dev/null +++ b/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java @@ -0,0 +1,53 @@ +package com.intergral.deep.tests.inst; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class ByteClassLoader extends ClassLoader { + + private final Map bytes = new HashMap<>(); + + public static byte[] loadBytes(final String name) throws IOException { + final byte[] bytes; + try (InputStream resourceAsStream = ByteClassLoader.class.getResourceAsStream("/" + name + ".class")) { + bytes = new byte[resourceAsStream.available()]; + resourceAsStream.read(bytes); + } + return bytes; + } + + public static ByteClassLoader forFile(final String name) throws IOException { + final byte[] loadedBytes = loadBytes(name); + final ByteClassLoader byteClassLoader = new ByteClassLoader(); + byteClassLoader.setBytes(name, loadedBytes); + return byteClassLoader; + } + + public void setBytes(final String name, final byte[] bytes) { + this.bytes.put(name, bytes); + } + + public byte[] getBytes(final String name) { + return this.bytes.get(name); + } + + @Override + public Class loadClass(final String name) throws ClassNotFoundException { + final byte[] bytes = this.bytes.get(name); + if (bytes != null) { + return defineClass(name, bytes, 0, bytes.length); + } + return super.loadClass(name); + } + + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + final byte[] bytes = this.bytes.get(name); + if (bytes != null) { + return defineClass(name, bytes, 0, bytes.length); + } + return super.findClass(name); + } +} diff --git a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java index e95da64..d4e730c 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java @@ -17,6 +17,7 @@ package com.intergral.deep.tests.snapshot; +import com.intergral.deep.proto.tracepoint.v1.Snapshot; import com.intergral.deep.proto.tracepoint.v1.Variable; import com.intergral.deep.proto.tracepoint.v1.VariableID; import java.util.List; @@ -26,6 +27,16 @@ public class SnapshotUtils { + /** + * Scan a snapshot for a variable with the given name + * + * @param name the variable name + * @param snapshot the snapshot + * @return a {@link IVariableScan} + */ + public static IVariableScan findVarByName(final String name, final Snapshot snapshot) { + return findVarByName(name, snapshot.getFrames(0).getVariablesList(), snapshot.getVarLookupMap()); + } /** * Scan a snapshot for a variable with a given name. * From 44d8e28d63b90d5b3e88fc4b765de30ebf9e5cb4 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 25 Aug 2023 15:52:47 +0100 Subject: [PATCH 07/15] chore(tests): add more tests for visitor --- .../intergral/deep/agent/push/PushUtils.java | 40 +- .../tracepoint/handler/FrameCollector.java | 64 +- .../TracepointInstrumentationService.java | 3 +- .../deep/agent/types/snapshot/StackFrame.java | 14 +- .../runtime/AttributeCollection.java | 24 + .../test/java/coldfusion/runtime/CFPage.java | 5 +- .../java/coldfusion/runtime/CfJspPage.java | 41 + .../java/coldfusion/runtime/LocalScope.java | 22 + .../coldfusion/runtime/NeoPageContext.java | 31 + .../java/coldfusion/runtime/Variable.java | 22 + .../coldfusion/runtime/VariableScope.java | 22 + .../coldfusion/tagext/io/DirectoryTag.java | 23 + .../java/coldfusion/tagext/io/FileTag.java | 22 + .../java/coldfusion/tagext/io/OutputTag.java | 24 + .../java/coldfusion/tagext/lang/LoopTag.java | 22 + .../tracepoint/inst/asm/VisitorTest.java | 1117 ++++++++++++++++- .../deep/test/MockEventSnapshot.java | 12 +- .../deep/test/target/BPTestTarget.java | 4 +- 18 files changed, 1474 insertions(+), 38 deletions(-) create mode 100644 agent/src/test/java/coldfusion/runtime/AttributeCollection.java create mode 100644 agent/src/test/java/coldfusion/runtime/CfJspPage.java create mode 100644 agent/src/test/java/coldfusion/runtime/LocalScope.java create mode 100644 agent/src/test/java/coldfusion/runtime/NeoPageContext.java create mode 100644 agent/src/test/java/coldfusion/runtime/Variable.java create mode 100644 agent/src/test/java/coldfusion/runtime/VariableScope.java create mode 100644 agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java create mode 100644 agent/src/test/java/coldfusion/tagext/io/FileTag.java create mode 100644 agent/src/test/java/coldfusion/tagext/io/OutputTag.java create mode 100644 agent/src/test/java/coldfusion/tagext/lang/LoopTag.java diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java index b82d0cf..b18ca29 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java @@ -26,6 +26,7 @@ import com.intergral.deep.proto.common.v1.KeyValue; import com.intergral.deep.proto.tracepoint.v1.Snapshot; import com.intergral.deep.proto.tracepoint.v1.StackFrame; +import com.intergral.deep.proto.tracepoint.v1.StackFrame.Builder; import com.intergral.deep.proto.tracepoint.v1.TracePointConfig; import com.intergral.deep.proto.tracepoint.v1.Variable; import com.intergral.deep.proto.tracepoint.v1.WatchResult; @@ -109,22 +110,29 @@ private static com.intergral.deep.proto.tracepoint.v1.VariableID convertVariable private static Iterable convertFrames( final Collection frames) { - return frames.stream().map(stackFrame -> StackFrame.newBuilder() - .setFileName(stackFrame.getFileName()) - .setMethodName(stackFrame.getMethodName()) - .setLineNumber(stackFrame.getLineNumber()) - .setClassName(stackFrame.getClassName()) - // Java does not have async frames or column Numbers - //.setIsAsync( false ) - //.setColumnNumber( 0 ) - //todo update for JSP -// .setTranspiledFileName( "" ) -// .setTranspiledLineNumber( 0 ) -// .setTranspiledColumnNumber( 0 ) - .addAllVariables(covertVariables(stackFrame.getFrameVariables())) - .setAppFrame(stackFrame.isAppFrame()) - .setNativeFrame(stackFrame.isNativeFrame()) - .build()).collect(Collectors.toList()); + return frames.stream().map(stackFrame -> { + final Builder builder = StackFrame.newBuilder() + .setFileName(stackFrame.getFileName()) + .setMethodName(stackFrame.getMethodName()) + .setLineNumber(stackFrame.getLineNumber()) + .setClassName(stackFrame.getClassName()) + // Java does not have async frames or column Numbers + //.setIsAsync( false ) + //.setColumnNumber( 0 ) + // .setTranspiledColumnNumber(0) + .addAllVariables(covertVariables(stackFrame.getFrameVariables())) + .setAppFrame(stackFrame.isAppFrame()) + .setNativeFrame(stackFrame.isNativeFrame()); + + // only set transpiled if they are set + if (stackFrame.getTranspiledFile() != null) { + builder.setTranspiledFileName(stackFrame.getTranspiledFile()); + } + if (stackFrame.getTranspiledLine() != -1) { + builder.setTranspiledLineNumber(stackFrame.getTranspiledLine()); + } + return builder.build(); + }).collect(Collectors.toList()); } private static Iterable covertVariables( diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java index 575f768..b6b3768 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java @@ -22,6 +22,9 @@ import com.intergral.deep.agent.settings.Settings; import com.intergral.deep.agent.tracepoint.handler.bfs.Node; import com.intergral.deep.agent.tracepoint.inst.InstUtils; +import com.intergral.deep.agent.tracepoint.inst.jsp.JSPUtils; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMap; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMapLookup; import com.intergral.deep.agent.types.TracePointConfig; import com.intergral.deep.agent.types.snapshot.StackFrame; import com.intergral.deep.agent.types.snapshot.Variable; @@ -45,6 +48,8 @@ public class FrameCollector extends VariableProcessor { private final StackTraceElement[] stack; private final Map varCache = new HashMap<>(); + private final String jspSuffix; + private final List jspPackages; public FrameCollector(final Settings settings, final IEvaluator evaluator, final Map variables, @@ -53,6 +58,8 @@ public FrameCollector(final Settings settings, final IEvaluator evaluator, this.evaluator = evaluator; this.variables = variables; this.stack = stack; + this.jspSuffix = settings.getSettingAs("jsp.suffix", String.class); + this.jspPackages = settings.getAsList("jsp.packages"); } protected IFrameResult processFrame() { @@ -87,8 +94,7 @@ private StackFrame processFrame(final StackTraceElement stackTraceElement, final boolean collectVars, final int frameIndex) { final String className = stackTraceElement.getClassName(); - final int lineNumber = stackTraceElement.getLineNumber(); - final String fileName = getFileName(stackTraceElement); + final boolean nativeMethod = stackTraceElement.isNativeMethod(); final boolean appFrame = isAppFrame(stackTraceElement); final String methodName = getMethodName(stackTraceElement, variables, frameIndex); @@ -99,14 +105,62 @@ private StackFrame processFrame(final StackTraceElement stackTraceElement, } else { varIds = Collections.emptyList(); } + final FileNameMapping mapping = getFileNameMapping(stackTraceElement, className); - return new StackFrame(fileName, - lineNumber, + return new StackFrame(mapping.fileName, + mapping.lineNumber, className, methodName, appFrame, nativeMethod, - varIds); + varIds, + mapping.transpiledFile, + mapping.transpiledLine); + } + + private FileNameMapping getFileNameMapping(final StackTraceElement stackTraceElement, final String className) { + + if (!JSPUtils.isJspClass(jspSuffix, jspPackages, className)) { + return new FileNameMapping(getFileName(stackTraceElement), stackTraceElement.getLineNumber(), null, -1); + } + + Class forName = null; + try { + forName = Class.forName(className); + } catch (ClassNotFoundException ignored) { + // not possible + } + + if (forName == null) { + return new FileNameMapping(getFileName(stackTraceElement), stackTraceElement.getLineNumber(), null, -1); + } + + final SourceMap sourceMap = JSPUtils.getSourceMap(forName); + if (sourceMap == null) { + return new FileNameMapping(getFileName(stackTraceElement), stackTraceElement.getLineNumber(), null, -1); + } + + final SourceMapLookup lookup = sourceMap.lookup(stackTraceElement.getLineNumber()); + final String jspFilename = lookup.getFilename(); + final int jspLine = lookup.getLineNumber(); + final String transpiledFile = getFileName(stackTraceElement); + final int transpiledLine = stackTraceElement.getLineNumber(); + return new FileNameMapping(jspFilename, jspLine, transpiledFile, transpiledLine); + } + + private static class FileNameMapping { + + public final String fileName; + public final int lineNumber; + private final String transpiledFile; + private final int transpiledLine; + + public FileNameMapping(final String fileName, final int lineNumber, final String transpiledFile, final int transpiledLine) { + this.fileName = fileName; + this.lineNumber = lineNumber; + this.transpiledFile = transpiledFile; + this.transpiledLine = transpiledLine; + } } protected Map selectVariables(final int frameIndex) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java index a9f5a1a..093df60 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java @@ -223,7 +223,8 @@ protected CFClassScanner reTransFormCfClasses( return new CFClassScanner(allTracepoints); } - private URL getLocation(final ProtectionDomain protectionDomain) { + //exposed for tests + protected URL getLocation(final ProtectionDomain protectionDomain) { return protectionDomain.getCodeSource().getLocation(); } diff --git a/agent/src/main/java/com/intergral/deep/agent/types/snapshot/StackFrame.java b/agent/src/main/java/com/intergral/deep/agent/types/snapshot/StackFrame.java index 7549f96..973e866 100644 --- a/agent/src/main/java/com/intergral/deep/agent/types/snapshot/StackFrame.java +++ b/agent/src/main/java/com/intergral/deep/agent/types/snapshot/StackFrame.java @@ -28,10 +28,12 @@ public class StackFrame { private final boolean appFrame; private final boolean nativeFrame; private final Collection frameVariables; + private final String transpiledFile; + private final int transpiledLine; public StackFrame(final String fileName, final int lineNumber, final String className, final String methodName, final boolean appFrame, final boolean nativeFrame, - final Collection frameVariables) { + final Collection frameVariables, final String transpiledFile, final int transpiledLine) { this.className = className; this.fileName = fileName; @@ -40,6 +42,8 @@ public StackFrame(final String fileName, final int lineNumber, final String clas this.appFrame = appFrame; this.nativeFrame = nativeFrame; this.frameVariables = frameVariables; + this.transpiledFile = transpiledFile; + this.transpiledLine = transpiledLine; } public String getClassName() { @@ -69,4 +73,12 @@ public boolean isNativeFrame() { public Collection getFrameVariables() { return frameVariables; } + + public String getTranspiledFile() { + return transpiledFile; + } + + public int getTranspiledLine() { + return transpiledLine; + } } diff --git a/agent/src/test/java/coldfusion/runtime/AttributeCollection.java b/agent/src/test/java/coldfusion/runtime/AttributeCollection.java new file mode 100644 index 0000000..057a6d7 --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/AttributeCollection.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +public class AttributeCollection { + + public AttributeCollection(Object[] objects) { + } +} diff --git a/agent/src/test/java/coldfusion/runtime/CFPage.java b/agent/src/test/java/coldfusion/runtime/CFPage.java index 89b58cc..e5f84a2 100644 --- a/agent/src/test/java/coldfusion/runtime/CFPage.java +++ b/agent/src/test/java/coldfusion/runtime/CFPage.java @@ -18,6 +18,9 @@ package coldfusion.runtime; @SuppressWarnings("ALL") -public class CFPage +public abstract class CFPage extends CfJspPage { + + @Override + protected abstract Object runPage() ; } diff --git a/agent/src/test/java/coldfusion/runtime/CfJspPage.java b/agent/src/test/java/coldfusion/runtime/CfJspPage.java new file mode 100644 index 0000000..a46a2b0 --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/CfJspPage.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +import coldfusion.tagext.io.OutputTag; +import java.io.IOException; +import javax.servlet.jsp.tagext.Tag; + +public abstract class CfJspPage { + public Tag parent; + public NeoPageContext pageContext = new NeoPageContext(); + protected void bindPageVariables(VariableScope varscope, LocalScope locscope) { + + } + + protected Variable bindPageVariable(String varName, VariableScope varScope, LocalScope locScope) { + return null; + } + protected abstract Object runPage(); + + public Tag _initTag(Class clazz, int slot, Tag parent) throws IOException { + return new OutputTag(); + } + + public void _setCurrentLineNo(int lineNo) {} +} diff --git a/agent/src/test/java/coldfusion/runtime/LocalScope.java b/agent/src/test/java/coldfusion/runtime/LocalScope.java new file mode 100644 index 0000000..f70cfe6 --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/LocalScope.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +public class LocalScope { + +} diff --git a/agent/src/test/java/coldfusion/runtime/NeoPageContext.java b/agent/src/test/java/coldfusion/runtime/NeoPageContext.java new file mode 100644 index 0000000..129f941 --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/NeoPageContext.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +import javax.servlet.jsp.JspWriter; +import org.apache.jasper.runtime.JspWriterImpl; +import org.mockito.Mockito; + +public class NeoPageContext { + + JspWriter getOut() { + return new JspWriterImpl(); + } + + void setPageEncoding(String encoding){} +} diff --git a/agent/src/test/java/coldfusion/runtime/Variable.java b/agent/src/test/java/coldfusion/runtime/Variable.java new file mode 100644 index 0000000..0f24a0a --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/Variable.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +public class Variable { + +} diff --git a/agent/src/test/java/coldfusion/runtime/VariableScope.java b/agent/src/test/java/coldfusion/runtime/VariableScope.java new file mode 100644 index 0000000..0f4c8d5 --- /dev/null +++ b/agent/src/test/java/coldfusion/runtime/VariableScope.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.runtime; + +public class VariableScope { + +} diff --git a/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java b/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java new file mode 100644 index 0000000..96a9f02 --- /dev/null +++ b/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.tagext.io; + +public class DirectoryTag { + +} + diff --git a/agent/src/test/java/coldfusion/tagext/io/FileTag.java b/agent/src/test/java/coldfusion/tagext/io/FileTag.java new file mode 100644 index 0000000..ffe3af1 --- /dev/null +++ b/agent/src/test/java/coldfusion/tagext/io/FileTag.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.tagext.io; + +public class FileTag { + +} diff --git a/agent/src/test/java/coldfusion/tagext/io/OutputTag.java b/agent/src/test/java/coldfusion/tagext/io/OutputTag.java new file mode 100644 index 0000000..ddb6e1d --- /dev/null +++ b/agent/src/test/java/coldfusion/tagext/io/OutputTag.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.tagext.io; + +import javax.servlet.jsp.tagext.TagSupport; + +public class OutputTag extends TagSupport { + +} diff --git a/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java b/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java new file mode 100644 index 0000000..2af7009 --- /dev/null +++ b/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.tagext.lang; + +public class LoopTag { + +} diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java index 0dc8e83..56fcc02 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java @@ -18,8 +18,11 @@ package com.intergral.deep.agent.tracepoint.inst.asm; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.intergral.deep.agent.api.resource.Resource; @@ -42,13 +45,26 @@ import com.intergral.deep.tests.snapshot.SnapshotUtils.IVariableScan; import java.lang.instrument.Instrumentation; import java.lang.reflect.Constructor; -import java.net.URISyntaxException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; import java.nio.file.Paths; +import java.security.ProtectionDomain; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; +import javax.servlet.DispatcherType; +import javax.servlet.Servlet; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.jsp.JspFactory; +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.PageContext; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -70,7 +86,8 @@ class VisitorTest { private final Instrumentation instrumentation = Mockito.mock(Instrumentation.class); private final String disPath = Paths.get(Paths.get(".").normalize().toAbsolutePath().getParent().toString(), "dispath").toString(); - private final AtomicReference tracepointRef = new AtomicReference<>(); + private final AtomicReference> tracepointRef = new AtomicReference<>(); + private final AtomicReference cfUrl = new AtomicReference<>(); private TracepointInstrumentationService instrumentationService; @BeforeEach @@ -87,18 +104,28 @@ void setUp() { Mockito.when(instrumentation.isModifiableClass(Mockito.any())).thenReturn(true); // these settings are needed for jsp transforms + Mockito.when(settings.getAsList("jsp.packages")).thenReturn(Arrays.asList("org.apache.jsp", "jsp")); Mockito.when(settings.getSettingAs("jsp.packages", List.class)).thenReturn(Arrays.asList("org.apache.jsp", "jsp")); Mockito.when(settings.getSettingAs("jsp.suffix", String.class)).thenReturn("_jsp"); Mockito.when(tracepointConfigService.loadTracepointConfigs(Mockito.any())).thenAnswer(invocationOnMock -> { - final TracePointConfig tracePointConfig = tracepointRef.get(); + final Collection tracePointConfig = tracepointRef.get(); if (tracePointConfig == null) { return Collections.emptyList(); } - return Collections.singletonList(tracePointConfig); + return tracePointConfig; }); - instrumentationService = new TracepointInstrumentationService(instrumentation, settings); + instrumentationService = new TracepointInstrumentationService(instrumentation, settings) { + @Override + protected URL getLocation(final ProtectionDomain protectionDomain) { + final URL url = cfUrl.get(); + if (url == null) { + return super.getLocation(protectionDomain); + } + return url; + } + }; Callback.init(settings, tracepointConfigService, pushService); } @@ -109,7 +136,7 @@ void constructor() throws Exception { final MockTracepointConfig tracepointConfig = new MockTracepointConfig( "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 21); - tracepointRef.set(tracepointConfig); + tracepointRef.set(Collections.singletonList(tracepointConfig)); final String name = "com/intergral/deep/test/target/BPTestTarget"; final ByteClassLoader classLoader = ByteClassLoader.forFile(name); @@ -148,6 +175,8 @@ void constructor() throws Exception { assertTrue(scanResult.found()); assertEquals("my test", scanResult.variable().getValue()); + + assertEquals("", snapshot.getFrames(0).getMethodName()); } // test the last line of the constructor closing brace @@ -156,7 +185,7 @@ void constructor_end_line() throws Exception { final MockTracepointConfig tracepointConfig = new MockTracepointConfig( "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 23); - tracepointRef.set(tracepointConfig); + tracepointRef.set(Collections.singletonList(tracepointConfig)); final String name = "com/intergral/deep/test/target/BPTestTarget"; final ByteClassLoader classLoader = ByteClassLoader.forFile(name); @@ -211,6 +240,8 @@ void constructor_end_line() throws Exception { final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), snapshot.getVarLookupMap()); assertTrue(superName.found()); assertEquals("i am a namemy test", superName.variable().getValue()); + + assertEquals("", snapshot.getFrames(0).getMethodName()); } // test first line in constructor (super call) @@ -219,7 +250,7 @@ void constructor_start_line() throws Exception { final MockTracepointConfig tracepointConfig = new MockTracepointConfig( "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 20); - tracepointRef.set(tracepointConfig); + tracepointRef.set(Collections.singletonList(tracepointConfig)); final String name = "com/intergral/deep/test/target/BPTestTarget"; final ByteClassLoader classLoader = ByteClassLoader.forFile(name); @@ -274,5 +305,1075 @@ void constructor_start_line() throws Exception { final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), snapshot.getVarLookupMap()); assertTrue(superName.found()); assertEquals("i am a namemy test", superName.variable().getValue()); + + assertEquals("", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void setName() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 51); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("my test", 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("setName", String.class); + setName.invoke(myTest, "something"); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // the parameter to the constructor should be available + assertTrue(scanResult.found()); + assertEquals("something", scanResult.variable().getValue()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("my test", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namemy test", superName.variable().getValue()); + + assertEquals("setName", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void getName_null_return() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 44); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance(null, 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("getName"); + setName.invoke(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("null", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namenull", superName.variable().getValue()); + + assertEquals("getName", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void getName_non_null_return() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 46); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("getName"); + setName.invoke(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("getName", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void errorSomething() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 75); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("errorSomething", String.class); + final InvocationTargetException invocationTargetException = assertThrows(InvocationTargetException.class, + () -> setName.invoke(myTest, (Object) null)); + assertNull(invocationTargetException.getTargetException().getMessage()); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("errorSomething", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void throwSomething() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 87); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("throwSomething", String.class); + final InvocationTargetException invocationTargetException = assertThrows(InvocationTargetException.class, + () -> setName.invoke(myTest, (Object) null)); + assertNull(invocationTargetException.getTargetException().getMessage()); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("throwSomething", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void catchSomething() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 112); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("catchSomething", String.class); + setName.invoke(myTest, (Object) null); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("catchSomething", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void finallySomething() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 146); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method setName = aClass.getDeclaredMethod("finallySomething", String.class); + setName.invoke(myTest, (Object) null); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("finallySomething", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void conditionalThrow() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 163); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method method = aClass.getDeclaredMethod("conditionalThrow", int.class, int.class); + method.invoke(myTest, 1, 2); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.never()) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + assertThrows(InvocationTargetException.class, () -> method.invoke(myTest, 3, 2)); + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("conditionalThrow", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void breakSomething() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 174); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method method = aClass.getDeclaredMethod("breakSomething"); + method.invoke(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("breakSomething", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void continueSomething() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 187); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final Method method = aClass.getDeclaredMethod("continueSomething"); + method.invoke(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("some name", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namesome name", superName.variable().getValue()); + + assertEquals("continueSomething", snapshot.getFrames(0).getMethodName()); + } + + + @Test + void superConstructor() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java", 10); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String superName = "com/intergral/deep/test/target/BPSuperClass"; + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(superName); + classLoader.setBytes(name, ByteClassLoader.loadBytes(name)); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(superName); + final byte[] transformed = instrumentationService.transform(null, superName, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, + superName + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + // modify the super class with the tracepoint + final String clazzName = InstUtils.externalClassName(superName); + classLoader.setBytes(clazzName, transformed); + classLoader.setBytes(InstUtils.externalClassName(name), ByteClassLoader.loadBytes(name)); + + // now load the subclass + final Class superClass = classLoader.loadClass(clazzName); + final Class aClass = classLoader.loadClass(InstUtils.externalClassName(name)); + assertNotNull(aClass); + assertEquals(aClass.getSuperclass(), superClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance("some name", 4); + assertNotNull(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertTrue(scanResult.found()); + assertEquals("i am a namesome name", scanResult.variable().getValue()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be null + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("null", thisName.variable().getValue()); + + assertEquals("", snapshot.getFrames(0).getMethodName()); + } + + @Test + void multipleTps_oneLine() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 44); + final MockTracepointConfig tracepointConfig2 = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 44); + tracepointRef.set(Arrays.asList(tracepointConfig, tracepointConfig2)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance(null, 4); + assertNotNull(myTest); + + final Method method = aClass.getDeclaredMethod("getName"); + method.invoke(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(2)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("null", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namenull", superName.variable().getValue()); + + assertEquals("getName", snapshot.getFrames(0).getMethodName()); + } + + @Test + void multipleTps_nextLine() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 42); + final MockTracepointConfig tracepointConfig2 = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 44); + tracepointRef.set(Arrays.asList(tracepointConfig, tracepointConfig2)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance(null, 4); + assertNotNull(myTest); + + final Method method = aClass.getDeclaredMethod("getName"); + method.invoke(myTest); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(2)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("null", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namenull", superName.variable().getValue()); + + assertEquals("getName", snapshot.getFrames(0).getMethodName()); + } + + @Test + void someFunctionWithABody() throws Exception { + + final MockTracepointConfig tracepointConfig = new MockTracepointConfig( + "/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java", 151); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + + final String name = "com/intergral/deep/test/target/BPTestTarget"; + final ByteClassLoader classLoader = ByteClassLoader.forFile(name); + + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final byte[] originalBytes = classLoader.getBytes(name); + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String clazzName = InstUtils.externalClassName(name); + classLoader.setBytes(clazzName, transformed); + + final Class aClass = classLoader.loadClass(clazzName); + assertNotNull(aClass); + + final Constructor constructor = aClass.getConstructor(String.class, int.class); + final Object myTest = constructor.newInstance(null, 4); + assertNotNull(myTest); + + final Method method = aClass.getDeclaredMethod("someFunctionWithABody", String.class); + method.invoke(myTest, "some string"); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertEquals(tracepointConfig.getId(), value.getTracepoint().getId()); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + final IVariableScan scanResult = SnapshotUtils.findVarByName("name", snapshot); + + // there should not be a local var called name + assertFalse(scanResult.found()); + + // we should also find the 'this' object which should be the BPTargetClass + final IVariableScan thisScan = SnapshotUtils.findVarByName("this", snapshot); + assertTrue(thisScan.found()); + assertEquals(myTest.getClass().getName(), thisScan.variable().getType()); + + // on the 'this' object we should find the field 'name' which should be the value we set in the constructor + final Variable variable = thisScan.variable(); + final IVariableScan thisName = SnapshotUtils.findVarByName("name", variable.getChildrenList(), snapshot.getVarLookupMap()); + assertTrue(thisName.found()); + assertEquals("null", thisName.variable().getValue()); + + // there should however be a value for the 'super.name' field that is set to the known value + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); + assertTrue(superName.found()); + assertEquals("i am a namenull", superName.variable().getValue()); + + assertEquals("someFunctionWithABody", snapshot.getFrames(0).getMethodName()); + } + + // cannot seem to load cf class files + // consistent issues with verifier +// @Test +// void cfVisitor() throws Exception { +// final MockTracepointConfig tracepointConfig = new MockTracepointConfig("/src/main/cfml/testFile.cfm", 3); +// tracepointRef.set(Collections.singletonList(tracepointConfig)); +// // we need to process the cfm tracepoints +// instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); +// +// final String name = "cftestFile2ecfm137384933"; +// final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(name); +// final byte[] originalBytes = byteClassLoader.getBytes(name); +// +// // for adobe cf we need a location url to be set +// cfUrl.set(new URL("file:///src/main/cfml/testFile.cfm")); +// +// final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); +// // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other +// TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); +// +// assertNotNull(transformed, "Failed to transform the test class!"); +// assertNotEquals(originalBytes.length, transformed.length); +// +// byteClassLoader.setBytes("coldfusion.runtime.CfJspPage", ByteClassLoader.loadBytes("coldfusion/runtime/CfJspPage")); +// byteClassLoader.setBytes("coldfusion.runtime.CFPage", ByteClassLoader.loadBytes("coldfusion/runtime/CFPage")); +// byteClassLoader.setBytes(name, transformed); +// byteClassLoader.loadClass("java.lang.Object"); +// final Class jspPageClass = byteClassLoader.loadClass("coldfusion.runtime.CfJspPage"); +// assertNotNull(jspPageClass); +// final Class pageClass = byteClassLoader.loadClass("coldfusion.runtime.CFPage"); +// assertNotNull(pageClass); +// final Class aClass = byteClassLoader.loadClass(name); +// +// final Constructor constructor = aClass.getConstructor(); +// final Object instance = constructor.newInstance(); +// final Method runPage = aClass.getDeclaredMethod("runPage"); +// runPage.setAccessible(true); +// runPage.invoke(instance); +// +// final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); +// +// Mockito.verify(pushService, Mockito.times(1)) +// .pushSnapshot(argumentCaptor.capture(), Mockito.any()); +// +// } + + + @Test + void jspVisitorTest() throws Exception { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig("/src/main/webapp/tests/string.jsp", 9); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + // we need to process the jsp tracepoints + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final String name = "org/apache/jsp/tests/string_jsp"; + final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(name); + final byte[] originalBytes = byteClassLoader.getBytes(name); + + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + final String className = InstUtils.externalClassName(name); + byteClassLoader.setBytes(className, transformed); + final Class aClass = byteClassLoader.loadClass(className); + + final JspFactory jspFactory = Mockito.mock(JspFactory.class); + final PageContext pageContext = Mockito.mock(PageContext.class); + final JspWriter jspWriter = Mockito.mock(JspWriter.class); + Mockito.when(pageContext.getOut()).thenReturn(jspWriter); + + JspFactory.setDefaultFactory(jspFactory); + Mockito.when(jspFactory.getPageContext(Mockito.any(Servlet.class), Mockito.any(ServletRequest.class), Mockito.any( + ServletResponse.class), Mockito.eq(null), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyBoolean())) + .thenReturn(pageContext); + + final Constructor constructor = aClass.getConstructor(); + final Object instance = constructor.newInstance(); + + final HttpServletRequest servletRequest = Mockito.mock(HttpServletRequest.class); + Mockito.when(servletRequest.getDispatcherType()).thenReturn(DispatcherType.ERROR); + final HttpServletResponse servletResponse = Mockito.mock(HttpServletResponse.class); + + final Method jspService = aClass.getDeclaredMethod("_jspService", HttpServletRequest.class, HttpServletResponse.class); + jspService.invoke(instance, servletRequest, servletResponse); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + + final Snapshot snapshot = PushUtils.convertToGrpc(value); + + assertEquals("_jspService", snapshot.getFrames(0).getMethodName()); + assertEquals("org.apache.jsp.tests.string_jsp", snapshot.getFrames(0).getClassName()); + assertEquals("string.jsp", snapshot.getFrames(0).getFileName()); + assertEquals(9, snapshot.getFrames(0).getLineNumber()); + assertEquals("string_jsp.java", snapshot.getFrames(0).getTranspiledFileName()); + assertEquals(127, snapshot.getFrames(0).getTranspiledLineNumber()); } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java b/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java index f1ee678..139a219 100644 --- a/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java +++ b/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java @@ -36,9 +36,12 @@ public MockEventSnapshot() { } public MockEventSnapshot withFrames() { - this.getFrames().add(new StackFrame("file_name_1", 4321, "class_name_1", "methodName_1", true, true, new ArrayList<>())); - this.getFrames().add(new StackFrame("file_name_2", 321, "class_name_2", "methodName_2", false, true, new ArrayList<>())); - this.getFrames().add(new StackFrame("file_name_3", 21, "class_name_3", "methodName_3", true, false, new ArrayList<>())); + this.getFrames().add(new StackFrame("file_name_1", 4321, "class_name_1", "methodName_1", true, true, new ArrayList<>(), + null, -1)); + this.getFrames().add(new StackFrame("file_name_2", 321, "class_name_2", "methodName_2", false, true, new ArrayList<>(), + null, -1)); + this.getFrames().add(new StackFrame("file_name_3", 21, "class_name_3", "methodName_3", true, false, new ArrayList<>(), + null, -1)); final VariableID someLocalVar = new VariableID("var-id-1", "someLocalVar", new HashSet<>(), null); final HashSet modifiers = new HashSet<>(); @@ -49,7 +52,8 @@ public MockEventSnapshot withFrames() { frameVariables.add(someLocalVar); frameVariables.add(someLocalVar2); frameVariables.add(someLocalVar3); - this.getFrames().add(new StackFrame("file_name_4", 1, "class_name_4", "methodName_4", false, false, frameVariables)); + this.getFrames().add(new StackFrame("file_name_4", 1, "class_name_4", "methodName_4", false, false, frameVariables, + null, -1)); return this; } diff --git a/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java b/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java index 7a9eaa2..fcbd756 100644 --- a/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java +++ b/agent/src/test/java/com/intergral/deep/test/target/BPTestTarget.java @@ -10,7 +10,7 @@ * plugin to see the code. */ // @formatter:off -@SuppressWarnings({"unused", "ThrowFromFinallyBlock"}) public class BPTestTarget extends BPSuperClass +@SuppressWarnings("ALL") public class BPTestTarget extends BPSuperClass { private String name; @@ -156,7 +156,7 @@ public String someFunctionWithABody(final String someArg){ } - public void checkEnd( final int val, final int max ) throws Exception + public void conditionalThrow( final int val, final int max ) throws Exception { if( val > max ) { From 190bca65fa7bd350d0f79ad6852839bb69549833 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 25 Aug 2023 15:54:24 +0100 Subject: [PATCH 08/15] chore(tests): add more tests for visitor --- .../resources/cftestFile2ecfm137384933.class | Bin 0 -> 3022 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 agent/src/test/resources/cftestFile2ecfm137384933.class diff --git a/agent/src/test/resources/cftestFile2ecfm137384933.class b/agent/src/test/resources/cftestFile2ecfm137384933.class new file mode 100644 index 0000000000000000000000000000000000000000..4b1fde84d4b35e1067697c40253035d2fbaf740e GIT binary patch literal 3022 zcma)8XB{%L5o3&id6)=U;qmR#0Ir!TRJ2Y7@B0#nF*j>EM2VK z_eJSqw@3QTY8!3)bot!h(%;bby)%IXO?~=d=G^6+bIj+`-*>RBO+mRpFlHp< ziL9j?>9Cni+j>e1M|y|V2~9zTK;yO0Eiy!7Mn>x_#W7&S)TFyD4_N`fz+ELYMH2m1#x-q~538|KK=cXJm(sa- zYNr>~0&8xyR={i?m{O6VOTd7|^lh>@E$%k!d zq}q&XYH6z7R6?muTW0O>h&n;7ccWRFvO}PvD;nwR^P)vywWZn8;+}NOh%*BMYfGd> z7R4^K3T!D+6w7^9u-k_{2(w_RTENrXC#Bo4k8M&ydNI2S_WN)E_Xsq_lB#7j6?mIm zH=1;#i2=-HZTiFdFRGr1l;~s(3PP=}P^)gVE(v)-9wlLzLu!g$yRDR`w`uj`Uap`z9-6PA~4Ch&N zBfR9M0`_F}7{edQnzA?s^t3i)`0)_-Neu%6RgH zQ;(5V!7(2m#xTJzZbWU>bX_Gd_4zS^<39A`gutpXHESCd5rx2XmT}&5M^x%`9d}{i z==hWtvstWMZ|MzTM}JjIP(J*_5_Jlc}b-w*bb z@Od#!%f@CV>423{V@1`xoxZ1OHLZC&I1yuiBV z)3;k7tyOTrhdI1TZ;79SG&t@UT$=~cWo1LOu_txjgBu3^>&hhn1f1J$5qCyp)F~J- z;w0W_)wJX_DX_H^zAs9-cMR3kVrA}my-TbmG#c_Es4oEsg=>KIIjttM zn)h-LToccsJ#-D-<-2lt@DKEN zctVvq36=hV zg2oOd=m{!0m*ll3nX@WFH{dH| za9IKHoNtx{25E{cm~i0GO&q?C?q4uBs{D!nANpwcpI{1+01DdYeE literal 0 HcmV?d00001 From e90f79bb1cc6136dacafbf39e78f3d5cd847cf43 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 31 Aug 2023 13:22:50 +0100 Subject: [PATCH 09/15] chore(tests): add more tests for data collection --- .idea/kubernetes-settings.xml | 6 + .../com/intergral/deep/agent/DeepAgent.java | 2 +- .../intergral/deep/agent/push/PushUtils.java | 4 +- .../tracepoint/handler/FrameCollector.java | 21 +- .../tracepoint/handler/FrameProcessor.java | 5 +- .../tracepoint/handler/VariableProcessor.java | 196 ++++++++++++++-- .../agent/tracepoint/handler/bfs/Node.java | 7 + .../agent/tracepoint/inst/asm/Visitor.java | 36 ++- .../test/java/coldfusion/package-info.java | 21 ++ .../runtime/AttributeCollection.java | 1 + .../java/coldfusion/runtime/CfJspPage.java | 6 + .../java/coldfusion/runtime/LocalScope.java | 1 + .../coldfusion/runtime/NeoPageContext.java | 7 +- .../java/coldfusion/runtime/Variable.java | 4 + .../coldfusion/runtime/VariableScope.java | 1 + .../java/coldfusion/runtime/package-info.java | 4 - .../java/coldfusion/tagext/GenericTag.java | 28 +++ .../coldfusion/tagext/io/DirectoryTag.java | 1 + .../java/coldfusion/tagext/io/FileTag.java | 1 + .../java/coldfusion/tagext/io/OutputTag.java | 17 +- .../java/coldfusion/tagext/lang/LoopTag.java | 1 + .../intergral/deep/agent/DeepAgentTest.java | 85 +++++++ .../handler/VariableProcessorTest.java | 222 ++++++++++++++++++ .../tracepoint/inst/asm/VisitorTest.java | 122 +++++++--- .../test/target/VariableSuperSuperTest.java | 23 ++ .../deep/test/target/VariableSuperTest.java | 27 +++ .../deep/test/target/VariableTypeTarget.java | 67 ++++++ .../lucee/commons/color/ConstantsDouble.java | 23 ++ agent/src/test/java/lucee/package-info.java | 4 + .../test/java/lucee/runtime/PageContext.java | 73 ++++++ .../java/lucee/runtime/PageContextImpl.java | 27 +++ .../src/test/java/lucee/runtime/PageImpl.java | 32 +++ .../test/java/lucee/runtime/PageSource.java | 23 ++ .../runtime/component/ImportDefintion.java | 23 ++ .../java/lucee/runtime/exp/PageException.java | 23 ++ .../interpreter/VariableInterpreter.java | 28 +++ .../test/java/lucee/runtime/op/Caster.java | 32 +++ .../test/java/lucee/runtime/op/Operator.java | 30 +++ .../lucee/runtime/scope/MockLuceeScope.java | 25 ++ .../java/lucee/runtime/type/Collection.java | 25 ++ .../src/test/java/lucee/runtime/type/UDF.java | 23 ++ .../lucee/runtime/type/UDFProperties.java | 23 ++ .../runtime/type/ref/VariableReference.java | 24 ++ .../lucee/runtime/type/scope/Undefined.java | 28 +++ .../lucee/runtime/type/util/KeyConstants.java | 26 ++ .../src/test/resources/testfile_cfm$cf.class | Bin 0 -> 2393 bytes examples/docker/lucee/Dockerfile | 3 + examples/docker/lucee/testFile.cfm | 3 + .../deep/reflect/ReflectionImpl.java | 5 + .../deep/tests/snapshot/SnapshotUtils.java | 4 +- 50 files changed, 1324 insertions(+), 99 deletions(-) create mode 100644 .idea/kubernetes-settings.xml create mode 100644 agent/src/test/java/coldfusion/package-info.java delete mode 100644 agent/src/test/java/coldfusion/runtime/package-info.java create mode 100644 agent/src/test/java/coldfusion/tagext/GenericTag.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessorTest.java create mode 100644 agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java create mode 100644 agent/src/test/java/com/intergral/deep/test/target/VariableSuperTest.java create mode 100644 agent/src/test/java/com/intergral/deep/test/target/VariableTypeTarget.java create mode 100644 agent/src/test/java/lucee/commons/color/ConstantsDouble.java create mode 100644 agent/src/test/java/lucee/package-info.java create mode 100644 agent/src/test/java/lucee/runtime/PageContext.java create mode 100644 agent/src/test/java/lucee/runtime/PageContextImpl.java create mode 100644 agent/src/test/java/lucee/runtime/PageImpl.java create mode 100644 agent/src/test/java/lucee/runtime/PageSource.java create mode 100644 agent/src/test/java/lucee/runtime/component/ImportDefintion.java create mode 100644 agent/src/test/java/lucee/runtime/exp/PageException.java create mode 100644 agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java create mode 100644 agent/src/test/java/lucee/runtime/op/Caster.java create mode 100644 agent/src/test/java/lucee/runtime/op/Operator.java create mode 100644 agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java create mode 100644 agent/src/test/java/lucee/runtime/type/Collection.java create mode 100644 agent/src/test/java/lucee/runtime/type/UDF.java create mode 100644 agent/src/test/java/lucee/runtime/type/UDFProperties.java create mode 100644 agent/src/test/java/lucee/runtime/type/ref/VariableReference.java create mode 100644 agent/src/test/java/lucee/runtime/type/scope/Undefined.java create mode 100644 agent/src/test/java/lucee/runtime/type/util/KeyConstants.java create mode 100644 agent/src/test/resources/testfile_cfm$cf.class create mode 100644 examples/docker/lucee/Dockerfile create mode 100644 examples/docker/lucee/testFile.cfm diff --git a/.idea/kubernetes-settings.xml b/.idea/kubernetes-settings.xml new file mode 100644 index 0000000..b8b3279 --- /dev/null +++ b/.idea/kubernetes-settings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java index 8a26099..7483914 100644 --- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java +++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java @@ -151,7 +151,7 @@ public String id() { @Override public boolean isEnabled() { - return this.settings.getSettingAs(ISettings.KEY_ENABLED, Boolean.class); + return this.settings.isActive(); } @Override diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java index b18ca29..1cfa6c0 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java @@ -135,12 +135,12 @@ private static Iterable convertFrames( }).collect(Collectors.toList()); } - private static Iterable covertVariables( + public static Collection covertVariables( final Collection frameVariables) { return frameVariables.stream().map(PushUtils::convertVariableID).collect(Collectors.toList()); } - private static Map convertVarLookup( + public static Map convertVarLookup( final Map varLookup) { return varLookup.entrySet() .stream() diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java index b6b3768..976d18a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java @@ -17,6 +17,7 @@ package com.intergral.deep.agent.tracepoint.handler; +import com.intergral.deep.agent.Utils; import com.intergral.deep.agent.api.plugin.IEvaluator; import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.settings.Settings; @@ -47,7 +48,6 @@ public class FrameCollector extends VariableProcessor { protected final Map variables; private final StackTraceElement[] stack; - private final Map varCache = new HashMap<>(); private final String jspSuffix; private final List jspPackages; @@ -214,23 +214,6 @@ protected boolean processNode(final Node node) { return true; } - private boolean checkVarCount() { - return varCache.size() <= this.frameConfig.maxVariables(); - } - - @Override - protected String checkId(final String identity) { - return this.varCache.get(identity); - } - - @Override - protected String newVarId(final String identity) { - final int size = this.varCache.size(); - final String newId = String.valueOf(size + 1); - this.varCache.put(identity, newId); - return newId; - } - protected boolean isAppFrame(final StackTraceElement stackTraceElement) { final List inAppInclude = settings.getAsList("in.app.include"); final List inAppExclude = settings.getAsList("in.app.exclude"); @@ -255,7 +238,7 @@ private String getFileName(final StackTraceElement stackTraceElement) { if (stackTraceElement.getFileName() == null) { return InstUtils.shortClassName(stackTraceElement.getClassName()); } - return stackTraceElement.getFileName(); + return Utils.trimPrefix(stackTraceElement.getFileName(), "/"); } protected String getMethodName(final StackTraceElement stackTraceElement, diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java index ff54248..1fd19ca 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java @@ -66,10 +66,7 @@ private boolean conditionPasses(final TracePointConfig tracePointConfig) { } public void configureSelf() { - for (TracePointConfig tracePointConfig : this.filteredTracepoints) { - this.frameConfig.process(tracePointConfig); - } - this.frameConfig.close(); + configureSelf(this.filteredTracepoints); } public Collection collect() { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java index bdee14c..3c9e1d1 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java @@ -22,8 +22,12 @@ import com.intergral.deep.agent.api.utils.ArrayObjectIterator; import com.intergral.deep.agent.api.utils.CompoundIterator; import com.intergral.deep.agent.tracepoint.handler.bfs.Node; +import com.intergral.deep.agent.tracepoint.handler.bfs.Node.IParent; +import com.intergral.deep.agent.tracepoint.inst.InstUtils; +import com.intergral.deep.agent.types.TracePointConfig; import com.intergral.deep.agent.types.snapshot.Variable; import com.intergral.deep.agent.types.snapshot.VariableID; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; @@ -33,8 +37,47 @@ import java.util.Map; import java.util.Set; +/** + * This type deals with processing the variables. Dealing with: + *

    + *
  • type definition - how the type of the variable is captured
  • + *
  • string values - how the string representation of the variable value is captured
  • + *
  • child variables - how many child variables are processed
  • + *
  • variable limits - total limits on how many variables are processed
  • + *
  • depth limits - how deep will process down the reference chain
  • + *
  • variable deduplication - ensuring we do not process the same variable multiple times
  • + *
  • variable referencing - using variable ids to reference already processed variables
  • + *
+ *

+ * While processing a variable or frame, we will process using a Breadth first approach. These means given the tree: + *

+ *   1 -> 1.1
+ *        1.2
+ *        1.3 -> 1.3.1
+ *   2 -> 2.1
+ *   3 -> 3.1 -> 3.1.1
+ * 
+ * We will attempt to gather the variables in the order: + *
    + *
  • 1
  • + *
  • 2
  • + *
  • 3
  • + *
  • 1.1
  • + *
  • 1.2
  • + *
  • 1.3
  • + *
  • 2.1
  • + *
  • 3.1
  • + *
  • 1.3.1
  • + *
  • 3.1.1
  • + *
+ * This ensures that we capture the variables closer to the tracepoint before we go deeper. + */ public abstract class VariableProcessor { + /** + * These are teh class names of types that we do not want to collect child variables from. These are mostly primitive or object types that + * the string value will provide the full value of, or where child values do not add value. + */ private static final Set> NO_CHILDREN_TYPES = new HashSet<>(); static { @@ -61,7 +104,17 @@ public abstract class VariableProcessor { } protected final FrameConfig frameConfig = new FrameConfig(); - private Map varLookup = new HashMap<>(); + /** + * This is the cache we use while building this lookup, this cache essentially maps {@link System#identityHashCode(Object)} to an internal + * id (monotonically incrementing id) used to deduplicate the variables. Essentially allows us to map the same object via different + * references, meaning if we are processing a type that references its self, or has multiple paths to the same object we will be process + * it again. + */ + private final Map varCache = new HashMap<>(); + /** + * This is the lookup that we are building from this processor. It will contain the deduplicated variables by reference. + */ + Map varLookup = new HashMap<>(); //todo i do not like this approach to getting an empty var look up for watches protected Map closeLookup() { @@ -70,6 +123,13 @@ protected Map closeLookup() { return lookup; } + public void configureSelf(final Iterable configs) { + for (TracePointConfig tracePointConfig : configs) { + this.frameConfig.process(tracePointConfig); + } + this.frameConfig.close(); + } + protected Set processChildNodes(final VariableID variableId, final Object value, final int depth) { // if the type is a type we do not want children from - return empty @@ -83,7 +143,18 @@ protected Set processChildNodes(final VariableID variableId, final Object return Collections.emptySet(); } - final Node.IParent parent = (node) -> this.appendChild(variableId.getId(), node); + final Node.IParent parent = new IParent() { + @Override + public void addChild(final VariableID child) { + VariableProcessor.this.appendChild(variableId.getId(), child); + } + + @Override + public boolean isCollection() { + return VariableProcessor.this.collectionType(value) || VariableProcessor.this.mapType(value) || VariableProcessor.this.arrayType( + value); + } + }; return findChildrenForParent(parent, value); } @@ -93,24 +164,36 @@ private Set findChildrenForParent(final Node.IParent parent, final Object return processNamedIterable(parent, new NamedIterable<>(new ArrayObjectIterator(value))); } if (collectionType(value)) { + //noinspection unchecked return processNamedIterable(parent, new NamedIterable<>(((Collection) value).iterator())); } if (mapType(value)) { + //noinspection unchecked return processNamedIterable(parent, new NamedMapIterator(((Map) value).entrySet().iterator())); } return processNamedIterable(parent, - new NamesFieldIterator(value, new FieldIterator(value.getClass()))); + new NamedFieldIterator(value, new FieldIterator(value.getClass()))); } + /** + * To allow for a common way to process variables from the multiple ways we can gather variables. We introduce the {@link NamedIterable}, + * which allows as to iterate maps, arrays and class fields. + *

+ * This method lets us convert the iterable of names items into {@link Node} for our Breadth First search algorithm. + * + * @param parent the parent node these values belong to + * @param iterable the iterable to process + * @return the new set of nodes to process + */ private Set processNamedIterable(final Node.IParent parent, - final NamedIterable objectNamedIterable) { + final NamedIterable iterable) { final HashSet nodes = new HashSet<>(); - while (objectNamedIterable.hasNext()) { - final NamedIterable.INamedItem next = objectNamedIterable.next(); + while (iterable.hasNext()) { + final NamedIterable.INamedItem next = iterable.next(); final Node node = new Node(new Node.NodeValue(next.name(), next.item(), @@ -118,7 +201,7 @@ private Set processNamedIterable(final Node.IParent parent, next.modifiers()), parent); nodes.add(node); - if (nodes.size() > this.frameConfig.maxCollectionSize()) { + if (parent.isCollection() && nodes.size() > this.frameConfig.maxCollectionSize()) { break; } } @@ -126,19 +209,50 @@ private Set processNamedIterable(final Node.IParent parent, return nodes; } + /** + * Is the passed value a type of map. + * + * @param value the object to check + * @return {@code true} if the object is a {@link Map} type, else {@code false} + */ private boolean mapType(final Object value) { return value instanceof Map; } + /** + * Is the object a type of array + * + * @param value the object to check + * @return {@code true} if the object is a form of array, else {@code false} + * @see Class#isArray() + */ private boolean arrayType(final Object value) { return value != null && value.getClass().isArray(); } + /** + * Is the object a type of collection, not including array + * + * @param value the object to check + * @return {@code true} if the object is a {@link Collection}, else {@code false}. + */ private boolean collectionType(final Object value) { return value instanceof Collection; } + /** + * Process the given node into a {@link VariableResponse}. + *

+ * If the provided value has already been processed, ie the {@link System#identityHashCode(Object)} of the object is already in the + * {@link #varCache} then we do not process the variable, and simply return a pointer to the reference. + *

+ * If the value has not already been process then we must gather the type, value, children etc. Then process the data into the + * {@link #varLookup}. + * + * @param value the node value to process + * @return the {@link VariableResponse} + */ protected VariableResponse processVariable(final Node.NodeValue value) { final Object objValue = value.getValue(); // get the variable hash id @@ -183,14 +297,26 @@ protected VariableResponse processVariable(final Node.NodeValue value) { return new VariableResponse(variableId, true); } + /** + * Create a string representation of the value. + *

+ * For most objects this is simply the result of {@link Object#toString()}, however for {@link Collection}, {@link Map}, {@link Iterator} + * and arrays, we create a simplified value as we collect the collection items as children. + * + * @param value the value to stringify + * @return a {@link String} that represents the value + */ private String valueToString(final Object value) { - if (value instanceof Collection) { - return String.format("Collection of size: %s", ((Collection) value).size()); + if (collectionType(value)) { + return String.format("%s of size: %s", InstUtils.shortClassName(value.getClass().getName()), ((Collection) value).size()); } else if (value instanceof Iterator) { - return String.format("Iterator of type: %s", value.getClass().getSimpleName()); - } else if (value instanceof Map) { - return String.format("Map of size: %s", ((Map) value).size()); + return String.format("Iterator of type: %s", InstUtils.shortClassName(value.getClass().getName())); + } else if (mapType(value)) { + return String.format("%s of size: %s", InstUtils.shortClassName(value.getClass().getName()), ((Map) value).size()); + } else if (arrayType(value)) { + return String.format("Array of length: %s", Array.getLength(value)); } + // we must use utils to create the string as it is possible for toString to fail return Utils.valueOf(value); } @@ -207,10 +333,24 @@ protected boolean checkDepth(final int depth) { return depth + 1 < this.frameConfig.maxDepth(); } - protected abstract String checkId(final String identity); + protected boolean checkVarCount() { + return varCache.size() <= this.frameConfig.maxVariables(); + } + + protected String checkId(final String identity) { + return this.varCache.get(identity); + } - protected abstract String newVarId(final String identity); + protected String newVarId(final String identity) { + final int size = this.varCache.size(); + final String newId = String.valueOf(size + 1); + this.varCache.put(identity, newId); + return newId; + } + /** + * This type is essentially a way to return the {@link VariableID} and to indicate if we need to process the children of this variable. + */ protected static class VariableResponse { private final VariableID variableId; @@ -230,6 +370,11 @@ public boolean processChildren() { } } + /** + * This is the basic named iterator that wraps iterator items with a name, e.g. the field name, collection index, or map key + * + * @param the type of value we are iterating + */ private static class NamedIterable implements Iterator { @@ -272,6 +417,9 @@ public Set modifiers() { }; } + /** + * The interface of an item named by the named iterator + */ public interface INamedItem { String name(); @@ -284,6 +432,9 @@ public interface INamedItem { } } + /** + * Use the named iterator to name map values with the map key + */ private static class NamedMapIterator extends NamedIterable> { public NamedMapIterator(final Iterator> iterator) { @@ -317,12 +468,15 @@ public Set modifiers() { } } - private static class NamesFieldIterator extends NamedIterable { + /** + * Use the named iterator to name the items with the field names. + */ + private static class NamedFieldIterator extends NamedIterable { private final Object target; private final HashSet seenNames = new HashSet<>(); - public NamesFieldIterator(final Object target, final Iterator iterator) { + public NamedFieldIterator(final Object target, final Iterator iterator) { super(iterator); this.target = target; } @@ -345,7 +499,11 @@ public Object item() { @Override public String originalName() { - return null; + // if we prefix the name with class name, then set the original name to the field name + if (name.equals(next.getName())) { + return null; + } + return next.getName(); } @Override @@ -372,6 +530,10 @@ private String getFieldName(final Field next) { } } + /** + * A simple iterator that used {@link ReflectionUtils} to create iterators for the {@link Field} that exist on an object. This allows us + * to feed the fields into the {@link NamedFieldIterator}. + */ private static class FieldIterator implements Iterator { private final Class clazz; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java index 11753ca..c12e072 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java @@ -85,6 +85,10 @@ public int depth() { public interface IParent { void addChild(final VariableID child); + + default boolean isCollection(){ + return false; + } } @@ -94,6 +98,9 @@ public interface IConsumer { } + /** + * This type wraps an Object that we are to process. They simply acts as a reference to the read data during the Breadth First search. + */ public static class NodeValue { private final String key; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java index a76392a..ff56405 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java @@ -388,7 +388,7 @@ public void visitEnd() { hook.add(new InsnNode(ATHROW)); // } (end catch) hook.add(new LabelNode( - startFinally)); // we start the finally before we end the catch for some reason + startFinally)); // we start the 'finally' before we end the catch for some reason // 'bad' finally { // store exception in next slot hook.add(new VarInsnNode(ASTORE, varOffset + 1)); @@ -408,7 +408,7 @@ public void visitEnd() { hook.add(new LdcInsnNode(bp.getId())); // bp id hook.add(new MethodInsnNode(INVOKEVIRTUAL, Type.getInternalName(HashSet.class), "add", "(Ljava/lang/Object;)Z")); - hook.add(new InsnNode(POP)); // dont care about return + hook.add(new InsnNode(POP)); // don't care about return } // if we are a next line then we need to remove the start line label from the seen labels so // the variable capture in the finally does not capture variables defined on the line we are wrapping @@ -447,17 +447,17 @@ public void visitEnd() { // we want to insert here - as the throw will be 'caught' by the catch/finally we are inserting instructions.insert(node, hook); } else { - // if we are a next line then we need to insert before the previous - // the previous on next lines will be the 'label' for the next line + // if we are a next line then we need to insert before the previous instruction + // the previous instruction on next lines will be the 'label' for the next line // methodVisitor.visitFieldInsn( PUTFIELD, "com/nerdvision/agent/BPTestTarget", "name", "Ljava/lang/String;" ); - this is the line we are wrapping // Label label1 = new Label(); // methodVisitor.visitLabel( label1 ); <- insert before this one - // methodVisitor.visitLineNumber( 32, label1 ); <- this it the node we are on + // methodVisitor.visitLineNumber( 32, label1 ); <- this is the node we are on // methodVisitor.visitInsn( RETURN ); <- this is the next line instructions.insertBefore(previous, hook); } - // remove the start so we know this line is done. + // remove the start, so we know this line is done. start = null; iBreakpoints.clear(); } @@ -481,6 +481,8 @@ public void visitEnd() { // first line will be hit as all subsequent will be disabled final List thisLineBps = lineNos.remove((long) ln.line); if (thisLineBps != null) { + // we track the breakpoints separate from the lineNos as we need to detect here what tracepoints should be installed, + // but we might need to use them in forth coming instructions for line end etc iBreakpoints.clear(); iBreakpoints.addAll(thisLineBps); } @@ -499,10 +501,18 @@ public void visitEnd() { if (!iBreakpoints.isEmpty()) { start = ln.start; LOGGER.trace("visitMethod {} {} line number {}", classname, name, ln.line); + // we insert the normal hook here, so we can capture the line data. + // the try/catch/finally that is added later can be attached regardless of when this hook is + // added as long as we have appropriate label instructions (which we add later) final InsnList hook = getAbstractInsnNodes(seenLabels, ln, iBreakpoints); changed = true; instructions.insert(ln, hook); + if (isCf) { + // if we are CF we do not support line capture, so we always clear the tracepoints + // for non-cf the tracepoints are cleared after the try/finally is added (on the next instruction) + iBreakpoints.clear(); + } LOGGER.debug("visitMethod {} {} patched @ {} {}", classname, name, ln.line, bps); } @@ -510,13 +520,13 @@ public void visitEnd() { } // Use this to debug the raw byte code instruction changes in the even the visitors fail - // if(changed) - // { - // for( AbstractInsnNode instruction : instructions ) - // { - // System.out.println(InsnPrinter.prettyPrint( instruction )); - // } - // } +// if(changed) +// { +// for( AbstractInsnNode instruction : instructions ) +// { +// System.out.println(InsnPrinter.prettyPrint( instruction )); +// } +// } this.accept(jsrInlinerAdapter); diff --git a/agent/src/test/java/coldfusion/package-info.java b/agent/src/test/java/coldfusion/package-info.java new file mode 100644 index 0000000..e1e5247 --- /dev/null +++ b/agent/src/test/java/coldfusion/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * These packages and classes are intended to act as stubs to let us load Cf classes during testing. + */ +package coldfusion; \ No newline at end of file diff --git a/agent/src/test/java/coldfusion/runtime/AttributeCollection.java b/agent/src/test/java/coldfusion/runtime/AttributeCollection.java index 057a6d7..0fc5fad 100644 --- a/agent/src/test/java/coldfusion/runtime/AttributeCollection.java +++ b/agent/src/test/java/coldfusion/runtime/AttributeCollection.java @@ -17,6 +17,7 @@ package coldfusion.runtime; +@SuppressWarnings("ALL") public class AttributeCollection { public AttributeCollection(Object[] objects) { diff --git a/agent/src/test/java/coldfusion/runtime/CfJspPage.java b/agent/src/test/java/coldfusion/runtime/CfJspPage.java index a46a2b0..63f2b8e 100644 --- a/agent/src/test/java/coldfusion/runtime/CfJspPage.java +++ b/agent/src/test/java/coldfusion/runtime/CfJspPage.java @@ -19,8 +19,10 @@ import coldfusion.tagext.io.OutputTag; import java.io.IOException; +import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.tagext.Tag; +@SuppressWarnings("ALL") public abstract class CfJspPage { public Tag parent; public NeoPageContext pageContext = new NeoPageContext(); @@ -38,4 +40,8 @@ public Tag _initTag(Class clazz, int slot, Tag parent) throws IOException { } public void _setCurrentLineNo(int lineNo) {} + + public void _whitespace(JspWriter out, String msg){ + + } } diff --git a/agent/src/test/java/coldfusion/runtime/LocalScope.java b/agent/src/test/java/coldfusion/runtime/LocalScope.java index f70cfe6..f1e91e4 100644 --- a/agent/src/test/java/coldfusion/runtime/LocalScope.java +++ b/agent/src/test/java/coldfusion/runtime/LocalScope.java @@ -17,6 +17,7 @@ package coldfusion.runtime; +@SuppressWarnings("ALL") public class LocalScope { } diff --git a/agent/src/test/java/coldfusion/runtime/NeoPageContext.java b/agent/src/test/java/coldfusion/runtime/NeoPageContext.java index 129f941..0f457fc 100644 --- a/agent/src/test/java/coldfusion/runtime/NeoPageContext.java +++ b/agent/src/test/java/coldfusion/runtime/NeoPageContext.java @@ -19,13 +19,14 @@ import javax.servlet.jsp.JspWriter; import org.apache.jasper.runtime.JspWriterImpl; -import org.mockito.Mockito; +@SuppressWarnings("ALL") public class NeoPageContext { - JspWriter getOut() { + public JspWriter getOut() { return new JspWriterImpl(); } - void setPageEncoding(String encoding){} + public void setPageEncoding(String encoding) { + } } diff --git a/agent/src/test/java/coldfusion/runtime/Variable.java b/agent/src/test/java/coldfusion/runtime/Variable.java index 0f24a0a..cb0fa7c 100644 --- a/agent/src/test/java/coldfusion/runtime/Variable.java +++ b/agent/src/test/java/coldfusion/runtime/Variable.java @@ -17,6 +17,10 @@ package coldfusion.runtime; +@SuppressWarnings("ALL") public class Variable { + public void set(int i) { + + } } diff --git a/agent/src/test/java/coldfusion/runtime/VariableScope.java b/agent/src/test/java/coldfusion/runtime/VariableScope.java index 0f4c8d5..e2e73a4 100644 --- a/agent/src/test/java/coldfusion/runtime/VariableScope.java +++ b/agent/src/test/java/coldfusion/runtime/VariableScope.java @@ -17,6 +17,7 @@ package coldfusion.runtime; +@SuppressWarnings("ALL") public class VariableScope { } diff --git a/agent/src/test/java/coldfusion/runtime/package-info.java b/agent/src/test/java/coldfusion/runtime/package-info.java deleted file mode 100644 index 123501f..0000000 --- a/agent/src/test/java/coldfusion/runtime/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * These packages and classes are intended to act as stubs to let us load Cf classes during testing. - */ -package coldfusion.runtime; \ No newline at end of file diff --git a/agent/src/test/java/coldfusion/tagext/GenericTag.java b/agent/src/test/java/coldfusion/tagext/GenericTag.java new file mode 100644 index 0000000..4486395 --- /dev/null +++ b/agent/src/test/java/coldfusion/tagext/GenericTag.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package coldfusion.tagext; + +import javax.servlet.jsp.tagext.TagSupport; + +@SuppressWarnings("ALL") +public abstract class GenericTag extends TagSupport { + + public void hasEndTag(boolean b) { + + } +} diff --git a/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java b/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java index 96a9f02..ae6185d 100644 --- a/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java +++ b/agent/src/test/java/coldfusion/tagext/io/DirectoryTag.java @@ -17,6 +17,7 @@ package coldfusion.tagext.io; +@SuppressWarnings("ALL") public class DirectoryTag { } diff --git a/agent/src/test/java/coldfusion/tagext/io/FileTag.java b/agent/src/test/java/coldfusion/tagext/io/FileTag.java index ffe3af1..5de709b 100644 --- a/agent/src/test/java/coldfusion/tagext/io/FileTag.java +++ b/agent/src/test/java/coldfusion/tagext/io/FileTag.java @@ -17,6 +17,7 @@ package coldfusion.tagext.io; +@SuppressWarnings("ALL") public class FileTag { } diff --git a/agent/src/test/java/coldfusion/tagext/io/OutputTag.java b/agent/src/test/java/coldfusion/tagext/io/OutputTag.java index ddb6e1d..954278a 100644 --- a/agent/src/test/java/coldfusion/tagext/io/OutputTag.java +++ b/agent/src/test/java/coldfusion/tagext/io/OutputTag.java @@ -17,8 +17,21 @@ package coldfusion.tagext.io; -import javax.servlet.jsp.tagext.TagSupport; +import coldfusion.tagext.GenericTag; -public class OutputTag extends TagSupport { +@SuppressWarnings("ALL") +public class OutputTag extends GenericTag { + public int doStartTag(){ + return 1; + } + public int doAfterBody(){ + return 0; + } + public int doEndTag(){ + return 0; + } + public int doCatch(){ + return 0; + } } diff --git a/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java b/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java index 2af7009..61639eb 100644 --- a/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java +++ b/agent/src/test/java/coldfusion/tagext/lang/LoopTag.java @@ -17,6 +17,7 @@ package coldfusion.tagext.lang; +@SuppressWarnings("ALL") public class LoopTag { } diff --git a/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java b/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java new file mode 100644 index 0000000..7c84bba --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.times; + +import com.intergral.deep.agent.api.plugin.IPlugin.IPluginRegistration; +import com.intergral.deep.agent.api.tracepoint.ITracepoint.ITracepointRegistration; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.tracepoint.handler.Callback; +import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +class DeepAgentTest { + + private final Settings settings = Mockito.mock(Settings.class); + private final TracepointInstrumentationService tracepointInstrumentationService = Mockito.mock(TracepointInstrumentationService.class); + private DeepAgent deepAgent; + + @BeforeEach + void setUp() { + + Mockito.when(settings.getSettingAs("poll.timer", Integer.class)).thenReturn(1010); + try (MockedStatic callback = Mockito.mockStatic(Callback.class, "init")) { + deepAgent = new DeepAgent(settings, tracepointInstrumentationService); + callback.verify(() -> Callback.init(Mockito.any(), Mockito.any(), Mockito.any()), times(1)); + } + } + + @Test + void start_shouldSetPluginsAndResource() { + deepAgent.start(); + Mockito.verify(settings).setPlugins(Mockito.anyCollection()); + Mockito.verify(settings).setResource(Mockito.any()); + } + + @Test + void registerPlugin() { + final IPluginRegistration iPluginRegistration = deepAgent.registerPlugin((settings, snapshot) -> null); + + Mockito.verify(settings, times(1)).addPlugin(Mockito.any()); + + iPluginRegistration.unregister(); + + Mockito.verify(settings, times(1)).removePlugin(Mockito.any()); + } + + @Test + void registerTracepoint() { + final ITracepointRegistration iTracepointRegistration = deepAgent.registerTracepoint("some/path", 123); + + iTracepointRegistration.unregister(); + } + + @Test + void isEnabled() { + Mockito.when(settings.isActive()).thenReturn(false).thenReturn(false).thenReturn(true); + + assertFalse(deepAgent.isEnabled()); + + deepAgent.setEnabled(true); + + assertTrue(deepAgent.isEnabled()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessorTest.java new file mode 100644 index 0000000..88895f9 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessorTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.handler; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.intergral.deep.agent.push.PushUtils; +import com.intergral.deep.agent.tracepoint.handler.VariableProcessor.VariableResponse; +import com.intergral.deep.agent.tracepoint.handler.bfs.Node; +import com.intergral.deep.agent.types.snapshot.VariableID; +import com.intergral.deep.proto.tracepoint.v1.Variable; +import com.intergral.deep.test.target.VariableTypeTarget; +import com.intergral.deep.tests.snapshot.SnapshotUtils; +import com.intergral.deep.tests.snapshot.SnapshotUtils.IVariableScan; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class VariableProcessorTest { + + private VariableProcessor variableProcessor; + + @BeforeEach + void setUp() { + variableProcessor = new VariableProcessor() { + }; + variableProcessor.configureSelf(new ArrayList<>()); + + } + + @Test + void variableTypes() { + final List variableIDS = processVars(Collections.singletonMap("this", new VariableTypeTarget())); + assertNotNull(variableIDS); + + final Collection localVars = PushUtils.covertVariables(variableIDS); + final Map lookup = PushUtils.convertVarLookup(variableProcessor.varLookup); + final IVariableScan aThis = SnapshotUtils.findVarByName("this", localVars, lookup); + assertNotNull(aThis); + assertEquals("VariableTypeTarget{}", aThis.variable().getValue()); + + final IVariableScan VariableSuperTesti = SnapshotUtils.findVarByName("VariableSuperTest.i", aThis.variable().getChildrenList(), lookup); + assertTrue(VariableSuperTesti.found()); + assertEquals("9", VariableSuperTesti.variable().getValue()); + assertArrayEquals( + Arrays.stream(new String[]{"public", "static"}).sorted().toArray(), + Arrays.stream(VariableSuperTesti.variableId().getModifiersList().toArray(new String[0])).sorted().toArray()); + assertEquals("i", VariableSuperTesti.variableId().getOriginalName()); + + final IVariableScan VariableSuperSuperTesti = SnapshotUtils.findVarByName("VariableSuperSuperTest.i", + aThis.variable().getChildrenList(), lookup); + assertTrue(VariableSuperSuperTesti.found()); + assertEquals("11", VariableSuperSuperTesti.variable().getValue()); + assertArrayEquals( + Arrays.stream(new String[]{"public", "static"}).sorted().toArray(), + Arrays.stream(VariableSuperSuperTesti.variableId().getModifiersList().toArray(new String[0])).sorted().toArray()); + assertEquals("i", VariableSuperSuperTesti.variableId().getOriginalName()); + + final IVariableScan VariableSuperTestl = SnapshotUtils.findVarByName("VariableSuperTest.l", aThis.variable().getChildrenList(), lookup); + assertTrue(VariableSuperTestl.found()); + assertEquals("8", VariableSuperTestl.variable().getValue()); + assertArrayEquals(new String[]{"static"}, VariableSuperTestl.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan VariableSuperTestd = SnapshotUtils.findVarByName("VariableSuperTest.d", aThis.variable().getChildrenList(), lookup); + assertTrue(VariableSuperTestd.found()); + assertEquals("9.8", VariableSuperTestd.variable().getValue()); + assertArrayEquals(Arrays.stream(new String[]{"private", "static"}).sorted().toArray(), + VariableSuperTestd.variableId().getModifiersList().toArray(Arrays.stream(new String[0]).sorted().toArray())); + + final IVariableScan VariableSuperTestf = SnapshotUtils.findVarByName("VariableSuperTest.f", aThis.variable().getChildrenList(), lookup); + assertTrue(VariableSuperTestf.found()); + assertEquals("8.8", VariableSuperTestf.variable().getValue()); + assertArrayEquals(Arrays.stream(new String[]{"protected", "static"}).sorted().toArray(), + Arrays.stream(VariableSuperTestf.variableId().getModifiersList().toArray(new String[0])).sorted().toArray()); + + final IVariableScan i = SnapshotUtils.findVarByName("i", aThis.variable().getChildrenList(), lookup); + assertTrue(i.found()); + assertEquals("1", i.variable().getValue()); + assertArrayEquals( + Arrays.stream(new String[]{"public", "static"}).sorted().toArray(), + Arrays.stream(i.variableId().getModifiersList().toArray(new String[0])).sorted().toArray()); + + final IVariableScan l = SnapshotUtils.findVarByName("l", aThis.variable().getChildrenList(), lookup); + assertTrue(l.found()); + assertEquals("2", l.variable().getValue()); + assertArrayEquals(new String[]{"static"}, l.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan d = SnapshotUtils.findVarByName("d", aThis.variable().getChildrenList(), lookup); + assertTrue(d.found()); + assertEquals("1.2", d.variable().getValue()); + assertArrayEquals(Arrays.stream(new String[]{"private", "static"}).sorted().toArray(), + d.variableId().getModifiersList().toArray(Arrays.stream(new String[0]).sorted().toArray())); + + final IVariableScan f = SnapshotUtils.findVarByName("f", aThis.variable().getChildrenList(), lookup); + assertTrue(f.found()); + assertEquals("2.2", f.variable().getValue()); + assertArrayEquals(Arrays.stream(new String[]{"protected", "static"}).sorted().toArray(), + Arrays.stream(f.variableId().getModifiersList().toArray(new String[0])).sorted().toArray()); + + final IVariableScan str = SnapshotUtils.findVarByName("str", aThis.variable().getChildrenList(), lookup); + assertTrue(str.found()); + assertTrue(str.variable().getValue().startsWith("str with a very large value will be truncated, needs to be over 1024 by default")); + assertEquals(1024, str.variable().getValue().length()); + assertTrue(str.variable().hasTruncated()); + assertArrayEquals(new String[]{"public"}, str.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan nul = SnapshotUtils.findVarByName("nul", aThis.variable().getChildrenList(), lookup); + assertTrue(nul.found()); + assertEquals("null", nul.variable().getValue()); + assertArrayEquals(new String[]{}, nul.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan object = SnapshotUtils.findVarByName("object", aThis.variable().getChildrenList(), lookup); + assertTrue(object.found()); + assertEquals("java.lang.Object", object.variable().getValue().split("@")[0]); + assertArrayEquals(new String[]{"private", "final"}, object.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan arry = SnapshotUtils.findVarByName("arry", aThis.variable().getChildrenList(), lookup); + assertTrue(arry.found()); + assertEquals("Array of length: 2", arry.variable().getValue()); + assertArrayEquals(new String[]{"protected"}, arry.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan list = SnapshotUtils.findVarByName("list", aThis.variable().getChildrenList(), lookup); + assertTrue(list.found()); + assertEquals("ArrayList of size: 0", list.variable().getValue()); + assertArrayEquals(new String[]{"private", "volatile"}, list.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan set = SnapshotUtils.findVarByName("set", aThis.variable().getChildrenList(), lookup); + assertTrue(set.found()); + assertEquals("HashSet of size: 2", set.variable().getValue()); + assertArrayEquals(new String[]{"private", "transient"}, set.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan simplemap = SnapshotUtils.findVarByName("simplemap", aThis.variable().getChildrenList(), lookup); + assertTrue(simplemap.found()); + assertEquals("HashMap of size: 1", simplemap.variable().getValue()); + assertArrayEquals(new String[]{"private"}, simplemap.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan complexMap = SnapshotUtils.findVarByName("complexMap", aThis.variable().getChildrenList(), lookup); + assertTrue(complexMap.found()); + assertEquals("VariableTypeTarget$1 of size: 3", complexMap.variable().getValue()); + assertArrayEquals(new String[]{"private"}, complexMap.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan iter = SnapshotUtils.findVarByName("iter", aThis.variable().getChildrenList(), lookup); + assertTrue(iter.found()); + assertEquals("Iterator of type: HashMap$EntryIterator", iter.variable().getValue()); + assertArrayEquals(new String[]{"private"}, iter.variableId().getModifiersList().toArray(new String[0])); + + final IVariableScan someObj = SnapshotUtils.findVarByName("someObj", aThis.variable().getChildrenList(), lookup); + assertTrue(someObj.found()); + assertEquals("VariableTypeTarget{}", someObj.variable().getValue()); + assertArrayEquals(new String[]{"private"}, someObj.variableId().getModifiersList().toArray(new String[0])); + } + + + protected List processVars(final Map variables) { + final List frameVars = new ArrayList<>(); + + final Node.IParent frameParent = frameVars::add; + + final Set initialNodes = variables.entrySet() + .stream() + .map(stringObjectEntry -> new Node(new Node.NodeValue(stringObjectEntry.getKey(), + stringObjectEntry.getValue()), frameParent)).collect(Collectors.toSet()); + + Node.breadthFirstSearch(new Node(null, new HashSet<>(initialNodes), frameParent), + this::processNode); + + return frameVars; + } + + protected boolean processNode(final Node node) { + if (!variableProcessor.checkVarCount()) { + // we have exceeded the var count, so do not continue + return false; + } + + final Node.NodeValue value = node.getValue(); + if (value == null) { + // this node has no value, continue with children + return true; + } + + // process this node variable + final VariableResponse processResult = variableProcessor.processVariable(value); + final VariableID variableId = processResult.getVariableId(); + + // add the result to the parent - this maintains the hierarchy in the var look up + node.getParent().addChild(variableId); + + if (value.getValue() != null && processResult.processChildren()) { + final Set childNodes = variableProcessor.processChildNodes(variableId, value.getValue(), + node.depth()); + node.addChildren(childNodes); + } + return true; + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java index 56fcc02..e945e7b 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/VisitorTest.java @@ -36,6 +36,7 @@ import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService; import com.intergral.deep.agent.types.TracePointConfig; import com.intergral.deep.agent.types.snapshot.EventSnapshot; +import com.intergral.deep.agent.types.snapshot.StackFrame; import com.intergral.deep.proto.tracepoint.v1.Snapshot; import com.intergral.deep.proto.tracepoint.v1.Variable; import com.intergral.deep.test.MockTracepointConfig; @@ -43,6 +44,7 @@ import com.intergral.deep.tests.inst.ByteClassLoader; import com.intergral.deep.tests.snapshot.SnapshotUtils; import com.intergral.deep.tests.snapshot.SnapshotUtils.IVariableScan; +import java.io.IOException; import java.lang.instrument.Instrumentation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -65,6 +67,7 @@ import javax.servlet.jsp.JspFactory; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; +import lucee.runtime.PageSource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -1271,37 +1274,48 @@ void someFunctionWithABody() throws Exception { assertEquals("someFunctionWithABody", snapshot.getFrames(0).getMethodName()); } - // cannot seem to load cf class files - // consistent issues with verifier -// @Test -// void cfVisitor() throws Exception { -// final MockTracepointConfig tracepointConfig = new MockTracepointConfig("/src/main/cfml/testFile.cfm", 3); -// tracepointRef.set(Collections.singletonList(tracepointConfig)); -// // we need to process the cfm tracepoints -// instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); -// -// final String name = "cftestFile2ecfm137384933"; -// final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(name); -// final byte[] originalBytes = byteClassLoader.getBytes(name); -// -// // for adobe cf we need a location url to be set -// cfUrl.set(new URL("file:///src/main/cfml/testFile.cfm")); -// -// final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); -// // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other -// TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); -// -// assertNotNull(transformed, "Failed to transform the test class!"); -// assertNotEquals(originalBytes.length, transformed.length); -// -// byteClassLoader.setBytes("coldfusion.runtime.CfJspPage", ByteClassLoader.loadBytes("coldfusion/runtime/CfJspPage")); -// byteClassLoader.setBytes("coldfusion.runtime.CFPage", ByteClassLoader.loadBytes("coldfusion/runtime/CFPage")); + + @Test + void cfVisitor() throws Exception { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig("/src/main/cfml/testFile.cfm", 3); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + // we need to process the cfm tracepoints + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + final String name = "cftestFile2ecfm137384933"; + final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(name); + final byte[] originalBytes = byteClassLoader.getBytes(name); + + final JspFactory jspFactory = Mockito.mock(JspFactory.class); + final PageContext pageContext = Mockito.mock(PageContext.class); + final JspWriter jspWriter = Mockito.mock(JspWriter.class); + Mockito.when(pageContext.getOut()).thenReturn(jspWriter); + + JspFactory.setDefaultFactory(jspFactory); + Mockito.when(jspFactory.getPageContext(Mockito.any(Servlet.class), Mockito.any(ServletRequest.class), Mockito.any( + ServletResponse.class), Mockito.eq(null), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyBoolean())) + .thenReturn(pageContext); + + // for adobe cf we need a location url to be set + cfUrl.set(new URL("file:///src/main/cfml/testFile.cfm")); + + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform the test class!"); + assertNotEquals(originalBytes.length, transformed.length); + + // we cannot load the cf class in test case for some reason + // consistent issues with verifier // byteClassLoader.setBytes(name, transformed); -// byteClassLoader.loadClass("java.lang.Object"); -// final Class jspPageClass = byteClassLoader.loadClass("coldfusion.runtime.CfJspPage"); -// assertNotNull(jspPageClass); -// final Class pageClass = byteClassLoader.loadClass("coldfusion.runtime.CFPage"); -// assertNotNull(pageClass); +// final List classes = Arrays.asList("java.lang.Object", "coldfusion.tagext.io.OutputTag", "coldfusion.runtime.NeoPageContext", +// "coldfusion.runtime.CfJspPage", "coldfusion.runtime.CFPage"); +// for (String s : classes) { +// final Class aClass = byteClassLoader.loadClass(s); +// assertNotNull(aClass); +// assertEquals(aClass.getName(), s); +// } // final Class aClass = byteClassLoader.loadClass(name); // // final Constructor constructor = aClass.getConstructor(); @@ -1314,8 +1328,8 @@ void someFunctionWithABody() throws Exception { // // Mockito.verify(pushService, Mockito.times(1)) // .pushSnapshot(argumentCaptor.capture(), Mockito.any()); -// -// } + + } @Test @@ -1376,4 +1390,48 @@ void jspVisitorTest() throws Exception { assertEquals("string_jsp.java", snapshot.getFrames(0).getTranspiledFileName()); assertEquals(127, snapshot.getFrames(0).getTranspiledLineNumber()); } + + @Test + void luceeVisitorTest() + throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig("/tests/testFile.cfm", 3); + tracepointRef.set(Collections.singletonList(tracepointConfig)); + // we need to process the jsp tracepoints + instrumentationService.processBreakpoints(Collections.singletonList(tracepointConfig)); + + cfUrl.set(new URL("file:///tests/testFile.cfm")); + final String name = "testfile_cfm$cf"; + final ByteClassLoader byteClassLoader = ByteClassLoader.forFile(name); + final byte[] originalBytes = byteClassLoader.getBytes(name); + + final byte[] transformed = instrumentationService.transform(null, name, null, null, originalBytes); + // we do this here so each test can save the modified bytes, else as they all use the same target class they would stomp over each other + TransformerUtils.storeUnsafe(disPath, originalBytes, transformed, name + Thread.currentThread().getStackTrace()[1].getMethodName()); + + assertNotNull(transformed, "Failed to transform test class."); + assertNotEquals(originalBytes.length, transformed.length); + + final String className = InstUtils.externalClassName(name); + byteClassLoader.setBytes(className, transformed); + final Class aClass = byteClassLoader.loadClass(className); + + final Constructor constructor = aClass.getConstructor(PageSource.class); + final Object instance = constructor.newInstance(new PageSource()); + final Method call = aClass.getMethod("call", lucee.runtime.PageContext.class); + call.invoke(instance, new lucee.runtime.PageContext()); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + + Mockito.verify(pushService, Mockito.times(1)) + .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + + final EventSnapshot value = argumentCaptor.getValue(); + assertNotNull(value); + // Cannot really verify variables as we are using fake classes to run this test + + final StackFrame stackFrame = value.getFrames().iterator().next(); + assertEquals("testFile.cfm", stackFrame.getFileName()); + assertEquals(3, stackFrame.getLineNumber()); + assertEquals("testfile_cfm$cf", stackFrame.getClassName()); + } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java b/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java new file mode 100644 index 0000000..2fdba45 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.test.target; + +@SuppressWarnings("ALL") +public class VariableSuperSuperTest { + public static int i = 11; +} diff --git a/agent/src/test/java/com/intergral/deep/test/target/VariableSuperTest.java b/agent/src/test/java/com/intergral/deep/test/target/VariableSuperTest.java new file mode 100644 index 0000000..e482ccf --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/target/VariableSuperTest.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.test.target; + +@SuppressWarnings("ALL") +public class VariableSuperTest extends VariableSuperSuperTest { + + public static int i = 9; + protected static float f = 8.8f; + static long l = 8L; + private static double d = 9.8d; +} diff --git a/agent/src/test/java/com/intergral/deep/test/target/VariableTypeTarget.java b/agent/src/test/java/com/intergral/deep/test/target/VariableTypeTarget.java new file mode 100644 index 0000000..c99697e --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/target/VariableTypeTarget.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.test.target; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@SuppressWarnings("ALL") +public class VariableTypeTarget extends VariableSuperTest { + + public static int i = 1; + protected static float f = 2.2f; + static long l = 2L; + private static double d = 1.2d; + private final Object object = new Object(); + public String str = + "str with a very large value will be truncated, needs to be over 1024 by default.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................." + + "str with a very large value will be truncated, needs to be over 1024 by default.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................." + + "str with a very large value will be truncated, needs to be over 1024 by default.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................."; + protected String[] arry = new String[]{"some", "thing"}; + Object nul = null; + private volatile List list = new ArrayList<>(); + private transient Set set = new HashSet<>(Arrays.asList("one", "two")); + + private Map simplemap = new HashMap<>(Collections.singletonMap("key", "value")); + private Map complexMap = new HashMap() {{ + put("key", "other"); + put("1", 1); + put("list", list); + }}; + + private Iterator iter = complexMap.entrySet().iterator(); + + private VariableTypeTarget someObj = this; + + public VariableTypeTarget() { + // this is here to ensure we have a line to install TP on for tests + nul = null; + } + + @Override + public String toString() { + return "VariableTypeTarget{}"; + } +} diff --git a/agent/src/test/java/lucee/commons/color/ConstantsDouble.java b/agent/src/test/java/lucee/commons/color/ConstantsDouble.java new file mode 100644 index 0000000..a7d0fb5 --- /dev/null +++ b/agent/src/test/java/lucee/commons/color/ConstantsDouble.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.commons.color; + +@SuppressWarnings("ALL") +public class ConstantsDouble { +public static Double _100 = Double.valueOf(100.0d); +} diff --git a/agent/src/test/java/lucee/package-info.java b/agent/src/test/java/lucee/package-info.java new file mode 100644 index 0000000..d44ba6a --- /dev/null +++ b/agent/src/test/java/lucee/package-info.java @@ -0,0 +1,4 @@ +/** + * These packages are here to allow for using lucee classes in visitor tests + */ +package lucee; diff --git a/agent/src/test/java/lucee/runtime/PageContext.java b/agent/src/test/java/lucee/runtime/PageContext.java new file mode 100644 index 0000000..52a0d99 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/PageContext.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime; + +import lucee.runtime.type.Collection.Key; +import lucee.runtime.type.scope.Undefined; + +@SuppressWarnings("ALL") +public class PageContext { + + private Object server = new Scope("server"); + private Object variables = new Scope("variables"); + private Object local = new Scope("local"); + private Object cookie = new Scope("cookie"); + private Object session = new Scope("session"); + private Object application = new Scope("application"); + private Object cgiR = new Scope("cgiR"); + private Object request = new Scope("request"); + private Object _form = new Scope("_form"); + private Object _url = new Scope("_url"); + private Object client = new Scope("client"); + private Object threads = new Scope("threads"); + + public void write(String str) { + + } + + public Undefined us() { + return new Undefined(){ + @Override + public Object get(final Key k) { + return new Object(); + } + + @Override + public Object set(final Key ket, final Object obj) { + return obj; + } + }; + } + + public void outputStart(){} + public void outputEnd(){} + + public static class Scope { + + private final String threads; + + + public Scope(final String threads) { + this.threads = threads; + } + + public boolean isInitalized() { + return false; + } + } +} diff --git a/agent/src/test/java/lucee/runtime/PageContextImpl.java b/agent/src/test/java/lucee/runtime/PageContextImpl.java new file mode 100644 index 0000000..8bd0f14 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/PageContextImpl.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime; + +@SuppressWarnings("ALL") +public class PageContextImpl +{ + public String evaluate( final String expression ) + { + return expression; + } +} diff --git a/agent/src/test/java/lucee/runtime/PageImpl.java b/agent/src/test/java/lucee/runtime/PageImpl.java new file mode 100644 index 0000000..4237048 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/PageImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime; + +import lucee.runtime.type.UDFProperties; + +@SuppressWarnings("ALL") +public class PageImpl +{ + protected UDFProperties[] udfs; + + protected void setPageSource(PageSource source){} + + public static class SomePageImpl extends PageImpl { + + } +} diff --git a/agent/src/test/java/lucee/runtime/PageSource.java b/agent/src/test/java/lucee/runtime/PageSource.java new file mode 100644 index 0000000..a600523 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/PageSource.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime; + +@SuppressWarnings("ALL") +public class PageSource { + +} diff --git a/agent/src/test/java/lucee/runtime/component/ImportDefintion.java b/agent/src/test/java/lucee/runtime/component/ImportDefintion.java new file mode 100644 index 0000000..44753a1 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/component/ImportDefintion.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.component; + +@SuppressWarnings("ALL") +public class ImportDefintion { + +} diff --git a/agent/src/test/java/lucee/runtime/exp/PageException.java b/agent/src/test/java/lucee/runtime/exp/PageException.java new file mode 100644 index 0000000..636b43c --- /dev/null +++ b/agent/src/test/java/lucee/runtime/exp/PageException.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.exp; + +@SuppressWarnings("ALL") +public class PageException extends Exception { + +} diff --git a/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java b/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java new file mode 100644 index 0000000..b17db32 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.interpreter; + +import lucee.runtime.type.ref.VariableReference; + +@SuppressWarnings("ALL") +public class VariableInterpreter { + + public static VariableReference getVariableReference(lucee.runtime.PageContext ctx, String name){ + return new VariableReference(); + } +} diff --git a/agent/src/test/java/lucee/runtime/op/Caster.java b/agent/src/test/java/lucee/runtime/op/Caster.java new file mode 100644 index 0000000..9037a47 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/op/Caster.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.op; + +@SuppressWarnings("ALL") +public class Caster { + public static Double toDouble(double d){ + return d; + } + public static double toDoubleValue(Double d){ + return d; + } + + public static String toString(Object obj){ + return ""; + } +} diff --git a/agent/src/test/java/lucee/runtime/op/Operator.java b/agent/src/test/java/lucee/runtime/op/Operator.java new file mode 100644 index 0000000..10a87e1 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/op/Operator.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.op; + +@SuppressWarnings("ALL") +public class Operator { + + public static Double modulusRef(Object obj1, Object obj2) { + return 2.0d; + } + + public static int compare(double d1, double d2) { + return 0; + } +} diff --git a/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java b/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java new file mode 100644 index 0000000..d3e2ea5 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.scope; + +import java.util.HashMap; + +@SuppressWarnings("ALL") +public class MockLuceeScope extends HashMap +{ +} diff --git a/agent/src/test/java/lucee/runtime/type/Collection.java b/agent/src/test/java/lucee/runtime/type/Collection.java new file mode 100644 index 0000000..64fa2e5 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/type/Collection.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.type; + +@SuppressWarnings("ALL") +public class Collection { +public static class Key { + +} +} diff --git a/agent/src/test/java/lucee/runtime/type/UDF.java b/agent/src/test/java/lucee/runtime/type/UDF.java new file mode 100644 index 0000000..5b064c1 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/type/UDF.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.type; + +@SuppressWarnings("ALL") +public class UDF { + +} diff --git a/agent/src/test/java/lucee/runtime/type/UDFProperties.java b/agent/src/test/java/lucee/runtime/type/UDFProperties.java new file mode 100644 index 0000000..f58bebb --- /dev/null +++ b/agent/src/test/java/lucee/runtime/type/UDFProperties.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.type; + +@SuppressWarnings("ALL") +public class UDFProperties { + +} diff --git a/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java b/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java new file mode 100644 index 0000000..4df1aac --- /dev/null +++ b/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.type.ref; + +@SuppressWarnings("ALL") +public class VariableReference { + + public void set(double d){} +} diff --git a/agent/src/test/java/lucee/runtime/type/scope/Undefined.java b/agent/src/test/java/lucee/runtime/type/scope/Undefined.java new file mode 100644 index 0000000..3be40bf --- /dev/null +++ b/agent/src/test/java/lucee/runtime/type/scope/Undefined.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.type.scope; + +import lucee.runtime.type.Collection.Key; + +@SuppressWarnings("ALL") +public interface Undefined { + + Object get(Key k); + + Object set(Key ket, Object obj); +} diff --git a/agent/src/test/java/lucee/runtime/type/util/KeyConstants.java b/agent/src/test/java/lucee/runtime/type/util/KeyConstants.java new file mode 100644 index 0000000..4fa2cd5 --- /dev/null +++ b/agent/src/test/java/lucee/runtime/type/util/KeyConstants.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.runtime.type.util; + +import lucee.runtime.type.Collection.Key; + +@SuppressWarnings("ALL") +public class KeyConstants { + + public static final Key _I = new Key(); +} diff --git a/agent/src/test/resources/testfile_cfm$cf.class b/agent/src/test/resources/testfile_cfm$cf.class new file mode 100644 index 0000000000000000000000000000000000000000..ea1eb7d0d8cc3c121b3562612e97b03b64ef7916 GIT binary patch literal 2393 zcmb7GZF3V<6n<`!k~ZD8G*GZOyh(*5Eo{LT)F6s!v4Iq-EkQx8+vFzQve}KByRo^G={@H>=Q%I;-2CzPPrm@TfX@ZS zlx(S*?a0+itua-pB_RY3IYC8A(+^x_H>9~_)n&2Kbdrb*jF{TuDlN{@LSWw|+qKnY zfuotyvu4X);8$cmTNa3CvSkDNFqlFDLjr@^?z()?GBB(+V=x4gcBAR}swI%S+Xq+i zC~~E%Omd;loUGa8=ec=-GrgYwRW*tI21YS9L~m(u{zll?!uCJ53=LCI>VMXzetY*`%u#FsOhcV?OXrwY=ot96RnPex#rq5@-P zrdL>nw$YW0`zMn`$%55dZ*wljcYZ(mn?RysIgWrCRfU4*D!HZd+0uq}-!dJ`t(!O3 zHe^LHcL$#Ct*rarrnTnCBuF5k)@_D7Ug~QR+D!K5TQ^APBZ(P-SU|henQUJL)q|!q zTNRJ*TW(dCPge5=&cRIKESQa{Uf8yNplruvn-IUHELXLXxIho9MS)XM_yx~#7#K@B zMJvL;j8}&65?-Z#2cF&0AedS49M3m*ap%0C9r7={T)sYhJ4djRLIxY#k z5W%)Dx+Hio;w`BJiv5LYN7u);%VAibh>{t28&`($7T#eVQmH}A2@Gudwvqx9nJ$QB z<=bwZhtI%OysLA(ATaC&su`$dW%#875D?749UhCIS}U{< z!9sr&T@9L#YMNVfSM$Y)us9;rx-YG22lx11xJ5nW#>jz#$qF1*ww!j0-#WV}9X|$lY8sK#Y-jE3RUykw^*JHUKkjm{~Bp3e~xJ`!d8{1fA&QPN{iq$i@JC!a`9VJdVy z7CMfnU;H+7dm3lRRmb`Tvl8c8U`Xd5;o>8_`51*bZcnG@cTnVRX*v`=#!?JFqRj2D zky_;LhcmzG_jLL$_ryc`If7Y!bb{~mt<$QX9H#l+5$sf$h}x|KeK6)NfA_0=1nO!Q(^SbY9Ep;)J3g6f*< SkS=j^8-52<-~l&d82AV2m1Q0P literal 0 HcmV?d00001 diff --git a/examples/docker/lucee/Dockerfile b/examples/docker/lucee/Dockerfile new file mode 100644 index 0000000..5ccdd6c --- /dev/null +++ b/examples/docker/lucee/Dockerfile @@ -0,0 +1,3 @@ +FROM lucee/lucee:5.4 + +COPY testFile.cfm /var/www/testFile.cfm \ No newline at end of file diff --git a/examples/docker/lucee/testFile.cfm b/examples/docker/lucee/testFile.cfm new file mode 100644 index 0000000..a4a683d --- /dev/null +++ b/examples/docker/lucee/testFile.cfm @@ -0,0 +1,3 @@ + + +#i# diff --git a/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java b/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java index ba7206a..1bc1821 100644 --- a/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java +++ b/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java @@ -27,6 +27,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.Set; import java.util.stream.Collectors; @@ -78,6 +79,7 @@ public T callMethod(final Object target, final String methodName, final Obje if (method != null) { try { setAccessible(target.getClass(), method); + //noinspection unchecked return (T) method.invoke(target, args); } catch (IllegalAccessException | InvocationTargetException e) { return null; @@ -173,6 +175,9 @@ public T callField(final Object target, final Field field) { @Override public Set getModifiers(final Field field) { final int modifiers = field.getModifiers(); + if (modifiers == 0) { + return Collections.emptySet(); + } final String string = Modifier.toString(modifiers); return Arrays.stream(string.split(" ")).collect(Collectors.toSet()); } diff --git a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java index d4e730c..7f15026 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java @@ -20,7 +20,7 @@ import com.intergral.deep.proto.tracepoint.v1.Snapshot; import com.intergral.deep.proto.tracepoint.v1.Variable; import com.intergral.deep.proto.tracepoint.v1.VariableID; -import java.util.List; +import java.util.Collection; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -46,7 +46,7 @@ public static IVariableScan findVarByName(final String name, final Snapshot snap * @return a {@link IVariableScan} */ public static IVariableScan findVarByName(final String name, - final List localVars, + final Collection localVars, final Map lookup) { final Optional first = localVars.stream() .filter(variableID -> Objects.equals(variableID.getName(), name)) From 7e1ee981f0893b57fe50d97b4889a4b75efd0c11 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Thu, 31 Aug 2023 13:46:28 +0100 Subject: [PATCH 10/15] chore(tests): add license check to lint --- .github/workflows/on_push.yml | 2 +- LICENSE.txt | 16 ++++++++++++++++ Makefile | 4 ++-- checkstyle.xml | 3 +++ pom.xml | 1 + 5 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 LICENSE.txt diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml index 1fb5a7b..5bf9510 100644 --- a/.github/workflows/on_push.yml +++ b/.github/workflows/on_push.yml @@ -39,7 +39,7 @@ jobs: distribution: 'temurin' cache: 'maven' - name: Check Formatting - run: make check-formatting + run: make lint pmd: runs-on: ubuntu-latest diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..dd104e2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ \ No newline at end of file diff --git a/Makefile b/Makefile index 9cd04d3..92b593b 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ pmd: mvn -U -B verify -Ppmd -DskipTests $(MVN_ARGS) -,PHONY: check-formatting -check-formatting: +,PHONY: lint +lint: mvn -U -B validate -Plint,examples,cf-it-tests $(MVN_ARGS) .PHONY: test diff --git a/checkstyle.xml b/checkstyle.xml index e5e6bc0..c0e09e2 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -404,4 +404,7 @@ + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 02513be..78b4f1a 100644 --- a/pom.xml +++ b/pom.xml @@ -286,6 +286,7 @@ checkstyle.xml + LICENSE.txt true true true From 75052c27bca0e1b1e7a672f085bafa32299fd82b Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 1 Sep 2023 00:01:29 +0100 Subject: [PATCH 11/15] chore(tests): add more unit tests --- .../agent/api/reflection/ReflectionUtils.java | 5 +- .../deep/agent/api/settings/ISettings.java | 22 ++ .../java/com/intergral/deep/agent/Agent.java | 10 +- .../com/intergral/deep/agent/AgentImpl.java | 3 + .../com/intergral/deep/agent/IDUtils.java | 5 +- .../intergral/deep/agent/ReflectionUtils.java | 5 +- .../java/com/intergral/deep/agent/Utils.java | 6 +- .../intergral/deep/agent/logging/Logger.java | 5 +- .../deep/agent/plugins/PluginLoader.java | 5 +- .../deep/agent/push/PushService.java | 44 +-- .../intergral/deep/agent/push/PushUtils.java | 5 +- .../deep/agent/resource/ResourceDetector.java | 5 +- .../deep/agent/resource/SpiUtil.java | 5 +- .../deep/agent/settings/Settings.java | 11 +- .../agent/tracepoint/TracepointUtils.java | 5 +- .../deep/agent/tracepoint/cf/CFEvaluator.java | 20 +- .../agent/tracepoint/cf/CFFrameProcessor.java | 2 +- .../deep/agent/tracepoint/cf/CFUtils.java | 12 +- .../evaluator/EvaluatorService.java | 5 +- .../evaluator/NashornReflectEvaluator.java | 8 +- .../agent/tracepoint/handler/Callback.java | 9 +- .../tracepoint/handler/FrameCollector.java | 223 ++++++++++++-- .../agent/tracepoint/handler/FrameConfig.java | 11 + .../tracepoint/handler/FrameProcessor.java | 84 +++++- .../tracepoint/handler/VariableProcessor.java | 12 +- .../deep/agent/tracepoint/inst/InstUtils.java | 5 +- .../agent/tracepoint/inst/asm/ClassInfo.java | 6 +- .../tracepoint/inst/asm/TransformerUtils.java | 12 +- .../agent/tracepoint/inst/asm/Visitor.java | 5 - .../agent/tracepoint/inst/jsp/JSPUtils.java | 9 +- .../inst/jsp/sourcemap/SmapUtils.java | 4 +- .../deep/agent/types/TracePointConfig.java | 2 +- .../intergral/deep/agent/AgentImplTest.java | 50 ++++ .../intergral/deep/agent/DeepAgentTest.java | 20 ++ .../com/intergral/deep/agent/UtilsTest.java | 15 + .../deep/agent/plugins/PluginLoaderTest.java | 1 + .../deep/agent/push/PushServiceTest.java | 10 + .../deep/agent/settings/SettingsTest.java | 85 +++++- .../agent/tracepoint/cf/CFEvaluatorTest.java | 58 ++++ .../deep/agent/tracepoint/cf/CFUtilsTest.java | 230 ++++++++++++++ .../evaluator/EvaluatorServiceTest.java | 41 +++ .../NashornReflectEvaluatorTest.java | 17 ++ .../handler/FrameCollectorTest.java | 66 ++++ .../handler/FrameProcessorTest.java | 175 +++++++++++ .../tracepoint/handler/bfs/NodeTest.java | 15 +- .../tracepoint/inst/CFClassScannerTest.java | 42 +++ .../agent/tracepoint/inst/InstUtilsTest.java | 70 +++++ .../TracepointInstrumentationServiceTest.java | 12 + .../asm/ClassInfoNotFoundExceptionTest.java | 32 ++ .../tracepoint/inst/asm/ClassInfoTest.java | 49 +++ .../tracepoint/inst/jsp/JSPUtilsTest.java | 97 ++++++ .../jsp/sourcemap/FileSectionEntryTest.java | 49 +++ .../jsp/sourcemap/LineSectionEntryTest.java | 27 ++ .../jsp/sourcemap/SouceMapParserTest.java | 283 ++++++++++++++++++ .../deep/test/MockTracepointConfig.java | 6 + .../deep/test/target/ConditionTarget.java | 22 ++ .../java/lucee/commons/lang/PCLBlock.java | 23 ++ .../loader/classloader/LuceeClassLoader.java | 23 ++ .../java/railo/commons/lang/PCLBlock.java | 23 ++ .../loader/classloader/RailoClassLoader.java | 23 ++ agent/src/test/resources/2_stratum.smap | 20 ++ agent/src/test/resources/include_time.smap | 35 +++ agent/src/test/resources/include_time2.smap | 39 +++ agent/src/test/resources/include_time3.smap | 37 +++ agent/src/test/resources/index2_jsp.smap | 13 + agent/src/test/resources/index_jsp.smap | 27 ++ agent/src/test/resources/noop.smap | 4 + .../apache/jsp/jdbc/views/companies_jsp.class | Bin 0 -> 36007 bytes .../com/intergral/deep/plugins/cf/Utils.java | 5 +- pom.xml | 8 + .../deep/tests/snapshot/SnapshotUtils.java | 6 +- 71 files changed, 2201 insertions(+), 127 deletions(-) create mode 100644 agent/src/test/java/com/intergral/deep/agent/AgentImplTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluatorTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFUtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorServiceTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java rename agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java => agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/bfs/NodeTest.java (72%) create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/CFClassScannerTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/InstUtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundExceptionTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java create mode 100644 agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java create mode 100644 agent/src/test/java/lucee/commons/lang/PCLBlock.java create mode 100644 agent/src/test/java/lucee/loader/classloader/LuceeClassLoader.java create mode 100644 agent/src/test/java/railo/commons/lang/PCLBlock.java create mode 100644 agent/src/test/java/railo/loader/classloader/RailoClassLoader.java create mode 100644 agent/src/test/resources/2_stratum.smap create mode 100644 agent/src/test/resources/include_time.smap create mode 100644 agent/src/test/resources/include_time2.smap create mode 100644 agent/src/test/resources/include_time3.smap create mode 100644 agent/src/test/resources/index2_jsp.smap create mode 100644 agent/src/test/resources/index_jsp.smap create mode 100644 agent/src/test/resources/noop.smap create mode 100644 agent/src/test/resources/org/apache/jsp/jdbc/views/companies_jsp.class diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java index cd6af61..28a1e19 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java @@ -21,7 +21,10 @@ import com.intergral.deep.agent.api.settings.ISettings; import java.lang.reflect.Constructor; -public class ReflectionUtils { +public final class ReflectionUtils { + + private ReflectionUtils() { + } public static T callConstructor(final Constructor constructor, final ISettings settings, final IReflection reflection) { if (constructor.getParameterTypes().length == 0) { diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java index f0d791a..a9edd13 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java @@ -48,6 +48,28 @@ public interface ISettings { */ String PLUGINS = "plugins"; + /** + * To let us calculate the class and file names for JSP classes we need to know the JSP suffix that is being used by monitored service. + *

+ * By default, tomcat take index.jsp and make it into index_jsp.class, but this suffix can be configured. + */ + String JSP_SUFFIX = "jsp.suffix"; + /** + * It is possible to put compiled JSP classes into specified packages, some versions put this in a {@code jsp} package, some use + * {@code org.apache.jsp} (newer). + */ + String JSP_PACKAGES = "jsp.packages"; + + /** + * Define which packages we should include as being part of your app. + */ + String APP_FRAMES_INCLUDES = "in.app.include"; + + /** + * Define which packages we should exclude as being part of your app. + */ + String APP_FRAMES_EXCLUDES = "in.app.include"; + T getSettingAs(String key, Class clazz); Map getMap(String attributeProperty); diff --git a/agent/src/main/java/com/intergral/deep/agent/Agent.java b/agent/src/main/java/com/intergral/deep/agent/Agent.java index 10230ab..2c3283a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/Agent.java +++ b/agent/src/main/java/com/intergral/deep/agent/Agent.java @@ -27,7 +27,10 @@ import java.util.Map; import java.util.jar.JarFile; -public class Agent { +public final class Agent { + + private Agent() { + } /** * This is called when the agent is dynamically attached to the VM @@ -81,12 +84,13 @@ private static void startNv(final Map args, final Instrumentatio System.err.println( "ERROR: Failed to start deep agent: " + t.getClass().getName() + " " + t.getMessage()); System.err.println("----------------------------------------------------------"); + //noinspection CallToPrintStackTrace t.printStackTrace(); System.err.println("----------------------------------------------------------"); } } - protected static Map parseArgs(final String args) { + static Map parseArgs(final String args) { if (args == null) { return new HashMap<>(); } @@ -107,7 +111,7 @@ protected static Map parseArgs(final String args) { return arguments; } - protected static List splitArgs(final String args) { + private static List splitArgs(final String args) { final List rtn = new ArrayList<>(); boolean escaped = false; diff --git a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java index 390fc85..5bd52c4 100644 --- a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java +++ b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java @@ -30,6 +30,9 @@ @SuppressWarnings("unused") public class AgentImpl { + private AgentImpl() { + } + private static final CountDownLatch LATCH = new CountDownLatch(1); private static DeepAgent deepAgent; diff --git a/agent/src/main/java/com/intergral/deep/agent/IDUtils.java b/agent/src/main/java/com/intergral/deep/agent/IDUtils.java index 22be2ef..ff20a56 100644 --- a/agent/src/main/java/com/intergral/deep/agent/IDUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/IDUtils.java @@ -7,7 +7,10 @@ import java.util.concurrent.ThreadLocalRandom; -public class IDUtils { +public final class IDUtils { + + private IDUtils() { + } static final int BYTE_BASE16 = 2; private static final String ALPHABET = "0123456789abcdef"; diff --git a/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java b/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java index 7a85e35..58de829 100644 --- a/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java @@ -24,7 +24,10 @@ import java.util.Iterator; import java.util.Set; -public class ReflectionUtils { +public final class ReflectionUtils { + + private ReflectionUtils() { + } private static final IReflection reflection; diff --git a/agent/src/main/java/com/intergral/deep/agent/Utils.java b/agent/src/main/java/com/intergral/deep/agent/Utils.java index 3dad02d..b5fab74 100644 --- a/agent/src/main/java/com/intergral/deep/agent/Utils.java +++ b/agent/src/main/java/com/intergral/deep/agent/Utils.java @@ -18,11 +18,13 @@ package com.intergral.deep.agent; import java.time.Instant; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -public class Utils { +public final class Utils { + + private Utils() { + } /** * Get the current version of Java running in this JVM diff --git a/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java b/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java index 175b38d..ef94398 100644 --- a/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java +++ b/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java @@ -26,7 +26,10 @@ import java.util.logging.SimpleFormatter; import org.slf4j.LoggerFactory; -public class Logger { +public final class Logger { + + private Logger() { + } public static void configureLogging(final Settings settings) { final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("com.intergral"); diff --git a/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java b/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java index be90fea..5c364f4 100644 --- a/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java +++ b/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java @@ -27,7 +27,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class PluginLoader { +public final class PluginLoader { + + private PluginLoader() { + } private static final Logger LOGGER = LoggerFactory.getLogger(PluginLoader.class); diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushService.java b/agent/src/main/java/com/intergral/deep/agent/push/PushService.java index 525a265..e7958e9 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushService.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushService.java @@ -17,8 +17,8 @@ package com.intergral.deep.agent.push; -import com.intergral.deep.agent.api.plugin.ISnapshotContext; import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.grpc.GrpcService; import com.intergral.deep.agent.settings.Settings; @@ -45,22 +45,7 @@ public PushService(final Settings settings, final GrpcService grpcService) { public void pushSnapshot(final EventSnapshot snapshot, final ISnapshotContext context) { decorate(snapshot, context); final Snapshot grpcSnapshot = PushUtils.convertToGrpc(snapshot); - this.grpcService.snapshotService().send(grpcSnapshot, new StreamObserver() { - @Override - public void onNext(final SnapshotResponse value) { - LOGGER.debug("Sent snapshot: {}", snapshot.getID()); - } - - @Override - public void onError(final Throwable t) { - LOGGER.error("Error sending snapshot: {}", snapshot.getID(), t); - } - - @Override - public void onCompleted() { - - } - }); + this.grpcService.snapshotService().send(grpcSnapshot, new LoggingObserver(snapshot.getID())); } private void decorate(final EventSnapshot snapshot, final ISnapshotContext context) { @@ -77,4 +62,29 @@ private void decorate(final EventSnapshot snapshot, final ISnapshotContext conte } snapshot.close(); } + + static class LoggingObserver implements StreamObserver { + + private final String id; + + public LoggingObserver(final String id) { + this.id = id; + } + + @Override + public void onNext(final SnapshotResponse value) { + LOGGER.debug("Sent snapshot: {}", id); + + } + + @Override + public void onError(final Throwable t) { + LOGGER.error("Error sending snapshot: {}", id, t); + } + + @Override + public void onCompleted() { + + } + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java index 1cfa6c0..40d8f32 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java @@ -35,7 +35,10 @@ import java.util.Map; import java.util.stream.Collectors; -public class PushUtils { +public final class PushUtils { + + private PushUtils() { + } public static Snapshot convertToGrpc(final EventSnapshot snapshot) { return Snapshot.newBuilder() diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java index 4754667..f9d4ee3 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java @@ -20,7 +20,10 @@ import java.util.Map; import java.util.Set; -public class ResourceDetector { +public final class ResourceDetector { + + private ResourceDetector() { + } // Visible for testing static final String ATTRIBUTE_PROPERTY = "deep.resource.attributes"; diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java b/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java index e632d86..49a1217 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java @@ -11,7 +11,10 @@ import java.util.List; import java.util.ServiceLoader; -public class SpiUtil { +public final class SpiUtil { + + private SpiUtil() { + } static List loadOrdered(Class spiClass, ClassLoader serviceClassLoader) { diff --git a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java index 679ad8b..7190415 100644 --- a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java +++ b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java @@ -79,6 +79,7 @@ static Settings build(final Map agentArgs, final InputStream str properties.load(resourceAsStream); } catch (IOException e) { // logging is not initialized until after the settings class + //noinspection CallToPrintStackTrace e.printStackTrace(); } @@ -219,13 +220,13 @@ public Map getMap(String key) { public String getServiceHost() { final String serviceUrl = getSettingAs(ISettings.KEY_SERVICE_URL, String.class); - if (serviceUrl.contains("://")) { + if (serviceUrl != null && serviceUrl.contains("://")) { try { return new URL(serviceUrl).getHost(); } catch (MalformedURLException e) { throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl, e); } - } else if (serviceUrl.contains(":")) { + } else if (serviceUrl != null && serviceUrl.contains(":")) { return serviceUrl.split(":")[0]; } @@ -234,13 +235,13 @@ public String getServiceHost() { public int getServicePort() { final String serviceUrl = getSettingAs(ISettings.KEY_SERVICE_URL, String.class); - if (serviceUrl.contains("://")) { + if (serviceUrl != null && serviceUrl.contains("://")) { try { return new URL(serviceUrl).getPort(); } catch (MalformedURLException e) { throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl, e); } - } else if (serviceUrl.contains(":")) { + } else if (serviceUrl != null && serviceUrl.contains(":")) { return Integer.parseInt(serviceUrl.split(":")[1]); } @@ -283,7 +284,7 @@ public void addPlugin(final IPlugin plugin) { } public void removePlugin(final IPlugin plugin) { - this.plugins.removeIf(iPlugin -> iPlugin.name().equals(plugin.name())); + this.customPlugins.removeIf(iPlugin -> iPlugin.name().equals(plugin.name())); } @Override diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java index 87d953e..23c876d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java @@ -21,7 +21,10 @@ import com.intergral.deep.agent.tracepoint.inst.InstUtils; import com.intergral.deep.agent.types.TracePointConfig; -public class TracepointUtils { +public final class TracepointUtils { + + private TracepointUtils() { + } /** * We normally get set the source file name, we need to convert this to a Java class name. diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java index 3834f4f..90a9af6 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java @@ -18,7 +18,8 @@ package com.intergral.deep.agent.tracepoint.cf; import com.intergral.deep.agent.api.plugin.AbstractEvaluator; -import java.lang.reflect.InvocationTargetException; +import com.intergral.deep.agent.api.plugin.IEvaluator; +import com.intergral.deep.agent.api.plugin.LazyEvaluator.IEvaluatorLoader; import java.lang.reflect.Method; import java.util.Map; import org.slf4j.Logger; @@ -42,9 +43,24 @@ public CFEvaluator(final Object page, final Method evaluate) { public Object evaluateExpression(final String expression, final Map values) { try { return evaluate.invoke(page, expression); - } catch (IllegalAccessException | InvocationTargetException e) { + } catch (Throwable e) { LOGGER.debug("Unable to evaluate expression {}", expression); } return null; } + + public static class Loader implements IEvaluatorLoader { + + private final Map variables; + + public Loader(final Map variables) { + + this.variables = variables; + } + + @Override + public IEvaluator load() { + return CFUtils.findCfEval(variables); + } + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java index 6f85202..82444a4 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java @@ -56,7 +56,7 @@ protected boolean isAppFrame(final StackTraceElement stackTraceElement) { @Override protected Map selectVariables(final int frameIndex) { - if (frameIndex != 0 || getTracepointConfig("cf.raw", Boolean.class, false)) { + if (frameIndex != 0 || frameConfig.isCfRaw()) { return super.selectVariables(frameIndex); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java index f000bc7..96efedb 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.Set; -public class CFUtils { +public final class CFUtils { private CFUtils() { } @@ -102,13 +102,6 @@ static boolean isScope(final Object varScope) { } - static boolean isLuceeScope(final Object varScope) { - return varScope instanceof Map - && varScope.getClass().getName().startsWith("lucee") - && varScope.getClass().getName().contains("scope"); - } - - public static Object findPage(final Map localVars) { final Object aThis = localVars.get("this"); if (aThis == null) { @@ -190,7 +183,8 @@ public static Set loadCfBreakpoints(final URL location, return iBreakpoints; } - public static Set loadCfBreakpoints(final String location, + public static Set loadCfBreakpoints( + final String location, final Map values) { if (location == null) { return Collections.emptySet(); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java index 8277cd0..f1e4812 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java @@ -22,7 +22,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class EvaluatorService { +public final class EvaluatorService { + + private EvaluatorService() { + } private static final Logger LOGGER = LoggerFactory.getLogger(EvaluatorService.class); private static final Exception NO_EVALUATOR_EXCEPTION = new RuntimeException( diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java index 66ad4e3..4b49f00 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java @@ -99,13 +99,13 @@ public Object evaluateExpression(final String expression, final Map> CALLBACKS = new ThreadLocal>() @@ -90,7 +93,7 @@ public static void callBackCF(final List bpIds, final int lineNo, final Map variables) { try { - final IEvaluator evaluator = new LazyEvaluator(() -> CFUtils.findCfEval(variables)); + final IEvaluator evaluator = new LazyEvaluator(new CFEvaluator.Loader(variables)); commonCallback(bpIds, filename, lineNo, variables, evaluator, CFFrameProcessor::new); } catch (Throwable t) { LOGGER.debug("Unable to process tracepoint {}:{}", filename, lineNo, t); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java index 976d18a..f121e8f 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java @@ -20,6 +20,7 @@ import com.intergral.deep.agent.Utils; import com.intergral.deep.agent.api.plugin.IEvaluator; import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; import com.intergral.deep.agent.settings.Settings; import com.intergral.deep.agent.tracepoint.handler.bfs.Node; import com.intergral.deep.agent.tracepoint.inst.InstUtils; @@ -41,27 +42,67 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * This type allows the collection of frame data, ie stack frames, watchers and other tracepoint data. + */ public class FrameCollector extends VariableProcessor { + /** + * The current settings use my deep + */ protected final Settings settings; + + /** + * The evaluator that should be used by this callback + */ protected final IEvaluator evaluator; + + /** + * The variables that have been captured by the callback + */ protected final Map variables; + + /** + * The stack trace elements captured by this callback + */ private final StackTraceElement[] stack; + /** + * We cache this value in the constructor to reduce look up later. + */ private final String jspSuffix; + + /** + * We cache this value in the constructor to reduce look up later. + */ private final List jspPackages; - public FrameCollector(final Settings settings, final IEvaluator evaluator, + /** + * Create a frame collector to collect the frame data + * + * @param settings the current settings being used by deep + * @param evaluator the evaluator to use for this callback + * @param variables the variables captured by the callback + * @param stack the stack captured by the callback + */ + public FrameCollector( + final Settings settings, + final IEvaluator evaluator, final Map variables, final StackTraceElement[] stack) { this.settings = settings; this.evaluator = evaluator; this.variables = variables; this.stack = stack; - this.jspSuffix = settings.getSettingAs("jsp.suffix", String.class); - this.jspPackages = settings.getAsList("jsp.packages"); + this.jspSuffix = settings.getSettingAs(ISettings.JSP_SUFFIX, String.class); + this.jspPackages = settings.getAsList(ISettings.JSP_PACKAGES); } + /** + * Processes and collects all the data for the captured frame. + * + * @return the result of the process {@link IFrameResult} + */ protected IFrameResult processFrame() { final ArrayList frames = new ArrayList<>(); @@ -90,13 +131,24 @@ public Map variables() { }; } - private StackFrame processFrame(final StackTraceElement stackTraceElement, + /** + * Process an individual stack frame into a {@link StackFrame} including the variables, if this frame should collect variables. + * + * @param stackTraceElement the stack frame to collect + * @param collectVars {@code true} if we should collect variables + * @param frameIndex the index of the frame we should collect, index {@code 0} indicates the highest frame (e.g. current executing + * frame) + * @return the result of the frame as {@link StackFrame} + */ + private StackFrame processFrame( + final StackTraceElement stackTraceElement, final boolean collectVars, final int frameIndex) { - final String className = stackTraceElement.getClassName(); + final String className = stackTraceElement.getClassName(); final boolean nativeMethod = stackTraceElement.isNativeMethod(); final boolean appFrame = isAppFrame(stackTraceElement); + // we use a method to get the method name, so we can override it in CF (as CF uses different method names) final String methodName = getMethodName(stackTraceElement, variables, frameIndex); final Collection varIds; @@ -105,6 +157,7 @@ private StackFrame processFrame(final StackTraceElement stackTraceElement, } else { varIds = Collections.emptyList(); } + // map the file name - this deals with transpiled jsp source files final FileNameMapping mapping = getFileNameMapping(stackTraceElement, className); return new StackFrame(mapping.fileName, @@ -118,6 +171,18 @@ private StackFrame processFrame(final StackTraceElement stackTraceElement, mapping.transpiledLine); } + /** + * We need to be careful when selecting the file name as it is not always available. + *

    + *
  • native frames do not have file names, or line numbers
  • + *
  • JSP classes can be mad up of multiple source files so we need to use the {@link SourceMap}
  • + *
  • it is possible to not include source names when compiling Java classes
  • + *
+ * + * @param stackTraceElement the stack frame element + * @param className the classname + * @return the mapped file name and line numbers + */ private FileNameMapping getFileNameMapping(final StackTraceElement stackTraceElement, final String className) { if (!JSPUtils.isJspClass(jspSuffix, jspPackages, className)) { @@ -129,6 +194,9 @@ private FileNameMapping getFileNameMapping(final StackTraceElement stackTraceEle forName = Class.forName(className); } catch (ClassNotFoundException ignored) { // not possible + // we are literally executing the code, so the class has to be loaded + // in theory this could happen with some complex class loaders, but it shouldn't + // if it does there is nothing we can do anyway } if (forName == null) { @@ -148,21 +216,13 @@ private FileNameMapping getFileNameMapping(final StackTraceElement stackTraceEle return new FileNameMapping(jspFilename, jspLine, transpiledFile, transpiledLine); } - private static class FileNameMapping { - - public final String fileName; - public final int lineNumber; - private final String transpiledFile; - private final int transpiledLine; - - public FileNameMapping(final String fileName, final int lineNumber, final String transpiledFile, final int transpiledLine) { - this.fileName = fileName; - this.lineNumber = lineNumber; - this.transpiledFile = transpiledFile; - this.transpiledLine = transpiledLine; - } - } - + /** + * Select from the available captured variables the variables we want to process for this frame. This is mainly here to allow for an easy + * way for CF to map the variables from their capture Java types to the expected CF types. + * + * @param frameIndex the index of the frame we are processing + * @return the variables available at this frame + */ protected Map selectVariables(final int frameIndex) { if (frameIndex == 0) { return this.variables; @@ -171,6 +231,15 @@ protected Map selectVariables(final int frameIndex) { return Collections.emptyMap(); } + /** + * This is where we start the Breadth first search (BFS) of the selected variables. + *

+ * Here we are essentially dealing with the BFS nodes and linking to the VariableProcessor to do the processing. + * + * @param variables the variables to process + * @return the variable ref used in the snapshot + * @see VariableProcessor + */ protected List processVars(final Map variables) { final List frameVars = new ArrayList<>(); @@ -187,6 +256,14 @@ protected List processVars(final Map variables) { return frameVars; } + /** + * This is where we take a node from BFS queue and process it back onto the queue. + *

+ * Essentially, we take a node, we process this node, the add the child nodes back onto the BFS queue. + * + * @param node the node to process + * @return {@code true} if we should continue to process more nodes, else {@code false}. + */ protected boolean processNode(final Node node) { if (!this.checkVarCount()) { // we have exceeded the var count, so do not continue @@ -214,9 +291,17 @@ protected boolean processNode(final Node node) { return true; } + /** + * An app frame is defined via the settings {@link ISettings#APP_FRAMES_INCLUDES} and {@link ISettings#APP_FRAMES_EXCLUDES}. This gives a + * way to tell deep that the frame is part of your app and not part of the framework. This is primarily used as a way to filter frames in + * the UI. + * + * @param stackTraceElement the stack frame to process + * @return {@code true} if the class name is in the included packages, and not in the excluded packages, else {@code false}. + */ protected boolean isAppFrame(final StackTraceElement stackTraceElement) { - final List inAppInclude = settings.getAsList("in.app.include"); - final List inAppExclude = settings.getAsList("in.app.exclude"); + final List inAppInclude = settings.getAsList(ISettings.APP_FRAMES_INCLUDES); + final List inAppExclude = settings.getAsList(ISettings.APP_FRAMES_EXCLUDES); final String className = stackTraceElement.getClassName(); @@ -234,6 +319,13 @@ protected boolean isAppFrame(final StackTraceElement stackTraceElement) { return false; } + /** + * Get the file name from the stack trace element, as we always need a file name. If there is not a file name then use the short class + * name. + * + * @param stackTraceElement the frame to process + * @return the file name, or class name + */ private String getFileName(final StackTraceElement stackTraceElement) { if (stackTraceElement.getFileName() == null) { return InstUtils.shortClassName(stackTraceElement.getClassName()); @@ -241,12 +333,31 @@ private String getFileName(final StackTraceElement stackTraceElement) { return Utils.trimPrefix(stackTraceElement.getFileName(), "/"); } + /** + * Get the method name from the stack frame + * + * @param stackTraceElement the stack frame to process + * @param variables the variables for the frame + * @param frameIndex the frame index + * @return the name of the method + */ protected String getMethodName(final StackTraceElement stackTraceElement, final Map variables, final int frameIndex) { return stackTraceElement.getMethodName(); } + /** + * Evaluate a watch expression into a {@link IExpressionResult}. + *

+ * We always need a result from a watch expression, however, it is possible to have bad watches that error. In some cases it is possible + * to not have a valid evaluator. + *

+ * So if we cannot get a result from the {@link IEvaluator} then we return an error result. + * + * @param watch the watch expression to evaluate + * @return a {@link IExpressionResult} + */ protected IExpressionResult evaluateWatchExpression(final String watch) { try { final Object result = this.evaluator.evaluateExpression(watch, this.variables); @@ -279,6 +390,23 @@ public Map variables() { } } + /** + * Using the current tracepoint config, create a {@link Resource} the can be used as the attributes. + *

+ * The basic attributes created are: + *

    + *
  • tracepoint - the id of the tracepoint
  • + *
  • path - the path of the tracepoint
  • + *
  • line - the line of the tracepoint
  • + *
  • stack - the stack type of the tracepoint
  • + *
  • frame - the frame type of the tracepoint
  • + *
  • has_watchers - boolean indicating there are watchers
  • + *
  • has_condition - boolean indicating there is a condition
  • + *
+ * + * @param tracepoint the current config + * @return the new {@link Resource} + */ protected Resource processAttributes(final TracePointConfig tracepoint) { final HashMap attributes = new HashMap<>(); attributes.put("tracepoint", tracepoint.getId()); @@ -298,6 +426,11 @@ protected Resource processAttributes(final TracePointConfig tracepoint) { return Resource.create(attributes); } + /** + * The result of processing the frames. + * + * @see #processFrame() + */ protected interface IFrameResult { Collection frames(); @@ -305,10 +438,56 @@ protected interface IFrameResult { Map variables(); } + /** + * The result of evaluating an expression. + * + * @see #evaluateWatchExpression(String) + */ protected interface IExpressionResult { WatchResult result(); Map variables(); } + + /** + * A small type to wrap the mapped file and line numbers into an easy return. + */ + private static class FileNameMapping { + + /** + * Should always have a value. Will be the source file name if available, or the class name if not. + *

+ * If the class being processed is a transpiled type, e.g. JSP then this will be the original source file. + */ + private final String fileName; + /** + * Can be negative. + *

+ * If the class being processed is a transpiled type, e.g. JSP then this will be the original source file line number. + * + * @see StackTraceElement#getLineNumber() + */ + private final int lineNumber; + + /** + * If the class being processed is a transpiled type, e.g. JSP then this will be the filename before it is mapped. + */ + private final String transpiledFile; + /** + * Can be negative. + *

+ * If the class being processed is a transpiled type, e.g. JSP then this will be the linenumber before it is mapped. + * + * @see StackTraceElement#getLineNumber() + */ + private final int transpiledLine; + + public FileNameMapping(final String fileName, final int lineNumber, final String transpiledFile, final int transpiledLine) { + this.fileName = fileName; + this.lineNumber = lineNumber; + this.transpiledFile = transpiledFile; + this.transpiledLine = transpiledLine; + } + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java index 966af39..a7a26f9 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java @@ -37,6 +37,8 @@ public class FrameConfig { private int maxWatchVars = -1; private int maxTpProcessTime = -1; + private boolean cfRaw = false; + public void process(final TracePointConfig tracePointConfig) { maxVarDepth = Math.max(tracePointConfig.getArg("MAX_VAR_DEPTH", Integer.class, -1), maxVarDepth); @@ -69,6 +71,11 @@ public void process(final TracePointConfig tracePointConfig) { this.stackType = stackType; } } + + final Boolean cfRaw = tracePointConfig.getArg("cf.raw", Boolean.class, null); + if (cfRaw != null && cfRaw) { + this.cfRaw = true; + } } public void close() { @@ -110,4 +117,8 @@ public int maxDepth() { public int maxCollectionSize() { return this.maxCollectionSize; } + + public boolean isCfRaw() { + return this.cfRaw; + } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java index 1fd19ca..23d3fa3 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java @@ -30,13 +30,37 @@ import java.util.Map; import java.util.stream.Collectors; +/** + * This type deals with matching tracepoints to the current state and working out if we can collect the data. + */ public class FrameProcessor extends FrameCollector implements ISnapshotContext { + /** + * The tracepoints that have been triggered by the Callback + */ private final Collection tracePointConfigs; + /** + * The time as a tuple when this line Callback started. + * + * @see Utils#currentTimeNanos() + */ private final long[] lineStart; + + /** + * These are the tracepoints that are filtered based on state and conditions to be valid to trigger collection. + */ private Collection filteredTracepoints; - private TracePointConfig currentTracePointConfig; + /** + * Create a new processor for this Callback + * + * @param settings the current settings being used + * @param evaluator the evaluator to use for watchers and conditions + * @param variables the variables we have at this state + * @param tracePointConfigs the tracepoints that are part of this Callback + * @param lineStart the Tuple of the time this Callback started + * @param stack the stack trace to use + */ public FrameProcessor(final Settings settings, final IEvaluator evaluator, final Map variables, @@ -47,6 +71,14 @@ public FrameProcessor(final Settings settings, this.lineStart = lineStart; } + /** + * Using the {@link #tracePointConfigs} can we collect any data at this point. + *

+ * This method will check the tracepoint config fire count, rate limits, windows and conditions and populate the + * {@link #filteredTracepoints} + * + * @return {@code true}, if the {@link #filteredTracepoints} have any values + */ public boolean canCollect() { this.filteredTracepoints = this.tracePointConfigs.stream() .filter(tracePointConfig -> tracePointConfig.canFire(this.lineStart[0]) @@ -56,6 +88,13 @@ public boolean canCollect() { return !this.filteredTracepoints.isEmpty(); } + /** + * Process the tracepoints condition with the evaluator to see if the condition is {@code true} + * + * @param tracePointConfig the config to check + * @return {@code false} if the condition on the tracepoint evaluates to a false + * @see IEvaluator#evaluate(String, Map) + */ private boolean conditionPasses(final TracePointConfig tracePointConfig) { final String condition = tracePointConfig.getCondition(); if (condition == null || condition.trim().isEmpty()) { @@ -65,17 +104,27 @@ private boolean conditionPasses(final TracePointConfig tracePointConfig) { return this.evaluator.evaluate(condition, variables); } + /** + * Using the {@link #filteredTracepoints} update the config to reflect the collection config for this Callback. + *

+ * If there are multiple tracepoints being process, the config will reflect the most inclusive, ie the higher number. + */ public void configureSelf() { configureSelf(this.filteredTracepoints); } + /** + * Collect the data into {@link EventSnapshot}. + * + * @return the collected {@link EventSnapshot} + */ public Collection collect() { final Collection snapshots = new ArrayList<>(); final IFrameResult processedFrame = super.processFrame(); for (final TracePointConfig tracepoint : filteredTracepoints) { - try (AutoCloseable ignored = withTracepoint(tracepoint)) { + try { final EventSnapshot snapshot = new EventSnapshot(tracepoint, this.lineStart[1], this.settings.getResource(), @@ -100,18 +149,9 @@ public Collection collect() { return snapshots; } - private AutoCloseable withTracepoint(final TracePointConfig tracepoint) { - this.currentTracePointConfig = tracepoint; - return () -> currentTracePointConfig = null; - } - - protected T getTracepointConfig(final String key, final Class clazz, T def) { - if (currentTracePointConfig == null) { - return def; - } - return currentTracePointConfig.getArg(key, clazz, def); - } - + /** + * {@inheritDoc} + */ @Override public String evaluateExpression(final String expression) throws EvaluationException { try { @@ -122,8 +162,24 @@ public String evaluateExpression(final String expression) throws EvaluationExcep } } + /** + * This defines a functional interface to allow for creating difference processors in the Callback + */ public interface IFactory { + /** + * Create a new processor + * + * @param settings the current settings being used + * @param evaluator the evaluator to use for watchers and conditions + * @param variables the variables we have at this state + * @param tracePointConfigs the tracepoints that are part of this Callback + * @param lineStart the Tuple of the time this Callback started + * @param stack the stack trace to use + * @return the new {@link FrameProcessor} + * @see FrameProcessor + * @see com.intergral.deep.agent.tracepoint.cf.CFFrameProcessor + */ FrameProcessor provide(Settings settings, IEvaluator evaluator, Map variables, Collection tracePointConfigs, long[] lineStart, StackTraceElement[] stack); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java index 3c9e1d1..00c6b66 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java @@ -51,11 +51,11 @@ *

* While processing a variable or frame, we will process using a Breadth first approach. These means given the tree: *

- *   1 -> 1.1
+ *   1 -> 1.1
  *        1.2
- *        1.3 -> 1.3.1
- *   2 -> 2.1
- *   3 -> 3.1 -> 3.1.1
+ *        1.3 -> 1.3.1
+ *   2 -> 2.1
+ *   3 -> 3.1 -> 3.1.1
  * 
* We will attempt to gather the variables in the order: *
    @@ -103,6 +103,10 @@ public abstract class VariableProcessor { NO_CHILDREN_TYPES.add(Iterator.class); } + /** + * Some config values from the triggered tracepoints affect all tracepoints at the point of collection. This {@link FrameConfig} + * calculates the most encompassing config for all triggered tracepoints. + */ protected final FrameConfig frameConfig = new FrameConfig(); /** * This is the cache we use while building this lookup, this cache essentially maps {@link System#identityHashCode(Object)} to an internal diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java index 33321ea..96f44cd 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java @@ -17,7 +17,10 @@ package com.intergral.deep.agent.tracepoint.inst; -public class InstUtils { +public final class InstUtils { + + private InstUtils() { + } public static String externalClassName(final String className) { return className.replaceAll("/", "."); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java index 2962d19..5f7eb0e 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java @@ -131,15 +131,15 @@ static ClassInfo loadClassInfo(final String type, final ClassLoader loader, fina return null; } - private static boolean isRailoClassLoader(final ClassLoader loader) { + static boolean isRailoClassLoader(final ClassLoader loader) { return loader.getClass().getName().equals(RAILO_LOADER); } - private static boolean isLuceeClassLoader(final ClassLoader loader) { + static boolean isLuceeClassLoader(final ClassLoader loader) { return loader.getClass().getName().equals(LUCEE_LOADER); } - private static boolean isSafeLoader(final ClassLoader loader) { + static boolean isSafeLoader(final ClassLoader loader) { final String name = loader.getClass().getName(); return SAFE_LOADERS.contains(name); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java index 62bc624..d3ff9fe 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java @@ -17,8 +17,6 @@ package com.intergral.deep.agent.tracepoint.inst.asm; -import static org.objectweb.asm.Opcodes.ACC_ABSTRACT; - import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -26,7 +24,10 @@ import java.util.Collections; import java.util.List; -public class TransformerUtils { +public final class TransformerUtils { + + private TransformerUtils() { + } private static final List EXCLUDE_PACKAGES = Collections.emptyList(); private static final List EXCLUDE_CONTAINS = Collections.emptyList(); @@ -110,9 +111,4 @@ public static boolean isExcludedClass(final String classname) { return false; } - - - public static boolean isAbstract(final int access) { - return (access & ACC_ABSTRACT) == ACC_ABSTRACT; - } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java index ff56405..ba0d372 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java @@ -149,11 +149,6 @@ public boolean wasChanged() { } - public String getFilename() { - return filename; - } - - @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java index 2419e4b..7e86b0a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtils.java @@ -28,9 +28,12 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class JSPUtils { + private final static Logger LOGGER = LoggerFactory.getLogger(JSPUtils.class); private JSPUtils() { } @@ -45,7 +48,7 @@ public static boolean isJspClass(final String jspSuffix, private static String getJspClassname(final String jspSuffix, final List jspPackages, final String className) { - if (jspSuffix.isEmpty() || className.endsWith(jspSuffix)) { + if (jspSuffix == null || jspSuffix.isEmpty() || className.endsWith(jspSuffix)) { for (final String jspPackage : jspPackages) { if (className.startsWith(jspPackage)) { return className.substring(jspPackage.length() + 1); @@ -69,7 +72,7 @@ public static SourceMap getSourceMap(final Class c) { } return null; } catch (IOException ioe) { - ioe.printStackTrace(); + LOGGER.error("Failed to load source map", ioe); return null; } } @@ -81,7 +84,7 @@ public static SourceMap getSourceMap(byte[] classfileBuffer) { final SourceMapParser parser = new SourceMapParser(rtn); return parser.parse(); } catch (IOException ioe) { - ioe.printStackTrace(); + LOGGER.error("Failed to load source map", ioe); return null; } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java index d3203ab..b24ad6c 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java @@ -32,7 +32,7 @@ * @author nwightma * @since 1.0.2 */ -public class SmapUtils { +public final class SmapUtils { private SmapUtils() { } @@ -92,7 +92,7 @@ public static String parseStream(final InputStream in) throws IOException { } - protected static String scan(ClassReader reader) { + private static String scan(ClassReader reader) { final Visitor v = new Visitor(); reader.accept(v, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); return v.getDebug(); diff --git a/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java b/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java index 16e6996..4d28722 100644 --- a/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java +++ b/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java @@ -58,7 +58,7 @@ public class TracePointConfig { /** * The condition that has to be 'truthy' for this tracepoint to fire */ - private static final String CONDITION = "condition"; + public static final String CONDITION = "condition"; /** * This is the key to indicate the frame collection type */ diff --git a/agent/src/test/java/com/intergral/deep/agent/AgentImplTest.java b/agent/src/test/java/com/intergral/deep/agent/AgentImplTest.java new file mode 100644 index 0000000..efc22a1 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/AgentImplTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.times; + +import com.intergral.deep.agent.api.hook.IDeepHook; +import com.intergral.deep.agent.tracepoint.handler.Callback; +import java.lang.instrument.Instrumentation; +import java.util.HashMap; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +class AgentImplTest { + + @Test + void awaitLatch() throws InterruptedException { + try (MockedStatic callback = Mockito.mockStatic(Callback.class, "init")) { + assertThrows(IllegalStateException.class, AgentImpl::loadDeepAPI); + AgentImpl.startup(Mockito.mock(Instrumentation.class), new HashMap<>()); + final Object o = AgentImpl.awaitLoadAPI(); + assertNotNull(o); + + final IDeepHook deepHook = (IDeepHook) o; + + assertNotNull(deepHook.deepService()); + assertNotNull(deepHook.reflectionService()); + + callback.verify(() -> Callback.init(Mockito.any(), Mockito.any(), Mockito.any()), times(1)); + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java b/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java index 7c84bba..ddbf078 100644 --- a/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/DeepAgentTest.java @@ -17,11 +17,16 @@ package com.intergral.deep.agent; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.times; +import com.intergral.deep.agent.api.DeepVersion; +import com.intergral.deep.agent.api.plugin.IPlugin; import com.intergral.deep.agent.api.plugin.IPlugin.IPluginRegistration; +import com.intergral.deep.agent.api.tracepoint.ITracepoint; import com.intergral.deep.agent.api.tracepoint.ITracepoint.ITracepointRegistration; import com.intergral.deep.agent.settings.Settings; import com.intergral.deep.agent.tracepoint.handler.Callback; @@ -58,6 +63,9 @@ void start_shouldSetPluginsAndResource() { void registerPlugin() { final IPluginRegistration iPluginRegistration = deepAgent.registerPlugin((settings, snapshot) -> null); + assertNotNull(iPluginRegistration.get()); + assertFalse(iPluginRegistration.isAuthProvider()); + Mockito.verify(settings, times(1)).addPlugin(Mockito.any()); iPluginRegistration.unregister(); @@ -69,6 +77,13 @@ void registerPlugin() { void registerTracepoint() { final ITracepointRegistration iTracepointRegistration = deepAgent.registerTracepoint("some/path", 123); + final ITracepoint iTracepoint = iTracepointRegistration.get(); + assertEquals("some/path", iTracepoint.path()); + assertEquals(123, iTracepoint.line()); + assertNotNull(iTracepoint.id()); + assertNotNull(iTracepoint.watches()); + assertNotNull(iTracepoint.args()); + iTracepointRegistration.unregister(); } @@ -82,4 +97,9 @@ void isEnabled() { assertTrue(deepAgent.isEnabled()); } + + @Test + void getVersion() { + assertEquals(DeepVersion.VERSION, deepAgent.getVersion()); + } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java index 9a8e9ee..cb3c10d 100644 --- a/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/UtilsTest.java @@ -74,11 +74,13 @@ public String toString() { assertNotNull(valueOf); assertTrue(valueOf.startsWith(badtostring.class.getName())); assertTrue(valueOf.endsWith("toString() failed")); + } @Test void trim() { assertEquals("value", Utils.trimPrefix("//value", "/")); + assertEquals("test", Utils.trimPrefix(".....test", ".")); } @Test @@ -88,4 +90,17 @@ void truncate() { assertFalse(Utils.truncate("somelongstring", 20).truncated()); assertEquals("somelongstring", Utils.truncate("somelongstring", 20).value()); } + + + @Test + void getVersion() { + assertEquals(7, Utils.extractVersion("1.7")); + assertEquals(8, Utils.extractVersion("1.8")); + assertEquals(9, Utils.extractVersion("1.9")); + assertEquals(10, Utils.extractVersion("10.1")); + assertEquals(11, Utils.extractVersion("11.2")); + assertEquals(12, Utils.extractVersion("12.4")); + assertEquals(13, Utils.extractVersion("13.5")); + assertEquals(14, Utils.extractVersion("14.5")); + } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java b/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java index 8c27177..d3d2f00 100644 --- a/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/plugins/PluginLoaderTest.java @@ -18,6 +18,7 @@ package com.intergral.deep.agent.plugins; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import com.intergral.deep.agent.ReflectionUtils; import com.intergral.deep.agent.api.plugin.IPlugin; diff --git a/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java index e6bf381..010a8a6 100644 --- a/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/push/PushServiceTest.java @@ -17,6 +17,7 @@ package com.intergral.deep.agent.push; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.ArgumentMatchers.any; @@ -28,6 +29,7 @@ import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.api.settings.ISettings; import com.intergral.deep.agent.grpc.GrpcService; +import com.intergral.deep.agent.push.PushService.LoggingObserver; import com.intergral.deep.agent.settings.Settings; import com.intergral.deep.proto.common.v1.KeyValue; import com.intergral.deep.proto.tracepoint.v1.Snapshot; @@ -57,6 +59,14 @@ void setUp() { pushService = new PushService(settings, grpcService); } + @Test + void snapshotLogger() { + final LoggingObserver observer = new LoggingObserver("test id"); + assertDoesNotThrow(() -> observer.onError(new RuntimeException("test exception"))); + assertDoesNotThrow(() -> observer.onNext(null)); + assertDoesNotThrow(observer::onCompleted); + } + @Test void canPushSnapshot() { final ISnapshotContext context = Mockito.mock(ISnapshotContext.class); diff --git a/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java b/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java index 0dfb4c7..9ce7094 100644 --- a/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java @@ -17,9 +17,19 @@ package com.intergral.deep.agent.settings; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.api.settings.ISettings; +import com.intergral.deep.agent.settings.Settings.InvalidConfigException; +import java.net.MalformedURLException; import java.net.URL; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -62,4 +72,77 @@ void canHandleTypes() { assertEquals(Level.FINEST, settings.getSettingAs("debug_2", Level.class)); assertEquals(Level.FINE, settings.getSettingAs("level", Level.class)); } + + @Test + void canHandleURLs() { + { + final Settings settings = Settings.build(Collections.singletonMap(Settings.KEY_SERVICE_URL, "://bad-url")); + + final InvalidConfigException invalidConfigException = assertThrows(InvalidConfigException.class, settings::getServiceHost); + assertEquals(MalformedURLException.class, invalidConfigException.getCause().getClass()); + assertThrows(InvalidConfigException.class, settings::getServicePort); + } + { + final Settings settings = Settings.build(Collections.singletonMap(Settings.KEY_SERVICE_URL, "https://google.com")); + + assertEquals("google.com", settings.getServiceHost()); + assertEquals(-1, settings.getServicePort()); + } + { + final Settings settings = Settings.build(Collections.singletonMap(Settings.KEY_SERVICE_URL, "http://google.com:80")); + + assertEquals("google.com", settings.getServiceHost()); + assertEquals(80, settings.getServicePort()); + } + { + final Settings settings = Settings.build(Collections.singletonMap(Settings.KEY_SERVICE_URL, "google.com:443")); + + assertEquals("google.com", settings.getServiceHost()); + assertEquals(443, settings.getServicePort()); + } + { + final Settings settings = Settings.build(Collections.singletonMap(Settings.KEY_SERVICE_URL, "google.com:80")); + + assertEquals("google.com", settings.getServiceHost()); + assertEquals(80, settings.getServicePort()); + } + { + final Settings settings = Settings.build(Collections.singletonMap(Settings.KEY_SERVICE_URL, "google.com")); + + assertThrows(InvalidConfigException.class, settings::getServiceHost); + assertThrows(InvalidConfigException.class, settings::getServicePort); + } + } + + @Test + void plugins() { + + final Settings settings = Settings.build(new HashMap<>()); + + final IPlugin plugin = new IPlugin() { + @Override + public Resource decorate(final ISettings settings, final ISnapshotContext snapshot) { + return null; + } + }; + settings.addPlugin(plugin); + final IllegalStateException illegalStateException = assertThrows(IllegalStateException.class, () -> settings.addPlugin(plugin)); + assertEquals("Cannot add duplicate named (com.intergral.deep.agent.settings.SettingsTest$1) plugin", + illegalStateException.getMessage()); + + assertEquals(1, settings.getPlugins().size()); + settings.removePlugin(plugin); + + assertEquals(0, settings.getPlugins().size()); + } + + @Test + void setActive() { + + final Settings settings = Settings.build(new HashMap<>()); + + assertTrue(settings.isActive()); + settings.setActive(false); + assertFalse(settings.isActive()); + } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluatorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluatorTest.java new file mode 100644 index 0000000..39b7d39 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluatorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.cf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.intergral.deep.agent.api.plugin.IEvaluator; +import com.intergral.deep.agent.tracepoint.cf.CFEvaluator.Loader; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import lucee.runtime.PageContextImpl; +import lucee.runtime.PageImpl; +import org.junit.jupiter.api.Test; + +class CFEvaluatorTest { + + @Test + void coverage() throws NoSuchMethodException { + final CFEvaluator evaluate = new CFEvaluator(this, this.getClass().getDeclaredMethod("evaluate", String.class)); + assertEquals("input", evaluate.evaluateExpression("input", new HashMap<>())); + assertNull(evaluate.evaluateExpression("error", new HashMap<>())); + } + + @Test + void loader() { + final Map hashMap = new HashMap<>(); + hashMap.put("this", new PageImpl.SomePageImpl()); + hashMap.put("param0", new PageContextImpl()); + final Loader loader = new Loader(hashMap); + final IEvaluator load = loader.load(); + assertNotNull(load); + } + + public Object evaluate(String input) { + if (input.equals("error")) { + throw new RuntimeException("test exception"); + } + return input; + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFUtilsTest.java new file mode 100644 index 0000000..e88255e --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/cf/CFUtilsTest.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.cf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import coldfusion.runtime.ArgumentCollection; +import coldfusion.runtime.TestScope; +import coldfusion.runtime.UDFMethod; +import com.intergral.deep.agent.api.plugin.IEvaluator; +import com.intergral.deep.agent.types.TracePointConfig; +import com.intergral.deep.test.MockTracepointConfig; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import lucee.runtime.PageContextImpl; +import lucee.runtime.PageImpl; +import org.junit.jupiter.api.Test; + +class CFUtilsTest { + + @Test + void isCfClassFile() { + assertTrue(CFUtils.isCFFile("somefile.cfm")); + assertTrue(CFUtils.isCFFile("somefile.cfc")); + assertTrue(CFUtils.isCFFile("somefile.cfml")); + + assertFalse(CFUtils.isCFFile("somefile.cfml.java")); + assertFalse(CFUtils.isCFFile("somefile_cfml.java")); + assertFalse(CFUtils.isCFFile("somefile_cfc.java")); + assertFalse(CFUtils.isCFFile("somefile_cfm.java")); + } + + @Test + void isCfFile() { + assertTrue(CFUtils.isCFFile("something.cfc")); + assertTrue(CFUtils.isCFFile("something.cfm")); + assertTrue(CFUtils.isCFFile("something.cf")); + assertTrue(CFUtils.isCFFile("something.cfml")); + assertFalse(CFUtils.isCFFile("something.c")); + } + + + @Test + void findLuceeEval() throws Throwable { + final Map hashMap = new HashMap<>(); + hashMap.put("this", new PageImpl.SomePageImpl()); + hashMap.put("param0", new PageContextImpl()); + final IEvaluator cfEval = CFUtils.findCfEval(hashMap); + + assertNotNull(cfEval); + + final Object some_expression = cfEval.evaluateExpression("some Expression", hashMap); + assertEquals("some Expression", some_expression); + + } + + + @Test + void findCfEval() throws Throwable { + final Map hashMap = new HashMap<>(); + hashMap.put("this", new CFEvaluatorTarget()); + final IEvaluator cfEval = CFUtils.findCfEval(hashMap); + assertNotNull(cfEval); + + final Object test = cfEval.evaluateExpression("test", hashMap); + assertEquals(CFEvaluatorTarget.class.getName(), test.toString()); + } + + + @Test + void findCfEval2() throws Throwable { + final Map hashMap = new HashMap<>(); + hashMap.put("this", new CFEvaluatorTarget2()); + final IEvaluator cfEval = CFUtils.findCfEval(hashMap); + assertNotNull(cfEval); + + final Object test = cfEval.evaluateExpression("test", hashMap); + assertEquals(CFEvaluatorTarget2.class.getName(), test.toString()); + } + + + @Test + void findCfEval3() { + final Map hashMap = new HashMap<>(); + final IEvaluator cfEval = CFUtils.findCfEval(hashMap); + assertNull(cfEval); + } + + + @Test + void findCfEval4() { + final Map hashMap = new HashMap<>(); + hashMap.put("this", this); + final IEvaluator cfEval = CFUtils.findCfEval(hashMap); + assertNull(cfEval); + } + + + @Test + void findUDFName() { + assertNull(CFUtils.findUdfName(Collections.emptyMap(), "someClassName", 1)); + assertEquals("FINDFILES", CFUtils.findUdfName(Collections.emptyMap(), "cf123546123$funcFINDFILES", 1)); + final Map hashMap = new HashMap<>(); + hashMap.put("this", this); + assertNull(CFUtils.findUdfName(hashMap, "cf123546123$funcFINDFILES", 0)); + + hashMap.put("this", new CFUDFTest("findUDFName")); + assertEquals("findUDFName", CFUtils.findUdfName(hashMap, "cf123546123$funcFINDFILES", 0)); + } + + + @Test + void isScope() { + assertFalse(CFUtils.isScope(this)); + assertFalse(CFUtils.isScope("this")); + assertFalse(CFUtils.isScope(new HashMap<>())); + + assertTrue(CFUtils.isScope(new TestScope())); + assertTrue(CFUtils.isScope(new ArgumentCollection())); + } + + + @Test + void findPage() { + final Map map = new HashMap<>(); + assertNull(CFUtils.findPage(map)); + + map.put("this", this); + assertSame(this, CFUtils.findPage(map)); + + map.put("this", new CFUDFTest("findPage")); + + assertNull(CFUtils.findPage(map)); + + map.put("parentPage", this); + assertSame(this, CFUtils.findPage(map)); + } + + + @Test + void findPageContext() { + final Map map = new HashMap<>(); + assertNull(CFUtils.findPageContext(map)); + + final Object pageContext = new Object(); + map.put("this", new PageContextTest(pageContext)); + assertNotNull(CFUtils.findPageContext(map)); + assertSame(pageContext, CFUtils.findPageContext(map)); + } + + + @Test + void isCfClass() { + assertFalse(CFUtils.isCfClass("any/random/class")); + assertFalse(CFUtils.isCfClass("any.random.class")); + assertFalse(CFUtils.isCfClass("anyclass")); + + assertTrue(CFUtils.isCfClass("cf123546123$funcFINDFILES")); + assertTrue(CFUtils.isCfClass("tests.now_cfm$cf")); + } + + @Test + void guessSource() { + assertNull(CFUtils.guessSource("cf123546123$funcFINDFILES")); + assertEquals("tests/now.cfm", CFUtils.guessSource("tests.now_cfm$cf")); + } + + @Test + void loadCfTracepoints() { + final Set tracePointConfigs = CFUtils.loadCfBreakpoints("some/file.cfm", + Collections.singletonMap("cfm", new MockTracepointConfig("some/file.cfm"))); + assertEquals(1, tracePointConfigs.size()); + } + + public static class CFEvaluatorTarget { + + public String Evaluate(String expr) { + return CFEvaluatorTarget.class.getName(); + } + } + + + public static class CFEvaluatorTarget2 { + + public String Evaluate(Object expr) { + return CFEvaluatorTarget2.class.getName(); + } + } + + + public static class CFUDFTest extends UDFMethod { + + public CFUDFTest(final String key) { + super(key); + } + } + + + public static class PageContextTest { + + private final Object pageContext; + + + public PageContextTest(final Object pageContext) { + this.pageContext = pageContext; + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorServiceTest.java new file mode 100644 index 0000000..d2a329b --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorServiceTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.evaluator; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.intergral.deep.agent.api.plugin.IEvaluator; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +class EvaluatorServiceTest { + + @Test + void coverage() throws Throwable { + final IEvaluator evaluator; + try (MockedStatic nashorn = Mockito.mockStatic(NashornReflectEvaluator.class, "loadEvaluator")) { + nashorn.when(() -> NashornReflectEvaluator.loadEvaluator(Mockito.any())).thenReturn(null); + evaluator = EvaluatorService.createEvaluator(); + } + + assertTrue(evaluator.evaluate(null, null)); + assertThrows(RuntimeException.class, () -> evaluator.evaluateExpression(null, null)); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluatorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluatorTest.java index bf33717..2736c1a 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluatorTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluatorTest.java @@ -46,6 +46,23 @@ void evalExpression() throws Throwable { assertEquals("bob", String.valueOf(o)); } + @Test + void parseExpression() { + assertEquals("deep_this", NashornReflectEvaluator.parseExpression("this")); + assertEquals("deep_this", NashornReflectEvaluator.parseExpression(" this")); + assertEquals("deep_this", NashornReflectEvaluator.parseExpression(" this ")); + assertEquals("deep_this", NashornReflectEvaluator.parseExpression("this ")); + + assertEquals("deep_this.some.path.to.value", NashornReflectEvaluator.parseExpression("this.some.path.to.value")); + assertEquals("deep_this.someFunction()", NashornReflectEvaluator.parseExpression("this.someFunction()")); + assertEquals("max(deep_this.someFunction(), 123)", NashornReflectEvaluator.parseExpression("max(this.someFunction(), 123)")); + assertEquals("101 - deep_this.someFunction()", NashornReflectEvaluator.parseExpression("101 - this.someFunction()")); + + assertEquals("101 - deep_this.someFunctionWithThisInTt()", NashornReflectEvaluator.parseExpression("101 - this.someFunctionWithThisInTt()")); + // todo this is a known issue + // assertEquals("101 - deep_this.thisFunction()", NashornReflectEvaluator.parseExpression("101 - this.thisFunction()")); + } + /** * This test is more of a note for using nashorn and allows for testing features etc. */ diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java new file mode 100644 index 0000000..e9eced9 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.intergral.deep.agent.api.plugin.IEvaluator; +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.tracepoint.handler.FrameCollector.IExpressionResult; +import com.intergral.deep.agent.types.snapshot.Variable; +import com.intergral.deep.test.MockTracepointConfig; +import com.intergral.deep.test.target.ConditionTarget; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class FrameCollectorTest { + + private Settings settings = Mockito.mock(Settings.class); + private IEvaluator evaluator = Mockito.mock(IEvaluator.class); + private FrameCollector frameCollector; + + @BeforeEach + void setUp() { + Mockito.when(settings.getResource()).thenReturn(Resource.DEFAULT); + frameCollector = new FrameCollector(settings, evaluator, Collections.singletonMap("this", new ConditionTarget()), + Thread.currentThread().getStackTrace()); + frameCollector.configureSelf(Collections.singletonList(new MockTracepointConfig())); + } + + @Test + void evaluateWatchers() throws Throwable { + Mockito.when(evaluator.evaluateExpression(Mockito.anyString(), Mockito.anyMap())).thenReturn("some result"); + final IExpressionResult someExpression = frameCollector.evaluateWatchExpression("some expression"); + assertEquals("some expression", someExpression.result().expression()); + assertEquals(1, someExpression.variables().size()); + final Variable variable = someExpression.variables().get("1"); + assertEquals("some result", variable.getValString()); + } + + @Test + void evaluateWatchers_error() throws Throwable { + Mockito.when(evaluator.evaluateExpression(Mockito.anyString(), Mockito.anyMap())).thenThrow(new RuntimeException("Test exception")); + final IExpressionResult someExpression = frameCollector.evaluateWatchExpression("some expression"); + assertEquals("some expression", someExpression.result().expression()); + assertEquals(0, someExpression.variables().size()); + assertEquals("java.lang.RuntimeException: Test exception", someExpression.result().error()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java new file mode 100644 index 0000000..1b66f9c --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.handler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.intergral.deep.agent.Utils; +import com.intergral.deep.agent.api.plugin.EvaluationException; +import com.intergral.deep.agent.api.plugin.IEvaluator; +import com.intergral.deep.agent.api.resource.Resource; +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.tracepoint.evaluator.EvaluatorService; +import com.intergral.deep.agent.types.TracePointConfig; +import com.intergral.deep.agent.types.snapshot.EventSnapshot; +import com.intergral.deep.agent.types.snapshot.WatchResult; +import com.intergral.deep.test.MockTracepointConfig; +import com.intergral.deep.test.target.ConditionTarget; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class FrameProcessorTest { + + private Settings settings = Mockito.mock(Settings.class); + private IEvaluator evaluator = EvaluatorService.createEvaluator(); + private Collection tracepoints = new ArrayList<>(); + private FrameProcessor frameProcessor; + + @BeforeEach + void setUp() { + Mockito.when(settings.getResource()).thenReturn(Resource.DEFAULT); + tracepoints.clear(); + frameProcessor = new FrameProcessor(settings, evaluator, Collections.singletonMap("this", new ConditionTarget()), tracepoints, + Utils.currentTimeNanos(), Thread.currentThread().getStackTrace()); + } + + @Test + void canCollect() { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig(); + tracepoints.add(tracepointConfig); + + assertTrue(frameProcessor.canCollect()); + tracepointConfig.fired(Utils.currentTimeNanos()[0]); + + assertFalse(frameProcessor.canCollect()); + } + + @Test + void canCollect_withCondition() { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig().withArg(TracePointConfig.CONDITION, "this.i == 101"); + tracepoints.add(tracepointConfig); + + assertFalse(frameProcessor.canCollect()); + tracepointConfig.withArg(TracePointConfig.CONDITION, "this.i == 100"); + + assertTrue(frameProcessor.canCollect()); + } + + @Test + void willCollect() { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig(); + tracepoints.add(tracepointConfig); + assertTrue(frameProcessor.canCollect()); + frameProcessor.configureSelf(); + + final Collection collect = frameProcessor.collect(); + assertEquals(1, collect.size()); + } + + @Test + void willCollect_2() { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig(); + tracepoints.add(tracepointConfig); + tracepoints.add(new MockTracepointConfig()); + assertTrue(frameProcessor.canCollect()); + frameProcessor.configureSelf(); + + final Collection collect = frameProcessor.collect(); + assertEquals(2, collect.size()); + } + + @Test + void willCollect_watches() { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig().withWatches("this.i", "this.i - 10"); + tracepoints.add(tracepointConfig); + assertTrue(frameProcessor.canCollect()); + frameProcessor.configureSelf(); + + final Collection collect = frameProcessor.collect(); + assertEquals(1, collect.size()); + final EventSnapshot next = collect.iterator().next(); + final ArrayList watches = next.getWatches(); + assertEquals(2, watches.size()); + + final WatchResult iWatch; + final WatchResult i10Watch; + + // order is not determined + if (watches.get(0).expression().equals("this.i")) { + iWatch = watches.get(0); + i10Watch = watches.get(1); + } else { + iWatch = watches.get(1); + i10Watch = watches.get(0); + } + + assertEquals("this.i", iWatch.expression()); + assertNotNull(iWatch.goodResult()); + assertEquals("100", next.getVarLookup().get(iWatch.goodResult().getId()).getValString()); + + assertEquals("this.i - 10", i10Watch.expression()); + assertNotNull(i10Watch.goodResult()); + assertEquals("90.0", next.getVarLookup().get(i10Watch.goodResult().getId()).getValString()); + } + + @Test + void willCollect_watches_2() { + final MockTracepointConfig tracepointConfig = new MockTracepointConfig().withWatches("this.i", "10 - this.i"); + tracepoints.add(tracepointConfig); + assertTrue(frameProcessor.canCollect()); + frameProcessor.configureSelf(); + + final Collection collect = frameProcessor.collect(); + assertEquals(1, collect.size()); + final EventSnapshot next = collect.iterator().next(); + final ArrayList watches = next.getWatches(); + assertEquals(2, watches.size()); + + final WatchResult iWatch; + final WatchResult i10Watch; + + // order is not determined + if (watches.get(0).expression().equals("this.i")) { + iWatch = watches.get(0); + i10Watch = watches.get(1); + } else { + iWatch = watches.get(1); + i10Watch = watches.get(0); + } + + assertEquals("this.i", iWatch.expression()); + assertNotNull(iWatch.goodResult()); + assertEquals("100", next.getVarLookup().get(iWatch.goodResult().getId()).getValString()); + + assertEquals("10 - this.i", i10Watch.expression()); + assertNotNull(i10Watch.goodResult()); + assertEquals("-90.0", next.getVarLookup().get(i10Watch.goodResult().getId()).getValString()); + } + + @Test + void willEvaluate() throws EvaluationException { + assertEquals("100", frameProcessor.evaluateExpression("this.i")); + } +} \ No newline at end of file diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/bfs/NodeTest.java similarity index 72% rename from agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java rename to agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/bfs/NodeTest.java index e28b612..207d507 100644 --- a/agent-api/src/test/java/com/intergral/deep/agent/api/reflection/ReflectionUtilsTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/bfs/NodeTest.java @@ -15,18 +15,23 @@ * along with this program. If not, see . */ -package com.intergral.deep.agent.api.reflection; +package com.intergral.deep.agent.tracepoint.handler.bfs; import static org.junit.jupiter.api.Assertions.*; +import com.intergral.deep.agent.tracepoint.handler.bfs.Node.IParent; +import com.intergral.deep.agent.types.snapshot.VariableID; import org.junit.jupiter.api.Test; -class ReflectionUtilsTest { +class NodeTest { @Test void coverage() { - // for coverage - //noinspection ObviousNullCheck,InstantiationOfUtilityClass - assertNotNull(new ReflectionUtils()); + assertFalse(new IParent(){ + @Override + public void addChild(final VariableID child) { + + } + }.isCollection()); } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/CFClassScannerTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/CFClassScannerTest.java new file mode 100644 index 0000000..b9f9a65 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/CFClassScannerTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CFClassScannerTest { + + private CFClassScanner cfClassScanner; + + @BeforeEach + void setUp() { + cfClassScanner = new CFClassScanner(new HashMap<>()); + } + + @Test + void getLocation() throws MalformedURLException { + final URL location = cfClassScanner.getLocation(CFClassScannerTest.class); + assertTrue(location.toString().endsWith("test-classes/")); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/InstUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/InstUtilsTest.java new file mode 100644 index 0000000..becba76 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/InstUtilsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class InstUtilsTest { + + @Test + void internalClass() { + assertEquals("java/lang/String", InstUtils.internalClass(String.class)); + } + + + @Test + void externalClassName() { + assertEquals(getClass().getName(), InstUtils.externalClassName("com/intergral/deep/agent/tracepoint/inst/InstUtilsTest")); + assertEquals(getClass().getName(), InstUtils.externalClassName(getClass().getName())); + } + + + @Test + void fileName() { + assertEquals("InstUtilsTest", InstUtils.fileName("com/intergral/deep/agent/tracepoint/inst/InstUtilsTest")); + assertEquals("InstUtilsTest", InstUtils.fileName("InstUtilsTest")); + } + + + @Test + void innerClassName() { + assertEquals("com/intergral/deep/agent/tracepoint/inst/InstUtilsTest", InstUtils.internalClassStripInner(BadToStringType.class)); + } + + + @Test + void shortclassName() { + assertEquals("InstUtilsTest", InstUtils.shortClassName(getClass().getName())); + } + + + public static class BadToStringType { + + public String someString() { + return "someString"; + } + + + @Override + public String toString() { + throw new RuntimeException("bad class"); + } + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java index 0d1ca47..3fc595f 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java @@ -31,6 +31,8 @@ import java.lang.instrument.UnmodifiableClassException; import java.net.MalformedURLException; import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -170,4 +172,14 @@ void jspClassesTriggerTransform() throws IOException, ClassNotFoundException, Un tracepointInstrumentationService.processBreakpoints(Collections.emptyList()); Mockito.verify(instrumentation, times(2)).retransformClasses(jspClass); } + + @Test + void getLocation() throws MalformedURLException { + final ProtectionDomain protectionDomain = Mockito.mock(ProtectionDomain.class); + final CodeSource codeSource = Mockito.mock(CodeSource.class); + Mockito.when(protectionDomain.getCodeSource()).thenReturn(codeSource); + Mockito.when(codeSource.getLocation()).thenReturn(new URL("http://google.com")); + final URL location = tracepointInstrumentationService.getLocation(protectionDomain); + assertEquals("http://google.com", location.toString()); + } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundExceptionTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundExceptionTest.java new file mode 100644 index 0000000..a32cc4a --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundExceptionTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst.asm; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ClassInfoNotFoundExceptionTest { + + @Test + void coverage() { + assertEquals("some test", new ClassInfoNotFoundException("some test", "some type").getMessage()); + assertEquals("some type", new ClassInfoNotFoundException("some test", "some type").getType()); + assertEquals("some cause", new ClassInfoNotFoundException("some test", "some type", new RuntimeException("some cause")).getCause().getMessage()); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoTest.java new file mode 100644 index 0000000..ff3e818 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst.asm; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import lucee.loader.classloader.LuceeClassLoader; +import org.junit.jupiter.api.Test; +import railo.loader.classloader.RailoClassLoader; + +class ClassInfoTest { + + @Test + void isLuceeClassLoader() { + assertTrue(ClassInfo.isLuceeClassLoader(new LuceeClassLoader())); + assertFalse(ClassInfo.isRailoClassLoader(new LuceeClassLoader())); + } + + @Test + void isRailoClassLoader() { + assertFalse(ClassInfo.isLuceeClassLoader(new RailoClassLoader())); + assertTrue(ClassInfo.isRailoClassLoader(new RailoClassLoader())); + + } + + @Test + void isSafeClassLoader() { + assertFalse(ClassInfo.isSafeLoader(new RailoClassLoader())); + assertFalse(ClassInfo.isSafeLoader(new LuceeClassLoader())); + assertTrue(ClassInfo.isSafeLoader(new railo.commons.lang.PCLBlock())); + assertTrue(ClassInfo.isSafeLoader(new lucee.commons.lang.PCLBlock())); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java new file mode 100644 index 0000000..27695bf --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst.jsp; + +import static org.junit.jupiter.api.Assertions.*; + +import com.intergral.deep.agent.settings.Settings; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SmapUtils; +import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SourceMap; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; + +class JSPUtilsTest { + @Test + void isJspClass() + { + assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), + "org.apache.jsp.jdbc.views.companies_jsp" ) ); + assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), + "org.apache.jsp.views.companies_jsp" ) ); + assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), + "org.apache.jsp.companies_jsp" ) ); + assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), + "jsp.companies_jsp" ) ); + + assertFalse( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), + "companies_jsp" ) ); + } + + + @Test + void sourceMap() throws Exception + { + final File file = new File( "src/test/resources" ); + final URL resourceUrl = file.toURI().toURL(); + final URL[] classUrls = { resourceUrl }; + final URLClassLoader ucl = new URLClassLoader( classUrls ); + final Class c = ucl.loadClass( "org.apache.jsp.jdbc.views.companies_jsp" ); + + final SourceMap sourceMap = JSPUtils.getSourceMap( c ); + + assertNotNull( sourceMap ); + final List filenames = sourceMap.getFilenames(); + assertTrue( filenames.containsAll( Arrays.asList( "companies.jsp", "header.jsp", "setup.jsp", "footer.jsp" ) ) ); + } + + + @Test + void sourceMap_bytes() throws Exception + { + final InputStream resourceAsStream = getClass().getResourceAsStream( "/org/apache/jsp/jdbc/views/companies_jsp.class" ); + final byte[] bytes = new byte[resourceAsStream.available()]; + resourceAsStream.read( bytes ); + + final SourceMap sourceMap = JSPUtils.getSourceMap( bytes ); + + assertNotNull( sourceMap ); + final List filenames = sourceMap.getFilenames(); + assertTrue( filenames.containsAll( Arrays.asList( "companies.jsp", "header.jsp", "setup.jsp", "footer.jsp" ) ) ); + } + + + @Test + void sourceMap_bytes_src() throws Exception + { + final InputStream resourceAsStream = getClass().getResourceAsStream( "/org/apache/jsp/jdbc/views/companies_jsp.class" ); + final byte[] bytes = new byte[resourceAsStream.available()]; + resourceAsStream.read( bytes ); + + + final String src = SmapUtils.scanSource( new ClassReader( bytes ) ); + + assertNotNull( src ); + assertEquals( "companies_jsp.java", src ); + } +} \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java new file mode 100644 index 0000000..448975c --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java @@ -0,0 +1,49 @@ +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class FileSectionEntryTest +{ + + + private FileSectionEntry fileSectionEntry; + + + @BeforeEach + void setUp() + { + new FileSectionEntry( 10, "source" ); + fileSectionEntry = new FileSectionEntry( 10, "source", "path" ); + } + + + @Test + void getSourceName() + { + assertEquals( "source", fileSectionEntry.getSourceName() ); + } + + + @Test + void getId() + { + assertEquals( 10, fileSectionEntry.getId() ); + } + + + @Test + void getSourcePath() + { + assertEquals( "path", fileSectionEntry.getSourcePath() ); + } + + + @Test + void toStringTest() + { + assertEquals( "FileSectionEntry#10:source:path", fileSectionEntry.toString() ); + } +} diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java new file mode 100644 index 0000000..2e61d8c --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java @@ -0,0 +1,27 @@ +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class LineSectionEntryTest +{ + + + private LineSectionEntry lineSectionEntry; + + + @BeforeEach + void setUp() + { + lineSectionEntry = new LineSectionEntry( 1, 101, 10, 202, 2 ); + } + + + @Test + void testToString() + { + assertEquals( "LineSectionEntry#101 1 10 202 2", lineSectionEntry.toString() ); + } +} diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java new file mode 100644 index 0000000..602be01 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java @@ -0,0 +1,283 @@ +/** + * Copyright (C) 2019 Intergral Information Solutions GmbH. All Rights Reserved + */ +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class SouceMapParserTest { + + public SouceMapParserTest() { + } + + + private String fileToString(final String filename) throws IOException { + final byte[] bytes; + try (InputStream resourceAsStream = getClass().getResourceAsStream(filename)) { + bytes = new byte[resourceAsStream.available()]; + resourceAsStream.read(bytes); + } + return new String(bytes); + } + + + @Test + public void testIndexJsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/index_jsp.smap")); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + { + final List found = smap.map("header.jsp", 1); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(71, entry.getStart(), "Should start @ line 71"); + assertEquals(71, entry.getEnd(), "Should end @ line 71"); + } + + { + final List found = smap.map("header.jsp", 11); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(83, entry.getStart(), "Should start @ line 83"); + assertEquals(87, entry.getEnd(), "Should end @ line 87"); + } + + { + final List found = smap.map("footer.jsp", 4); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(138, entry.getStart(), "Should start @ line 138"); + assertEquals(140, entry.getEnd(), "Should end @ line 140"); + } + + { + final List found = smap.map("index.jsp", 4); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(92, entry.getStart(), "Should start @ line 92"); + assertEquals(92, entry.getEnd(), "Should end @ line 92"); + } + } + + + /* Test based on example table from JSR-045 document + Input Source Output Source + Line Begin Line End Line + 123 207 207 + 130 210 210 + 131 211 211 + 132 212 212 + 140 250 256 + 160 300 301 + 161 302 303 + 162 304 305*/ + @Test + public void testIndex2Jsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/index2_jsp.smap")); + final SourceMap smap = parser.parse(); + + // smap.dumpInformation(); + { + final List found = smap.map("index2.jsp", 123); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(207, entry.getStart(), "Should start @ line 207"); + assertEquals(207, entry.getEnd(), "Should end @ line 207"); + } + + { + final List found = smap.map("index2.jsp", 140); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(250, entry.getStart(), "Should start @ line 250"); + assertEquals(256, entry.getEnd(), "Should end @ line 256"); + } + + { + final List found = smap.map("index2.jsp", 160); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(300, entry.getStart(), "Should start @ line 300"); + assertEquals(301, entry.getEnd(), "Should end @ line 301"); + } + + { + final List found = smap.map("index2.jsp", 162); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(304, entry.getStart(), "Should start @ line 304"); + assertEquals(305, entry.getEnd(), "Should end @ line 305"); + } + } + + + @Test + public void testParseIncludeTimeJsp() throws Exception { + // Test with a page including the same jsp twice. + + final SourceMapParser parser = new SourceMapParser(fileToString("/include_time.smap")); + final SourceMap smap = parser.parse(); + + // smap.dumpInformation(); + { + final List found = smap.map("time.jsp", 1); + assertEquals(2, found.size(), "Should fine 2 line"); + { + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(90, entry.getStart(), "Should start @ line 90"); + assertEquals(91, entry.getEnd(), "Should end @ line 91"); + } + { + final SourceMapLineStartEnd entry = found.get(1); + assertEquals(99, entry.getStart(), "Should start @ line 99"); + assertEquals(100, entry.getEnd(), "Should end @ line 100"); + } + } + + { + final List found = smap.map("footer.jsp", 29); + assertEquals(1, found.size(), "Should fine 1 line"); + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(140, entry.getStart(), "Should start @ line 140"); + assertEquals(140, entry.getEnd(), "Should end @ line 140"); + } + } + + + @Test + public void testParseIncludeTime2Jsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/include_time2.smap")); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + + final List found = smap.map("time.jsp", 1); + assertEquals(4, found.size(), "Should contain 4 entries"); + { + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(91, entry.getStart(), "Should start @ line 91"); + assertEquals(92, entry.getEnd(), "Should end @ line 92"); + } + { + final SourceMapLineStartEnd entry = found.get(1); + assertEquals(102, entry.getStart(), "Should start @ line 102"); + assertEquals(103, entry.getEnd(), "Should end @ line 103"); + } + { + final SourceMapLineStartEnd entry = found.get(2); + assertEquals(114, entry.getStart(), "Should start @ line 114"); + assertEquals(115, entry.getEnd(), "Should end @ line 115"); + } + { + final SourceMapLineStartEnd entry = found.get(3); + assertEquals(117, entry.getStart(), "Should start @ line 117"); + assertEquals(118, entry.getEnd(), "Should end @ line 119"); + } + } + + + @Test + public void testIncludeTime3Jsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/include_time3.smap")); + final SourceMap smap = parser.parse(); + + final List found = smap.map("time.jsp", 1); + assertEquals(3, found.size(), "Should contain 3 entries"); + { + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(91, entry.getStart(), "Should start @ line 91"); + assertEquals(92, entry.getEnd(), "Should end @ line 92"); + } + { + final SourceMapLineStartEnd entry = found.get(1); + assertEquals(102, entry.getStart(), "Should start @ line 102"); + assertEquals(103, entry.getEnd(), "Should end @ line 103"); + } + { + final SourceMapLineStartEnd entry = found.get(2); + assertEquals(113, entry.getStart(), "Should start @ line 113"); + assertEquals(116, entry.getEnd(), "Should end @ line 116"); + } + } + + + @Test + public void testIncludeTime3JspLookup() throws Exception { + final String s = fileToString("/include_time3.smap"); + final SourceMapParser parser = new SourceMapParser(s); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + // line 91 doesnt map back to line 1 as there 2 files which match to line 91 + // and the first include_time.jsp should take precident according to the spec. + // 1 include_time.jsp 4 -> 91 + // 2 time.jsp 1 -> 91 + final SourceMapLookup lookup = smap.lookup(92); + + assertEquals("time.jsp", lookup.getFilename(), "Should find time.jsp line 1"); + assertEquals(1, lookup.getLineNumber(), "Should find time.jsp line 1"); + } + + + @Test + public void testCompaniesJsp() throws Exception { + final URL resourceUrl = getClass().getResource("/org/apache/jsp/jdbc/views/companies_jsp.class"); + final URL[] classUrls = {resourceUrl}; + final URLClassLoader ucl = new URLClassLoader(classUrls); + final Class c = ucl.loadClass("org.apache.jsp.jdbc.views.companies_jsp"); + + final String sourceDebugExtension = SmapUtils.lookUp(c); + assertNotNull(sourceDebugExtension, "smap should not be null"); + assertFalse(sourceDebugExtension.isEmpty(), "smap should not be emtpy"); + + assertTrue(sourceDebugExtension.startsWith("SMAP"), "smap should start with SMAP"); + assertTrue(sourceDebugExtension.trim().endsWith("*E"), "smap should end with *E"); + + final SourceMapParser parser = new SourceMapParser(sourceDebugExtension); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + + final SourceMapLookup lookup = smap.lookup(458); + assertNull(lookup, "Should not find a line for 458"); + + assertEquals(71, smap.lookup(457).getLineNumber()); + } + + + @Test + public void testNoopJspBug() throws Exception { + // This code will cause a Null pointer exception FR-5116 + // as the SMAP is corrupt + final String s = fileToString("/noop.smap"); + final SourceMapParser parser = new SourceMapParser(s); + try { + final SourceMap smap = parser.parse(); + fail("Should not parse"); + } catch (IOException ioe) { + assertEquals("Cannot find a Stratum Section in SMAP", ioe.getMessage()); + } + } +} diff --git a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java index f323372..08cbcd7 100644 --- a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java +++ b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java @@ -19,6 +19,7 @@ import com.intergral.deep.agent.types.TracePointConfig; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; public class MockTracepointConfig extends TracePointConfig { @@ -40,4 +41,9 @@ public MockTracepointConfig withArg(final String key, final String value) { this.getArgs().put(key, value); return this; } + + public MockTracepointConfig withWatches(final String... watches) { + this.getWatches().addAll(Arrays.asList(watches)); + return this; + } } diff --git a/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java b/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java new file mode 100644 index 0000000..efea5ac --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.test.target; + +public class ConditionTarget { + public int i = 100; +} diff --git a/agent/src/test/java/lucee/commons/lang/PCLBlock.java b/agent/src/test/java/lucee/commons/lang/PCLBlock.java new file mode 100644 index 0000000..334b107 --- /dev/null +++ b/agent/src/test/java/lucee/commons/lang/PCLBlock.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.commons.lang; + +@SuppressWarnings("ALL") +public class PCLBlock extends ClassLoader { + +} diff --git a/agent/src/test/java/lucee/loader/classloader/LuceeClassLoader.java b/agent/src/test/java/lucee/loader/classloader/LuceeClassLoader.java new file mode 100644 index 0000000..2037419 --- /dev/null +++ b/agent/src/test/java/lucee/loader/classloader/LuceeClassLoader.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package lucee.loader.classloader; + +@SuppressWarnings("ALL") +public class LuceeClassLoader extends ClassLoader { + +} diff --git a/agent/src/test/java/railo/commons/lang/PCLBlock.java b/agent/src/test/java/railo/commons/lang/PCLBlock.java new file mode 100644 index 0000000..5bfeae4 --- /dev/null +++ b/agent/src/test/java/railo/commons/lang/PCLBlock.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package railo.commons.lang; + +@SuppressWarnings("ALL") +public class PCLBlock extends ClassLoader{ + +} diff --git a/agent/src/test/java/railo/loader/classloader/RailoClassLoader.java b/agent/src/test/java/railo/loader/classloader/RailoClassLoader.java new file mode 100644 index 0000000..7f3913a --- /dev/null +++ b/agent/src/test/java/railo/loader/classloader/RailoClassLoader.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package railo.loader.classloader; + +@SuppressWarnings("ALL") +public class RailoClassLoader extends ClassLoader { + +} diff --git a/agent/src/test/resources/2_stratum.smap b/agent/src/test/resources/2_stratum.smap new file mode 100644 index 0000000..e18f012 --- /dev/null +++ b/agent/src/test/resources/2_stratum.smap @@ -0,0 +1,20 @@ +SMAP +Hi.java +Java +*S Foo +*F +1 Hi.foo +2 Incl.foo +*L +1#1,1:1,1 +2#1,4:6,2 +1#2,2:2,2 +*S Bar +*F +1 Hi.bar +2 Incl.bar +*L +1#1:1 +1#2,4:2 +3#1,8:6 +*E \ No newline at end of file diff --git a/agent/src/test/resources/include_time.smap b/agent/src/test/resources/include_time.smap new file mode 100644 index 0000000..c8cffca --- /dev/null +++ b/agent/src/test/resources/include_time.smap @@ -0,0 +1,35 @@ +SMAP +include_005ftime_jsp.java +JSP +*S JSP +*F ++ 0 header.jsp +jsp/../header.jsp ++ 1 include_time.jsp +jsp/include_time.jsp ++ 2 time.jsp +jsp/time.jsp ++ 3 footer.jsp +jsp/../footer.jsp +*L +1,5:70 +6,4:76 +9,2:80 +11:82,5 +12,2:87 +1#1,2:88 +1#2:90,2 +3#1,2:92 +6,2:95 +7,2:97 +1#2:99,2 +9#1,2:101 +11,2:103,2 +13:107 +1#3,3:108 +4:111,3 +5,4:114 +9,17:119 +25,5:136 +14#1:140 +*E diff --git a/agent/src/test/resources/include_time2.smap b/agent/src/test/resources/include_time2.smap new file mode 100644 index 0000000..5483877 --- /dev/null +++ b/agent/src/test/resources/include_time2.smap @@ -0,0 +1,39 @@ +SMAP +include_005ftime_jsp.java +JSP +*S JSP +*F ++ 0 header.jsp +smap/../header.jsp ++ 1 include_time.jsp +smap/include_time.jsp ++ 2 time.jsp +smap/includes/time.jsp ++ 3 footer.jsp +smap/../footer.jsp +*L +1,5:70 +6,4:76 +9,2:80 +11:82,5 +12,2:87 +1#1,4:88 +1#2:91,2 +4#1,2:93 +7,2:96 +8,5:98 +1#2:102,2 +12#1,4:104 +15,2:107,2 +17,4:111 +1#2:114,2 +20#1:116 +1#2:117,2 +21#1,2:119 +1#3,3:121 +4:124,3 +5,4:127 +9,17:132 +25,5:149 +23#1:153 +*E diff --git a/agent/src/test/resources/include_time3.smap b/agent/src/test/resources/include_time3.smap new file mode 100644 index 0000000..748a597 --- /dev/null +++ b/agent/src/test/resources/include_time3.smap @@ -0,0 +1,37 @@ +SMAP +include_005ftime_jsp.java +JSP +*S JSP +*F ++ 0 header.jsp +smap/../header.jsp ++ 1 include_time.jsp +smap/include_time.jsp ++ 2 time.jsp +smap/includes/time.jsp ++ 3 footer.jsp +smap/../footer.jsp +*L +1,5:70 +6,4:76 +9,2:80 +11:82,5 +12,2:87 +1#1,4:88 +1#2:91,2 +4#1,2:93 +7,2:96 +8,5:98 +1#2:102,2 +12#1,4:104 +15:107,3 +16,4:110 +1#2:113,4 +19#1,2:117 +1#3,3:119 +4:122,3 +5,4:125 +9,17:130 +25,5:147 +21#1:151 +*E diff --git a/agent/src/test/resources/index2_jsp.smap b/agent/src/test/resources/index2_jsp.smap new file mode 100644 index 0000000..7eaad8f --- /dev/null +++ b/agent/src/test/resources/index2_jsp.smap @@ -0,0 +1,13 @@ +SMAP +index2_jsp.java +JSP +*S JSP +*F ++ 0 index2.jsp +index2.jsp +*L +123:207 +130,3:210 +140:250,7 +160,3:300,2 +*E \ No newline at end of file diff --git a/agent/src/test/resources/index_jsp.smap b/agent/src/test/resources/index_jsp.smap new file mode 100644 index 0000000..0f2ed37 --- /dev/null +++ b/agent/src/test/resources/index_jsp.smap @@ -0,0 +1,27 @@ +SMAP +index_jsp.java +JSP +*S JSP +*F ++ 0 header.jsp +header.jsp ++ 1 index.jsp +index.jsp ++ 2 footer.jsp +footer.jsp +*L +1,5:71 +6,4:77 +9,2:81 +11:83,5 +12,2:88 +1#1,9:89 +10,33:99 +42,4:132 +1#2,3:135 +4:138,3 +5,4:141 +9,17:146 +25,5:163 +45#1:167,2 +*E diff --git a/agent/src/test/resources/noop.smap b/agent/src/test/resources/noop.smap new file mode 100644 index 0000000..46c6d75 --- /dev/null +++ b/agent/src/test/resources/noop.smap @@ -0,0 +1,4 @@ +SMAP +noop_jsp.java +JSP +null*E \ No newline at end of file diff --git a/agent/src/test/resources/org/apache/jsp/jdbc/views/companies_jsp.class b/agent/src/test/resources/org/apache/jsp/jdbc/views/companies_jsp.class new file mode 100644 index 0000000000000000000000000000000000000000..22c5962419dd974ac4a681c52f9edfe18197a3d1 GIT binary patch literal 36007 zcmeHwcYK@0{r~6gk*&w_<0ZvOl&Ltw&ah?6TOJ`30$F4yj38UGtsq-Qk~7$}>{)1; zg|Z4^Z=8fM0%f8+TFfxUu#Fey)N4AUgvLVb*~QwHbmX6;m&SfS1=G=iwY}Q z7)?wD_C>n`5qG4gD;Dewxc7_2y7!NE&-6tDR?1{FZTFx6TN>_(v<7Aex&vMQKv#^B zA!uxz>uZgLBbykFnZM4r-nY>m4Mf(50x`)MnyLB*M)?wctzTmBb;Y8L@?;x#Pb?U6 zFYt8(C~Il3y~`Kti3Au`rq(u^k0D=IyL)LY66|VkNGh2h#&a_;EM8k)Ufm{GlbM*W zeQ`J(lKEEo+ZK+@f$os^)X0MMzKBeNu|Q{3<{+X>TLSIDE=E)4?_M*@eC_-By8NL) zM34dfCfRZj`r)1!KoYb=JptLGE8G(HLyv`P1EIBnjopzzG#U(dwZ-*2N(#Ht9SFJS zBx?J#J49%AZLljE^L4cbI(=Qf_CSQu_+-{&;m%fH%so#5Eii$=v)wJ+6BDV0-6*>7fnnq<-DrJ-_sY!bh)?<*H${AVL`_Lte z+88-X_BYvgCtw<~$wO6Es$`VY$1C_WsG4e^z3v{2jJlE}!(7y|F3=iFHdR%Uj9V4y zRL3aW-4XEl1CcUxsLA#61AnGdBO@CISFEQyQDJ*$F)H?9bT9<;wgp20uRGiwQ*%S9 z+1;geW#wg+?hSz!Uw3!Zz5lWqd(hN4_kuagW=K`Gmc>GT@Ibqs`P18rb($@>YYT^C zCU;^02fG3bdpcVJk!8M?5C*M%ez?^aTH%WX#j`B5kw*K8J}iV`#X5o*_>+^(5_0Z# z4uf&#NW&7-jyY)*D5FZAHmduV?d4>li%~NVmT!YKEjH*tT7roR1ecn#*enddU^}d9 zfFY*t8gy#n8AMD0(6F1iV<0Mw$_`p*r6AVERFQ;frKN7r;S^#t1l$dnO)(`U zx;yP`D|FdqP(BsdsGA~II)YL8?)$ype5+XQ;u|+Tn{}UTj*vY;I{?Hl0MLSm|V}4XJ2$$D2W?(rH)=fI%8&S8Um)Zs?#e zc@9_Z#^e>kvd=K+Ogalq&~uBi$|mgj^!{p7toK*b;7Ux?D%~Y5C5nwT^VXyr8cDoh z1fCf7TaWPUA_3{zxnmef!g22mceza- z_6vmIryF!T-2smnw9R5h%hC`YOqPK>B|9C;oOHSi0{dHa>J<*m2ER^Qk?Dy>bh<~> zwCHp%3Oa(w+>cqK)tta>w1Xb9(t~lgO*%<4;RFYKm>k{o8_dRO_)X?OLBoo6&?5#t zN{>O;EhdT;!VLr2lF4hV^aP`+d#l6QGZxwCar&+BIDW?{oQ8*idilOeCa=)xY3Qg? z%uUU3O`k;%x?GLIr4x_8heBO08~u@9u+sDJHG~+_C7&prD#|rd%UXyl>Dy z=>x1TAzu`WoIkKpxYfdYpC_t6GU#La7xWYDX~Eir4IQJQCG+MbTS&r2KgEpU7Og%r z=yUo46G9Xo1ty!cHf9bArB`#WC~V>@gT5Ae$bcSZ&(!HZj3!{+E;BdNWpKJ%HkFC( zblDuZw0?iUKRXg!ABgB6PNnEZ?2-<5VbyFZ3Stx#xnfu=knhAY;NDnjmUTn|ZB0d( z+!!~QCg-kh2p7{?1|}bOU}I~j#~*OZMp1X04{L6?s|-IyFc4;CvO+~&?NYcs1G?vn& zwI>Q!A}wqV_Tnk93G}70jzDK%w?y*b7o{|8!_-mg+YpF`2WeFR)}VrDYb4kmQ!189 z5M#wGEpkO8txZKbgLgA6L1A6gy$+5^WRuwTiJhMieqFSvxzTMl6AFgkQpn3nLi(Shwsk@f;V99E9o24K<5-Sm)XOq)m^^zu2zhgXj2igExw4$OiKOc?izN0IaXE%+oeY>J9J!}34bur zUd+@!?9d{mZK0l^U&4)t%?oD5zs4k3Ma0~DO2~M?LJkfPNp_{4L zYsPtLbc1bCh&{YFQP|y&IbktH&2!~ayTn{-&0fL&q0!wf+PfKbC)%eKF0T5BkR~o^ z>iR9_%d*zCPAuy|F~yaMnSU@XR4|&=UkfQ7!CI-4?ymMd=%kX-x&-@(YIVAT{-$C$ zva?&};;FcwLL`LhWhIYkNYQ{V(%OL`C1#eUqHdqxkGZ&1!gyU(<=q<_q&-w)8i>;q zX|nYo7TJ1uOL(KJCDtYWPZ+Dvb1kFVCHWfjN7|87p$hxK2}1EYcGwa_J0_1&hdIO<_$2 zYi85o!D3(I3q|{_Y4Lc?8lBA$?vD^P;MdtKtmT5kIoQl3bC|HtefudaRcAA%<~v6OvZ?YeKM zDxvY^jD|JOkdbfKY_V83ig*`L2pTKlBkmoI&OoOivl_T189eLkAh_u%xLxzQN*4q= z5!`l_Y=8^q>I%nP@XMpYXl!ya;X|;>?H%EO@+t&%#9wkG^?0Gqe2iSFEX3WAWOA*0 zqsz`s(Ie0`ItxIT@Dlz>zhBpcBfeGy^*e&_d31I-*0H`4 zopr)*YhENlT+0BHj6RHDUw`BSdfr4PP#=LoXg=u-<8W*oZHZ9+hH&I?%uFfD@7XJD zM3pu+2yKW5oo$lZSlUk;N9~O^B!-ZPa|)J^fj0U|bcWc*@a6>qv8zM^rSN;g-dJgO zI2vom0f0dNQ&?&IS!X9S?MZuNdrkne$C#nYdYzI-OXUveA3zVg56ote^17EBMdQh=)n(`^SGEU zZ)P+-xfb4?av#i-zJ&WQU`1c*TMw^Rx@EUA^7TWaT-SIjY<*y4n~Ge1U(AOiiS~Bk zV|0f7V(^${z6iEsO-187H!YCG%`^OdS6m!P=5};(ADKoRK}0%*40pnnN-B@LaK+J{ z)>dq5|Hif=Q$bT?clL32Ces;Eu! zoTV_XHk{d*YhhdtSIpNUUBafK(u%m(j!hWcyCRnuYNgW22?hKuo1hX?`uW1|QPmwm zY(cw}%OYsshZ#}w)}MN*LMwnCP#b|XOqNP#p$mR7;2y;3pg<=64G^a^X&>+#Mi-c1 z611f!7Q>=#x*O(0QQR?T4TZ&MlKturMmvLI7h`sIb7K@o)27_1f=v>q(a{DNCjb7Yp66P$jfd{xMoS1m6M!Y(jD@(20FqaxFStOBJ|;vPKg&j z5j-ES2u}K28d?#oj0Kv6zuXY_q>FX-41!X{Lbm4^)%4>^+P3uYizI7B&1NjcHB0Pm zlO*~BquKr1nzK~AE7OQ1P{ch=g&kZxpE4aN9jE%3>7zZfLP5mZO#Z~=`XYwVo*{Mi zGNU=EtYawc4Z*ly*GKZt12i}Q6#}4T(v>qqRDEs#0;eEtrOsX(U~~h}n(mf~(=Wqa zWu`CJN8k<2J9_}r*fc}QV;ds~q^Z`~ z2WcGIeyEk}BP{i!J)4c76n7vYZl9e0g4v|4)Y+$uJpH6g8qYoGwxZ_c_;oGBt~+Tg ze}?1ce(m-!J4sw>UNZA3Gg1VB%%lnD3q}?F@F(%rEoO!0#rVZe!Jov|SBwtr*K7}L zNmPn0GoQkiC=xW1So>zs<ylY7W*Pt z9t3vm8RKUxie7`2kHBzFI>wR54KsU!Vo61CiI2iz7$4p5)G!`X8hGwx<4!(A{J0E0 zju*kr7yF4WKOBxUCs_l}N)+V=fntKeCyMh9Jr*_{37r?CE6g~hIOl<&lOZyn0^?QK z<8lq;49DMqpJwn^rATkEO0K!)qyh?D89Sbjq z7?D}nZRs30rLu&(jUgIr)_EOzQ((}!SNh>H+Xxca%^(JdqZHmOj)5oWd>?>z!)eEv zHJ@qlSsYiA41_#q#GrQAAbKd>fl!h#am_=RxqLq>-#6v_avrP$7v~Hzj=z$7 zRB_Xg%~$c6R=yhJMi^S!8yE6C-pUV3oTeinieL$D`vqe`Ur5=#jjy5obbcuMOknb2 zb?tTsy4Gj&!+498`*4V8Qc$fE!=;tO0y9H6MDOYDXUlPI3{E7EQ5l{83S<*#-5nk= z{hY(EF!+`HD$^SFFx8|CU^UrT`L7wd`Z~$H@P%P1_IrIdn>enjOl-mR38+k&kqkPw zq4WK&4LEHNcDbUh;qHKqU(JW;{CY;K6U@e=A4UCEYIz*9G3x5>35CoC!OQV>1MZin6xEUiP-a@2}I3Jc5 zANZ}9W>ae-qQUKHLTYFPVg0d=rs9h7iN($HaYTqy)D8iEXFBh=AN)MHekL@+I{n;O z$x1*$zP}aYZ{$Y8#`nIA}cW-(+Q=#kJHt5|+Yj zmj~T(Xfy5KCXWBUln@xiaL1$qAHc$sj6uLFl|Kl3O|G(XL~=av_~S1R;BGuFOGgv8 zrQIlSN5o~MNWcx_5ZB~!p%hmJ`6C8@lw;M`Fdf1NPxeIQem?DGO`yYs;|2 z4E`klEyfqt>!dqq*lQ=IZFS8IHko`&O-{)V@uv*_G=Bz@tZXk_GQ?`qHBF+6`#AhL z=wZT9i6M22mH#2-Vv9LQZTxxuqLsgZFl>^F)}>yrO=5dqXLrcpFY%W#&38w_aEk*m z1Ul!4V@AYE7@d}^B(+8+Es>_$WI^A0-e2&PYtsE#iQpP`Yb@ZO+tVfEM!1CzH$!!V zdG$q2KSKF`Hux+2FPLVbym{h9hkh&vek>{VDt}=T{u4P8~mSQQrB@o zM&9@m=sz_0NAkiy(9h}ei=uxS{1X9Z^TU(%MPjfio&UR^?F(aXG>66KX*TF)wcos; zAn%We{f^hXy|b>ny+IrW)KpEwU2y)T!N1~PBV=#Bw1SI&9c#_kN6gnZs@uvD`x(Ue z_K$W8yWd0q9yD+A8X?|)48BX;$j$PH=LWlQM@QV#)tI4iVelD|0Jg@sd#YIsEkg_; zySllXWRJRLMf)N*oa&gkYNllk2>+XtL8GW9rezzNF}R2ox|Rzhle$`>-3`gVCJBKg z-_QyMN8-^nhjiryS<$4>(1s4)q*8zlH#FzqU{$&{l9AJtD)m>&sB5E>E{LQ8z)%LP zmui=-*2^NOaBNaAxPN;*tL=_94x0}14TktlAvJv5e#{BMP*i$C^Y;Uf|S_z{?Y1oiOdlW};JKm2Ipjbe1gouFPXNsXs9h{#UT`R?oLGU9Q zD|Xzw8Dok?Q#<41PR4ZHKa0r-@+3vK!q7Z})2-FDD(oGLo9CddqFS>Xvldk0h--C* zRxkFa*}-VyI*G0|47h@#xHJ>RYb0R4rGPu#o$@uC>5A1RLu($K)jD0n-Za0sc}X0H z;&&!qEJ>l2^sv{RhKXHVJ4o)e*@iY}aF*(IZC`0cn3Y9q#n8O(gCX2;g!j{t%+LXb zHeWC_a<@|^Hon?I$z%_-;UpIxJcx3!&K$ug!C_-hB4UTlE><%0Knqat zHxd{41UTY7y4E8wnp=uu92W|V{$gF*fQcm$PQuBU+6&3f2iibWMN#vlknf0|aDz&O zHf1mgl(4V1^|jWuqp&BLaFl4bRBWBf)Nb||U4!Nv2~5c-j_Y^p0nEnP#GQTI2rlj; zFutf+*G_;S$utBt5WptfmW&sB@L|bdeH(26pMsGAZX!}HWgc+5>Rxv<6r3rU>B$^INH#@T6f_lWy>+WD}ac%WCi zP{i*##KuH}r8yeA`)|2X@k_-q4o$ma@k@vDVzAiQd!wE5it_B9h%_FT7qj|h+GV&( ztzmC&!8@5S#VcU*u1;*eL_0wZd-DwQR;MPm+Jj#n#^BMe)vmK@o3Z`<@6ud494C-|h?H+AA)GkUCO8H8{T+u!EiSD^ybkAJTJv-EdJ~(%Cmn8Fc zt@eRm1BW<3$)Jb;q{!@L>01jQB(( zN2uhFhW5OegT*@+GkeSS#iS7!RWIDlX8E1V0FR9Ah>JVD?E5l_#oZ_c?EHl2nuU z%d!Y@B-vYHu!EbdouKylf-M)KCo(ETP^uk%NR`eNinlQ6^a)`g+FQ;&7ZJZ#tk^76~h+oRoGB~ zJ*8U^X&N36;B6|C@tPS?Gop43KrE9COL085Wd@PZ?q8_UApN+w?Iy(*6$Q)>;03B2 z-p^c;Vm#%0Z7QZ@4Z5WalYSDrek=mJBT%7RD$sL%&}uv_#9T5+Gmmbm!bH^93|@l> zMi0`YQn%EmG{FZUqKHKg(gY-S$9MJatHl0%H|hifyQ`|(eN{bTJi9?yfgs#&>!8@( z)_E}LnfLz8Q+>OsclF^Md@p3Z4~|~^lQ%6r?Q=H90$pMSqYA=QjQ=wzlSYUym*Ddl zHt`t}lw~8&$-w{k;)2ZMdkZqfw*q8}_wkWB&}s-3qQy`=Z6*tTiQ2c)usdn=b{e}{ z-$LW=q=|RXM0@cNn!J^!Zlel~R*Hh!Ayl7I&@hCWQVOOIp&2O!vyvfh2PsZ-@H+`? z6HS0P;~~ssusDT^Xew3W6DhN)l$KK&wc$1N%KM2)X@Sdc^OIt5)0(IO%f4onKo$zCAxPMOk6 zi?-6zyJ>Y__MjY@-u73CX5TW0nFJS$gU8g{5A-w3p8IX5jbS zmH0W&Yr*dY-b`nv*Xqo)U(!pL{Sw$jaBoBd8vaA%?* z+kUqwxI0l`*tdv+Es260`!-RqEm4qb-!2NaCkpcH_lbi05(W9r{H^prFFjmSu$>-X z?aXy%J8e$Gnd8jcLQi__eY$zd)YLO(@*I+_^oLpp*nb)97b5)&(xFrBuS?|KVa{QX zk=6dDcNp^ycMi3`)k|+*Oe35_HRsTLcQ3uyOCQ=l>7{>rhi@hW0A}8q`xx1r_FnqZ z3tj|7{!v8X(}yC1uIv@9+g(9v__?MD)l<^ zdRgPel;vRiY^DtRn@YPgRsC$0#@o+T&-=-=uwc(E_g5|Et8}4Eow*LSI8J#pB8ST zf;%z+cXV2~V-?)-3Amr6g*#Efot%I>H7(rf3hvAV+}Ua2ex~5gOThgiE!+hP?xFjd1jY2mI@a5tzlZm~D1=UY^In@aDHsdJbz^t)7XuS&P7 z^d6PotJ3>bxeBM>iKb%#?Ajp_53@TI&F`UZhsSY@3bjKo>l;H{^Jdw zRpq}|=^sr5*$(zXT7AUF&r1r<{!Lo2`0)8t0^KWV;o`&M)dbw@Y2o6-;>`ryTWR6q z=Kgj9?wz!7akKk-0`9%EaHq?92m7Z9hiPSS^ZYP@@8h)iPE`1AP-*|}_(YZe+r*QT z=*OHr^yBBM;g@DZBhk>cJ2zH4 zGwh#ivsJ9wFBJ_3~vUXEXZ$s?bt%?8lJ zW*Q%du2sZLkopA3iKAHIsKU`#?g}Ja0|#%AvgO-)d6T7PC5FeaGV;anQW7Pi-DosgSUB2j6hw1Xe!9p@Zpsv;nS%Qaj5^f?(gLrM5nm=N}Z!j z8lY_NqCV&QmG8stwdoRG+$T3D3GpY9w79Rc7> zrwf@R^`EUwOAT5s*NU-^QjTEr7mO~U(Mix3D`)|=IrE&PnqSHq=>p0@p?q`)zbvT< z^Gv%wB%^4t_q$CO$^F+xgWnv}?BT%?~&y{bn;+@%um$0kv3gHM+T4 zB)D26xEgcZTqGP^tqwdsrqt@d)#||2>cG|Nz{Tny_B{^%P%nRY2Y<*r$vMfX_wvV` zlUOhRopX|dKkMMXcj{~_|6?!zlPGw#m%r|ur1kQ@il?{l;P05#JNVpQ{$4Ntz;dt6 zTkOQJ`naZ~u;gN`E=i(Km1aH@lm#tk@fQAt@U{W}jo<|tP3(@qhNj&PmswLYf+l8! zcpJ?&c$*xPPHLOH!Q0r=wHfj)+6>#fHX{_hQ7RoH)3_(Cx$u|}w`g#b6BUZ$_B z+-Cp5+bmFR;yf-&ZhPS1ZI!d4tw?UOYVbCQCWxJnCRG*R#yhk_RPkCfl|xaOzOQC- z;pB|VzkXUg`1LF3kll<7Z^>qCKQTz!5h5to4lX;$;$L*xt`9M&N_w?*sjW8C1TSnU zd5AfuV9rS?=wq%+G1slqh)gm2UV-&RizV%UwDo%iQ9hJ*qXlZ@?Hh3oi%=zo%oeq>?M<^2d?frt|rzk334=?jYn ziXWHl#DT<5R(zeR(!tQ2u3*ko>DdF(sl`e=&jc|StwfhGHRWr+kkzTtB8Mt|tAGTGy1}j&lb7IPJr>W&hi(@sHDd$A7J#V~3 zn9MDk2}9tv)Mj_2P>L^}2`uJfwpHux)$UE|?me`6KqB3$J@{YIdRVo3R0IgLCnUGO z9Snm*GowrnO@x^?(<*P4GwVU^xl5?VX%#WMM`?&Nvse4W#fTUoeF5>|l}=sw7`ij7 zS9^Ig=s z=S5_CTHY-( zeI)NqnSPNk>kUEgBqB*qq=KGE1wD}pdPpTbkqUYu74$?Z=pmK#L@MZsRL~QtpeIs6 z4{84L{Fq3|$niMdl5)1mG@NsZOs~#)TBa}Na*=}Wa+w~Qdzwto%Y8(qPv;F6Dd^6Z z>GHheWqMlPy)u0y??ah>kuU4>mpk4N&yHO~EJLh_<%0eYL~$W^n@mAprl2oV&=;wo zFH_K$Dd@`-^hGM@%M|ow3i>hyeVKy3Nb{Eu87ER4C(Oau9B<(0a|GQrT8NPPV#N9n z#L>iZ96YSR!Np2)A)P?0sF+qG*1rZHHb02!=wO7u522Z~77^;h(8>qM7JMLwgO`Z5+Y8RZaFm1!Pk$2D$^cY$^Los@t zdgvos&p2&h4%)~j&?Z(*N3pqdG+Ru^umHXa9HQe{6dwUTl77NYpcC22bP_uoU-P|y zPGMKjsq7j$on237vYYS$-d;L~J&8};K1DxgFW}p?f2IrBo4DY%6JMA82fix%F2xPQkM81^(B1q>>gCsCJ9#^8<=bc*e~|9sPtbP$9No)bru+CC zbU%NG9^fBhGxQlfh|Ap%X;%7;mPZe3!{`xh3_Yq%qQ|r{Tv)B4C$wgIQkzS^)efcK zX>IhB)**QoSbA1FnV!=wqTg#*&>ytx=#ScM^t`r}UeI>Xi?~PflJ+{iti3~j z!Wq(^Ejqno$)~?qO6XNfIlX47qt`8q=?%*&ded?k{nfIO-m)A|f3uuQZ(~8;X}ONx zvD`-QS{|prTb`wVSYD#{EFaSQme1**8AKms45tq>T=Y>!34NTg5B)1+Kl&tNF@2iR zLI2L^rq41q(&rgJqc1WpqAxS9pszBv($^U~=$nkk=|35-)2@tna8C9iW0^YRnfXl1 z9L_A6E|!s5!ZI_6!bmtjv9xEprje&Rofi%(W~hvz_H;hFMu@&C>S9IK5;opColUUr%O+YEu}Ri;R%{Kk66*#w*?JC}V!e<}wO-DqS+}rK z>jSLJ`WSOtUt{Igovgz80rTiqR;lN)Dt#EM*4?Z|uVuCRbXKRYWcB)59M80~20hFg z^$n~^KaMr)r?BbzMQk7a3N}N(j?L6>W3%+FY_`6G&C#D@bM=?mzWN(%KmA=cPydMR zuYbV~$YN}MRu)^3RlpWzIoYDDacptcWOiUy1zVC;&z5G*V9T>suoYQr*vhOhTb*?} zTa$GQJ2>lQc1YGM?69n_*-Bd`^V@P*z*flGY@=9*Z7d7grm}Um26nh@9t+v#vrgM8 z7Pk3Vx9tRWgzYRAv0ch~Y*(`Nwj0<6+cvh*_9EM4dy5@u`+yx~`3J&&D|y@;KfeGoe>yPch$9c5=^AI;9p#)!>6lbw_O3-+_@OWC>E zzh*zrzLTAweGj`J`yqB=_LJ=5?5Ef-v;W90&3=Vlmi;#SRrdSr^6XF971`ggD{*Rg zm66G=HiocY8%}nOF#*qI>^h^GU2in88;m*ZMq>fH$ym;AHV$F87y)*x(aCNzdf4s8 zG3*ZGBzC897Q4$hpWSU-#_FWksCyc9FR&ML zUSu!jyvAP6*~$Kt^9ky{;A3++KP}hB{+yf5UO{~CFS#SxtGVOZYq?X|>$x8GMy{8= znY%9^lRF=<%K>```)lr@?5*53_P5+F_IB=3s5>5Yr=jj#wlnuU_D=34?A_d}*xz$+ zVE@QP_$2pU)IWmyr`UVBPqX)PUts^teU*Kf`!@SH_kH%S+)vpjx!gJ^Q-cvjy?|Gh?_a^FgqV7Y~ea@|U zUvNF2@vQtTo|B)?bMuGuynGkW&oAKx`EG8{ujWJZ!Am}P$p`7yw30?bK#bp9#GpNahW+?9V3@|PihEgzSEGcU@&n~%@Gk59;dgip+W ziBHOZofqf7!%OnN;*$$BKBZtNpIR`6Pb-+j%L=A)cfnHbDOkfR3tD(pK@YDkIEL30 zoW$!2&gAt4ck#x8?YybrH@vyvMLxaYHNH>5$9zV?mwcw3^I3KqpKTw*=h!Flx%M)? zuYCsJ&pwaOvmeO!x3A_4?T7J2_71+--pvoRZ{SPp$MU82lle0H*?hVE0=~ljE56cx zJzr(Nov*fU<7?~>@`LP8@I&lR^F!^=bD#Yc-eP}?x7y$1e)}gp;4pZb!@=7fBYB5o zJP$gi@O2IkKiuKvA;(PK>6pj690&5SV>Rz~9LA4ubnu9yn@1fRdCYMf?{S>M*E`O^ zR_X%2(eW$3$#E?|(s2(z%JCpS+VKQG#_=3K*6}hw&hZ96-tjg+!SPT26UWE=r;c6x zM7*kc(vV^N$DGXVdEqFhjbTm*^Jv?qh90;=CD6aTan9Q6YTeCA0d~^ zs_i$?y~yRUx%OYu{mA9B#rAWxkF|eM0SnlV);`fbB|AIWevtNW?KAwzg|qETwa)=p z$S$xSpnZYdPii3G{>S_ zvdCrIMl}{2a^q}oQMM)9Vo*``Sp6Y>7Vx>UEA?CW8OV*xo~8eap9aiD+4J;^_({l( z&t9aT$4^0Sfir8aHL!Y{{|Y;*alqk`b`vS@Nma_)1%2DF9qazMC>FcH}1K zucXlyhh+$r*q>$&OCe}Xbi7RsmZ2!ivJ8W7I+VELeRzU*Uso~!l zEie+7eC*utLm)lohd_$I^Y#6U|9?pTPp9AWi26U1`v2LaKBGr|hmW5QXZU5G<|k7$88fq3lqi8#(L zf`;>HoJ;5BS_Ue~}UDR_{{*cu&ig_#X?<-nT*&u!@8!Gdr?!w;@E&d@H zlB@{oP^b3l*JQzO<}O^A{~uDd=Y-UF{2vij8zB6pIXPYbZ>fGoYrdkx4*5}Ne@3%^ z6uO^N(=NJ+CjOw7OgqJtfF9*PJRxO4pV1BfV~V@*ennP7jhMAoAzY*a3vvkW!mBX0 z-2h;03l$wpxnEM&{&>PV!^H=K)Tt4H1V0SKS`o zVHxeUI4#q9Ex6@2b%({(YbnBYH_LcjnVKeVOj&UEtJhMpQv6QFWsY3Y7!Mj*Zc1de zn?OU8FY}a%H*5IMC&wM-+62+ts(#=fQnR?BGO;wwVmS#I35 z`%ZFS4Y{v@+}A>GT&=|6TqVB&a^DEKZ-U%6L+)E4_if)wZg*O_@!_NIEO+^Ll)D#l zZ-w03Aoo3x`(DU>ALPCtaz6mMcR=okAop*+mE4}Ra^vew-&yX;?-hcva(@K5KZe}@g4~}%?teq>&mi~b zkoybB{T1Z?`di6epH^;s6zj*9TjP|WS!jrsK_j$GeEvbFN-c}(H5<*;vgrWLpaohk zt-!?s@sj3$JD+>g%8ie5{pfNRLhfOZdpP8FLhg}}dlcjz4Y|ia?y-=29ON$gR&qC_ zl^b99`qAYsf!tFd_f*I|4RV)3Za3sEhujsA+XK0)Ab0h*lDjdj-1v;xk1lruO_u-Jc6LNQb zE4inql^Y*u`_bi&LGJaCdjsU&2)U1h+($v~qapV(ko#E3eLUno;akbQPdd3RGq5sn z%S_8G+zdQu>4F)HZ3Dk{V8h>CwM|*-lF3}#R9AW5H>kyT*KG15?(2hr4N>v8%FS<_ z%ZjP43jDQK{P8RN85h7MWv9A4E_}hYM}A4s)|Z>=s>I&~!q;of7D?GD^KBK=s=f8q zwuX50Q zR%xp)pN6VBWJFRwt=d*yJI&**sIRhB*CVL`OilT;N@VM8HI-gZxkr>#p~O>OZL6t8 zQYT9AHiC81;Y$@I9>_ zxmrT8hHOhbLG zpjqXC0_we0mGw}%t+re=2F2P6Z%uXGG?5V{AX)42*3{OD5=o$@%2r$Ht$}V)QYm4; zUzNAEqFR(xiIQ56t+v`*TUjMage+CH$kZVby@pI}b)~41GSmTKy|)(FQ6hz@tFzTr zcx&q`P*Nw^s7H55TJT0}o&^bzABHn7~qOR6k=cyMZ!VId*Yi)IP-a4oX zC8CR~p_ck`Z(WTLr(TF$U4cx6w+<*oMwEEUZS{gywFf;e)Ljit)uRWz)m1Rp@^YlI zKg%n;)zz?KlmQyLt0=D&;OH+{g}1uKgR~ZDrReGMI^+b5u#D;&z`*LOp{WXVMm01E zJQb*^6*bj@zEoC4g%qVu)YgkqD5t_xE^yRioQy|1Px# literal 0 HcmV?d00001 diff --git a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java index 7759d4d..aee9f15 100644 --- a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java +++ b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java @@ -17,7 +17,10 @@ package com.intergral.deep.plugins.cf; -public class Utils { +public final class Utils { + + private Utils() { + } public static boolean isCFServer() { return System.getProperty("sun.java.command").contains("coldfusion"); diff --git a/pom.xml b/pom.xml index 78b4f1a..1b8641c 100644 --- a/pom.xml +++ b/pom.xml @@ -374,6 +374,10 @@ **/java/com/intergral/deep/** + + **/*InsnPrinter.* + + **/com/intergral/deep/agent/Agent.* @@ -413,6 +417,10 @@ **/java/com/intergral/deep/** + + **/*InsnPrinter.* + + **/com/intergral/deep/agent/Agent.* diff --git a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java index 7f15026..9beed43 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java @@ -25,7 +25,10 @@ import java.util.Objects; import java.util.Optional; -public class SnapshotUtils { +public final class SnapshotUtils { + + private SnapshotUtils() { + } /** * Scan a snapshot for a variable with the given name @@ -37,6 +40,7 @@ public class SnapshotUtils { public static IVariableScan findVarByName(final String name, final Snapshot snapshot) { return findVarByName(name, snapshot.getFrames(0).getVariablesList(), snapshot.getVarLookupMap()); } + /** * Scan a snapshot for a variable with a given name. * From 24bec9ab91912de3673a18c9d006c3988f7e7285 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 1 Sep 2023 00:04:09 +0100 Subject: [PATCH 12/15] chore(tests): correct use of logger in SmapUtils --- .../tracepoint/inst/jsp/sourcemap/SmapUtils.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java index b24ad6c..268ceaf 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SmapUtils.java @@ -24,16 +24,12 @@ import java.net.URL; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -/** - * Use getResource and getResourceAsStream from class loaders to get Debug Source of loaded - * classes. - * - * @author nwightma - * @since 1.0.2 - */ public final class SmapUtils { + private final static Logger LOGGER = LoggerFactory.getLogger(SmapUtils.class); private SmapUtils() { } @@ -49,7 +45,7 @@ public static String lookUp(final Class c) { final InputStream in = resource.openStream(); return parseStream(in); } catch (Throwable t) { - t.printStackTrace(); + LOGGER.error("Could not parse source map for class {}", c.getName(), t); } } else { try { @@ -58,7 +54,7 @@ public static String lookUp(final Class c) { return parseStream(in); } } catch (Throwable t) { - t.printStackTrace(); + LOGGER.error("Could not parse source map for class {}", c.getName(), t); } } @@ -72,7 +68,7 @@ public static String lookUp(final Class c) { final InputStream in = resource.openStream(); return parseStream(in); } catch (Throwable t) { - t.printStackTrace(); + LOGGER.error("Could not parse source map for class {}", c.getName(), t); } } From 308b714ebbd0521c0bfb9cf6d16a3aec9ad50dee Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 1 Sep 2023 13:07:27 +0100 Subject: [PATCH 13/15] chore(lint): correct lint errors --- .../intergral/deep/agent/api/DeepVersion.java | 2 +- .../deep/agent/api/DeepRuntimeException.java | 14 + .../deep/agent/api/IRegistration.java | 2 +- .../deep/agent/api/auth/AuthProvider.java | 17 +- .../agent/api/auth/BasicAuthProvider.java | 12 + .../deep/agent/api/auth/IAuthProvider.java | 6 +- .../deep/agent/api/hook/IDeepHook.java | 13 + .../agent/api/plugin/AbstractEvaluator.java | 16 + .../agent/api/plugin/EvaluationException.java | 4 +- .../deep/agent/api/plugin/IEvaluator.java | 20 ++ .../deep/agent/api/plugin/IPlugin.java | 9 +- .../agent/api/plugin/ISnapshotContext.java | 2 +- .../deep/agent/api/plugin/LazyEvaluator.java | 13 +- .../agent/api/reflection/IReflection.java | 96 +++++- .../agent/api/reflection/ReflectionUtils.java | 37 ++- .../api/resource/ResourceAttributes.java | 3 + .../deep/agent/api/settings/ISettings.java | 29 +- .../agent/api/tracepoint/ITracepoint.java | 32 ++ .../deep/agent/api/utils/ArrayIterator.java | 5 + .../agent/api/utils/ArrayObjectIterator.java | 3 + .../agent/api/utils/CompoundIterator.java | 5 + .../deep/agent/api/auth/AuthProviderTest.java | 8 - .../java/com/intergral/deep/agent/Agent.java | 9 +- .../com/intergral/deep/agent/AgentImpl.java | 20 +- .../com/intergral/deep/agent/DeepAgent.java | 13 + .../com/intergral/deep/agent/IDUtils.java | 8 + .../intergral/deep/agent/ReflectionUtils.java | 3 + .../java/com/intergral/deep/agent/Utils.java | 20 +- .../deep/agent/grpc/GrpcService.java | 16 + .../intergral/deep/agent/logging/Logger.java | 8 + .../deep/agent/plugins/PluginLoader.java | 10 + .../deep/agent/poll/DriftAwareThread.java | 7 +- .../intergral/deep/agent/poll/ITimerTask.java | 7 +- .../deep/agent/poll/LongPollService.java | 9 + .../deep/agent/push/PushService.java | 9 + .../intergral/deep/agent/push/PushUtils.java | 15 + .../agent/resource/JavaResourceDetector.java | 3 + .../deep/agent/resource/ResourceDetector.java | 10 + .../deep/agent/resource/SpiUtil.java | 3 + .../deep/agent/settings/Settings.java | 86 ++++- .../agent/tracepoint/ITracepointConfig.java | 2 +- .../tracepoint/TracepointConfigService.java | 19 ++ .../agent/tracepoint/TracepointUtils.java | 5 +- .../deep/agent/tracepoint/cf/CFEvaluator.java | 6 + .../agent/tracepoint/cf/CFFrameProcessor.java | 6 +- .../deep/agent/tracepoint/cf/CFUtils.java | 82 ++++- .../evaluator/EvaluatorService.java | 8 + .../evaluator/NashornReflectEvaluator.java | 6 + .../agent/tracepoint/handler/Callback.java | 279 ++++++++-------- .../tracepoint/handler/FrameCollector.java | 12 +- .../agent/tracepoint/handler/FrameConfig.java | 44 +++ .../tracepoint/handler/FrameProcessor.java | 10 +- .../tracepoint/handler/VariableProcessor.java | 17 +- .../agent/tracepoint/handler/bfs/Node.java | 53 ++- .../agent/tracepoint/inst/CFClassScanner.java | 7 +- .../inst/CompositeClassScanner.java | 13 +- .../agent/tracepoint/inst/IClassScanner.java | 14 + .../deep/agent/tracepoint/inst/InstUtils.java | 59 +++- .../tracepoint/inst/JSPClassScanner.java | 7 + .../tracepoint/inst/SetClassScanner.java | 3 + .../TracepointInstrumentationService.java | 56 ++-- .../agent/tracepoint/inst/asm/ClassInfo.java | 21 +- .../inst/asm/ClassInfoNotFoundException.java | 3 + .../inst/asm/ClassLoaderAwareClassWriter.java | 4 + .../tracepoint/inst/asm/SkipException.java | 3 + .../tracepoint/inst/asm/TransformerUtils.java | 73 ++-- .../agent/tracepoint/inst/asm/Visitor.java | 311 +++++++++--------- .../inst/jsp/JSPMappedBreakpoint.java | 3 + .../agent/tracepoint/inst/jsp/JSPUtils.java | 44 ++- .../inst/jsp/sourcemap/SmapUtils.java | 3 +- .../inst/jsp/sourcemap/SourceMap.java | 2 +- .../deep/agent/types/TracePointConfig.java | 107 +++++- .../agent/types/snapshot/EventSnapshot.java | 26 ++ .../deep/agent/types/snapshot/StackFrame.java | 16 + .../deep/agent/types/snapshot/Variable.java | 11 + .../deep/agent/types/snapshot/VariableID.java | 11 + .../agent/types/snapshot/WatchResult.java | 16 + .../com/intergral/deep/ProxyCallback.java | 4 +- .../runtime/ArgumentCollection.java | 4 +- .../test/java/coldfusion/runtime/CFPage.java | 5 +- .../java/coldfusion/runtime/CfJspPage.java | 8 +- .../java/coldfusion/runtime/TestScope.java | 4 +- .../java/coldfusion/runtime/UDFMethod.java | 13 +- .../java/coldfusion/tagext/io/OutputTag.java | 11 +- .../com/intergral/deep/agent/AgentTest.java | 24 +- .../intergral/deep/agent/DeepAgentTest.java | 1 - .../com/intergral/deep/agent/IDUtilsTest.java | 3 +- .../com/intergral/deep/agent/UtilsTest.java | 7 +- .../deep/agent/plugins/PluginLoaderTest.java | 1 - .../deep/agent/poll/DriftAwareThreadTest.java | 11 +- .../deep/agent/poll/LongPollServiceTest.java | 4 +- .../agent/tracepoint/TracepointUtilsTest.java | 6 +- .../agent/tracepoint/cf/CFEvaluatorTest.java | 1 - .../deep/agent/tracepoint/cf/CFUtilsTest.java | 10 +- .../NashornReflectEvaluatorTest.java | 27 +- .../tracepoint/handler/bfs/NodeTest.java | 9 +- .../TracepointInstrumentationServiceTest.java | 5 +- .../asm/ClassInfoNotFoundExceptionTest.java | 5 +- .../tracepoint/inst/asm/VisitorTest.java | 53 +-- .../tracepoint/inst/jsp/JSPUtilsTest.java | 75 +++-- .../jsp/sourcemap/FileSectionEntryTest.java | 69 ++-- .../jsp/sourcemap/LineSectionEntryTest.java | 40 ++- .../jsp/sourcemap/SouceMapParserTest.java | 283 ---------------- .../jsp/sourcemap/SourceMapParserTest.java | 296 +++++++++++++++++ .../deep/test/MockTracepointConfig.java | 3 + .../deep/test/target/BPSuperClass.java | 10 +- .../deep/test/target/ConditionTarget.java | 1 + .../deep/{ => test/target}/Person.java | 2 +- .../test/target/VariableSuperSuperTest.java | 1 + .../lucee/commons/color/ConstantsDouble.java | 3 +- .../test/java/lucee/runtime/PageContext.java | 75 +++-- .../java/lucee/runtime/PageContextImpl.java | 11 +- .../src/test/java/lucee/runtime/PageImpl.java | 13 +- .../interpreter/VariableInterpreter.java | 2 +- .../test/java/lucee/runtime/op/Caster.java | 8 +- .../lucee/runtime/scope/MockLuceeScope.java | 4 +- .../java/lucee/runtime/type/Collection.java | 5 +- .../runtime/type/ref/VariableReference.java | 3 +- .../java/railo/commons/lang/PCLBlock.java | 2 +- agent/src/test/resources/logging.properties | 17 + checkstyle-suppressions.xml | 31 +- checkstyle.xml | 2 +- .../intergral/deep/plugins/cf/CFPlugin.java | 7 +- .../com/intergral/deep/plugins/cf/Utils.java | 15 + .../com/intergral/deep/plugin/JavaPlugin.java | 5 +- pom.xml | 1 + .../deep/reflect/ReflectionImpl.java | 5 +- .../deep/reflect/Java9ReflectionImpl.java | 3 + .../com/intergral/deep/tests/AssertUtils.java | 8 + .../deep/tests/grpc/TestInterceptor.java | 2 +- .../deep/tests/inst/ByteClassLoader.java | 85 +++-- .../deep/tests/snapshot/SnapshotUtils.java | 2 +- 132 files changed, 2226 insertions(+), 1045 deletions(-) delete mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java create mode 100644 agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParserTest.java rename agent/src/test/java/com/intergral/deep/{ => test/target}/Person.java (96%) diff --git a/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java b/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java index 2817720..85864a7 100644 --- a/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java +++ b/agent-api/src/main/java-templates/com/intergral/deep/agent/api/DeepVersion.java @@ -19,5 +19,5 @@ public interface DeepVersion { - public static final String VERSION = "${project.version}"; + String VERSION = "${project.version}"; } \ No newline at end of file diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java b/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java index d8b7deb..791d397 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/DeepRuntimeException.java @@ -17,12 +17,26 @@ package com.intergral.deep.agent.api; +/** + * A general exception used by deep to throw {@link RuntimeException}. + */ public class DeepRuntimeException extends RuntimeException { + /** + * Create a new exception with a message. + * + * @param message the error message + */ public DeepRuntimeException(final String message) { super(message); } + /** + * Create a new exception with a message and cause. + * + * @param message the error message + * @param cause the cause of the error + */ public DeepRuntimeException(final String message, final Throwable cause) { super(message, cause); } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java b/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java index df32f9c..c0b8305 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java @@ -28,7 +28,7 @@ public interface IRegistration { void unregister(); /** - * Get the registered item + * Get the registered item. * * @return the item that this registration is for. */ diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java index f0abd0d..720f1c3 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/AuthProvider.java @@ -26,10 +26,25 @@ import java.util.Collections; import java.util.Map; -public class AuthProvider { +/** + * A utility class to load the configured {@link IAuthProvider}. + */ +public final class AuthProvider { + + private AuthProvider() { + } private static final NoopProvider NOOP_PROVIDER = new NoopProvider(); + /** + * Load the configured {@link IAuthProvider}. + *

    + * This will always return a {@link IAuthProvider}. + * + * @param settings the current settings + * @param reflection the reflection service that is available + * @return the loaded {@link IAuthProvider} + */ public static IAuthProvider provider(final ISettings settings, final IReflection reflection) { final String serviceAuthProvider = settings.getSettingAs("service.auth.provider", String.class); if (serviceAuthProvider == null || serviceAuthProvider.trim().isEmpty()) { diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/BasicAuthProvider.java b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/BasicAuthProvider.java index 6a537c4..819a141 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/BasicAuthProvider.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/BasicAuthProvider.java @@ -22,6 +22,18 @@ import java.util.Collections; import java.util.Map; +/** + * This is an {@link IAuthProvider} that will attach basic authorization to the outbound requests. + *

    + * This provider can be set using {@code service.auth.provider=com.intergral.deep.agent.api.auth.BasicAuthProvider}. The username and + * password that is configured onto requests can be set with the setting: + *

      + *
    • {@code service.username=yourusername}
    • + *
    • {@code service.password=yourpassword}
    • + *
    + *

    + * These values are then base64 encoded and attached to the outbound requests as the {@code authorization} header. + */ public class BasicAuthProvider implements IAuthProvider { private final ISettings settings; diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/IAuthProvider.java b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/IAuthProvider.java index dda8c64..dbbfcd9 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/auth/IAuthProvider.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/auth/IAuthProvider.java @@ -19,10 +19,14 @@ import java.util.Map; +/** + * Allows for custom auth providers to be configured. These can be provided as an instantiatable class using the class name via the setting + * {@code service.auth.provider}. Alternatively a plugin can be configured as an auth provider. + */ public interface IAuthProvider { /** - * Provide the headers that should be attached to the GRPC calls + * Provide the headers that should be attached to the GRPC calls. * * @return a Map of the header values */ diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/hook/IDeepHook.java b/agent-api/src/main/java/com/intergral/deep/agent/api/hook/IDeepHook.java index 291f146..72d7044 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/hook/IDeepHook.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/hook/IDeepHook.java @@ -20,9 +20,22 @@ import com.intergral.deep.agent.api.IDeep; import com.intergral.deep.agent.api.reflection.IReflection; +/** + * This type is used to pass an object from the agent to the API. It should not be directly used by clients. + */ public interface IDeepHook { + /** + * Get the deep service from the agent. + * + * @return the deep agent. + */ IDeep deepService(); + /** + * Get the configured reflection api. + * + * @return the reflection api. + */ IReflection reflectionService(); } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/AbstractEvaluator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/AbstractEvaluator.java index f5b5030..80bc961 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/AbstractEvaluator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/AbstractEvaluator.java @@ -34,6 +34,22 @@ public boolean evaluate(final String expression, final Map value } + /** + * Given an input convert to a boolean expression. + *

    + * As Java doesn't have inherit truthiness such as JavaScript. We want to simulate it here, we will convert the following to inputs, all + * other inputs are {@code true}. + *

      + *
    • null - All null values are {@code false}.
    • + *
    • boolean = All booleans are returned as they are.
    • + *
    • 0 = All numbers are {@code true}, except 0 which is {@code false}
    • + *
    • "true" = A string of the value {@code "true"} (ignoring case) is {@code true}, all other strings are {@code false}.
    • + *
    + * + * @param obj the value to convert + * @return the value as a boolean + * @see Boolean#parseBoolean(String) + */ public static boolean objectToBoolean(final Object obj) { if (obj == null) { return false; diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java index ccb22b1..7058b90 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java @@ -28,7 +28,7 @@ public class EvaluationException extends Exception { private final String expression; /** - * Create a new exception + * Create a new exception. * * @param expression the expression that failed * @param cause the failure @@ -39,7 +39,7 @@ public EvaluationException(final String expression, final Throwable cause) { } /** - * Get the expression + * Get the expression. * * @return {@link #expression} */ diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEvaluator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEvaluator.java index 919a1c0..a74ffec 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEvaluator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEvaluator.java @@ -19,10 +19,30 @@ import java.util.Map; +/** + * This defines an evaluator, and evaluator is used to evaluate expression at runtime. ie allows a watch or condition to execute within the + * scope of the tracepoint. + */ public interface IEvaluator { + /** + * Evaluate an expression as a boolean response. + * + * @param expression the expression to evaluate + * @param values the variables that the expression can evaluate against + * @return {@code true} if the expression evaluates to truthy value. + * @see AbstractEvaluator#objectToBoolean(Object) + */ boolean evaluate(final String expression, final Map values); + /** + * Evaluate an expression to the value. + * + * @param expression the expression to evaluate + * @param values the variables that the expression can evaluate against + * @return the result of the expression + * @throws Throwable if the expression fails + */ Object evaluateExpression(final String expression, final Map values) throws Throwable; } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java index 1a05365..4134e11 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java @@ -52,7 +52,8 @@ default String name() { * Is this plugin active. *

    * By default, this will check the Deep settings for the plugin name. e.g. the setting com.intergral.deep.plugin.JavaPlugin.active=false - * will disable the JavaPlugin. The normal settings rules apply, e.g. deep. or DEEP_ as a prefix when using system properties or environment variables. + * will disable the JavaPlugin. The normal settings rules apply, e.g. deep. or DEEP_ as a prefix when using system properties + * or environment variables. * * @param settings the current deep settings. * @return {@code false} if setting is 'false', otherwise {@code true} @@ -73,10 +74,10 @@ default boolean isActive(final ISettings settings) { interface IPluginRegistration extends IRegistration { /** - * Indicates if this plugin is currently set to be the auth provider + * Indicates if this plugin is currently set to be the auth provider. * - * @return {@code true} if the registered plugin is an {@link com.intergral.deep.agent.api.auth.IAuthProvider} and deep is configured to - * use this provider, else {@code false} + * @return {@code true} if the registered plugin is an {@link com.intergral.deep.agent.api.auth.IAuthProvider} and deep is configured + * to use this provider, else {@code false} */ boolean isAuthProvider(); } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java index ba1f694..2ec8354 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java @@ -23,7 +23,7 @@ public interface ISnapshotContext { /** - * Evaluate an expression in the frame of the tracepoint that triggered this snapshot + * Evaluate an expression in the frame of the tracepoint that triggered this snapshot. * * @param expression the express to evaluate * @return the result of the expression as a string diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java index e4d41ab..dd9821b 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/LazyEvaluator.java @@ -27,7 +27,7 @@ */ public class LazyEvaluator extends AbstractEvaluator { - private final static Logger LOGGER = LoggerFactory.getLogger(LazyEvaluator.class); + private static final Logger LOGGER = LoggerFactory.getLogger(LazyEvaluator.class); private static final Exception NO_EVALUATOR_EXCEPTION = new RuntimeException( "No evaluator available."); private final IEvaluatorLoader loader; @@ -51,7 +51,7 @@ public Object evaluateExpression(final String expression, final Map clazz, final Field field); - + /** + * Set the method as accessible. + * + * @param clazz the clazz the field is on + * @param method the method to access + * @return could we complete the operation + * @see Method#setAccessible(boolean) + */ boolean setAccessible(final Class clazz, final Method method); + /** + * Set the constructor as accessible. + * + * @param clazz the clazz the field is on + * @param constructor the constructor to set + * @return could we complete the operation + * @see Constructor#setAccessible(boolean) + */ boolean setAccessible(final Class clazz, final Constructor constructor); - + /** + * Call a method on the target object. + *

    + * This will look for the method using {@link #findMethod(Class, String, Class[])} using the input arguments as the argument types of the + * method. The method will then be invoked on the target argument. + * + * @param target the target instance to call the method on + * @param methodName the name of the method to call + * @param args the arguments to the method + * @param the type of the response + * @return the response of the method + */ T callMethod(Object target, String methodName, Object... args); - + /** + * Scan the hierarchy of the input class for a method with the given name. This will scan declared methods and methods, as well as + * stepping up the super class. + * + * @param clazz the class to start the scan on + * @param methodName the name of the method to look for + * @param argTypes the argument types in the method signature + * @return the discovered method or {@code null} + */ Method findMethod(Class clazz, String methodName, Class... argTypes); - + /** + * Get a filed from the target. + * + * @param target the target instance to look on. + * @param fieldName the name of the field to get + * @return the field, or {@code null} + */ Field getField(Object target, String fieldName); /** - * Get a field from an object + * Get a field from an object. * * @param target the object to look at * @param fieldName the field name to look for @@ -56,13 +104,49 @@ public interface IReflection { */ T getFieldValue(Object target, String fieldName); + /** + * Get an iterator that will iterator over all the available fields on the given class. + * + * @param clazz the class to scan + * @return the iterator for the fields + */ Iterator getFieldIterator(Class clazz); + /** + * Call a field on a target. + * + * @param target the object to get the field from + * @param field the field to call + * @param the type of the return + * @return the value of the field + */ T callField(Object target, Field field); + /** + * Get the modifier names of a field. + * + * @param field the field to look at + * @return a set of strings that represent the modifiers. + */ Set getModifiers(Field field); + /** + * Find a constructor on the given class. + * + * @param clazz the class to look on + * @param args the arguments for the constructor + * @return the constructor or {@code null} + */ Constructor findConstructor(final Class clazz, final Class... args); + /** + * Call a constructor with the arguments. + * + * @param constructor the constructor to call + * @param args the arguments for the constructor + * @param the type of the return + * @return the new object, or {@code null} + * @throws DeepRuntimeException if we could not create a new object + */ T callConstructor(final Constructor constructor, final Object... args) throws DeepRuntimeException; } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java index 28a1e19..0353c92 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/reflection/ReflectionUtils.java @@ -21,11 +21,24 @@ import com.intergral.deep.agent.api.settings.ISettings; import java.lang.reflect.Constructor; +/** + * A simple util to get create plugins. A plugin can have a default constructor or one that accepts the {@link ISettings} class. So this + * type helps us find and call the appropriate constructor. + */ public final class ReflectionUtils { private ReflectionUtils() { } + /** + * Call the constructor. + * + * @param constructor the constructor + * @param settings the settings object + * @param reflection the reflection service to use + * @param the type of the new object + * @return the new object + */ public static T callConstructor(final Constructor constructor, final ISettings settings, final IReflection reflection) { if (constructor.getParameterTypes().length == 0) { return reflection.callConstructor(constructor); @@ -33,20 +46,34 @@ public static T callConstructor(final Constructor constructor, final ISet return reflection.callConstructor(constructor, settings); } - public static Constructor findConstructor(final Class aClass, final IReflection reflection) { + /** + * Find the constructor to use. + *

    + * Will look for the constructor that we can use in the order: + *

      + *
    • constructor(ISettings.class)
    • + *
    • constructor()
    • + *
    + * + * @param clazz the class to look on + * @param reflection the reflection service to use + * @return the constructor that was found + * @throws DeepRuntimeException if we could not find a constructor + */ + public static Constructor findConstructor(final Class clazz, final IReflection reflection) { - final Constructor constructor = reflection.findConstructor(aClass, ISettings.class); + final Constructor constructor = reflection.findConstructor(clazz, ISettings.class); if (constructor != null) { return constructor; } - final Constructor defaultConstructor = reflection.findConstructor(aClass); + final Constructor defaultConstructor = reflection.findConstructor(clazz); if (defaultConstructor != null) { return defaultConstructor; } - final String simpleName = aClass.getSimpleName(); + final String simpleName = clazz.getSimpleName(); throw new DeepRuntimeException( - String.format("Cannot create auth provider of type: %s. Class is missing constructor %s(%s) or %s().", aClass.getName(), + String.format("Cannot create auth provider of type: %s. Class is missing constructor %s(%s) or %s().", clazz.getName(), simpleName, ISettings.class.getName(), simpleName)); } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java index 8f04842..bdcbe5c 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/resource/ResourceAttributes.java @@ -5,6 +5,9 @@ package com.intergral.deep.agent.api.resource; +/** + * A collection of known keys that are used in the attributes. + */ public interface ResourceAttributes { /** diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java index a9edd13..75e2e4f 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/settings/ISettings.java @@ -21,10 +21,13 @@ import com.intergral.deep.agent.api.resource.Resource; import java.util.Map; +/** + * The exposed API for the Deep settings. + */ public interface ISettings { /** - * This is the settings key for the configured auth provider + * This is the settings key for the configured auth provider. */ String KEY_AUTH_PROVIDER = "service.auth.provider"; @@ -34,17 +37,17 @@ public interface ISettings { String KEY_ENABLED = "enabled"; /** - * This is the setting key for the service url + * This is the setting key for the service url. */ String KEY_SERVICE_URL = "service.url"; /** - * This is the setting key for the service secure setting + * This is the setting key for the service secure setting. */ String KEY_SERVICE_SECURE = "service.secure"; /** - * This is the setting key for the plugin list + * This is the setting key for the plugin list. */ String PLUGINS = "plugins"; @@ -70,12 +73,26 @@ public interface ISettings { */ String APP_FRAMES_EXCLUDES = "in.app.include"; + /** + * Get a setting from the config as a given type. + * + * @param key the key for the setting + * @param clazz the type to return as + * @param the type to return as + * @return the value as the given type + */ T getSettingAs(String key, Class clazz); - Map getMap(String attributeProperty); + /** + * Get the property as a map. + * + * @param key the for the setting + * @return the value as a map + */ + Map getMap(String key); /** - * Returns the resource that describes this client + * Returns the resource that describes this client. * * @return the {@link Resource} */ diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java index 3f3cce0..8018d65 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java @@ -21,18 +21,50 @@ import java.util.Collection; import java.util.Map; +/** + * This type describes a tracepoint that has been attached via code, using + * {@link com.intergral.deep.agent.api.IDeep#registerTracepoint(String, int)}. + */ public interface ITracepoint { + /** + * Get the tracepoint path. + * + * @return the tracepoint path + */ String path(); + /** + * Get the tracepoint line number. + * + * @return the line number + */ int line(); + /** + * Get the args for the tracepoint. + * + * @return the args on the tracepoint + */ Map args(); + /** + * Get the tracepoint watches. + * + * @return the configured watches + */ Collection watches(); + /** + * The generated ID for the tracepoint. + * + * @return the tracepoint id + */ String id(); + /** + * Defines the tracepoint registration. + */ interface ITracepointRegistration extends IRegistration { } diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayIterator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayIterator.java index 94d7a09..833b210 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayIterator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayIterator.java @@ -19,6 +19,11 @@ import java.util.Iterator; +/** + * An iterator that will iterate an array. + * + * @param the type of the objects in the array + */ public class ArrayIterator implements Iterator { private final T[] value; diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayObjectIterator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayObjectIterator.java index f39cbe4..ca56559 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayObjectIterator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/ArrayObjectIterator.java @@ -20,6 +20,9 @@ import java.lang.reflect.Array; import java.util.Iterator; +/** + * An iterator that will iterate an array, but does so using the {@link Array} class. + */ public class ArrayObjectIterator implements Iterator { private final int length; diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java index c261a08..80a7901 100644 --- a/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java +++ b/agent-api/src/main/java/com/intergral/deep/agent/api/utils/CompoundIterator.java @@ -20,6 +20,11 @@ import java.util.Iterator; import java.util.NoSuchElementException; +/** + * An iterator that can iterate across multiple iterators. + * + * @param the type of the object in the iterators + */ public class CompoundIterator implements Iterator { private final Iterator[] iterators; diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java index c43255d..8658a53 100644 --- a/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java +++ b/agent-api/src/test/java/com/intergral/deep/agent/api/auth/AuthProviderTest.java @@ -18,7 +18,6 @@ package com.intergral.deep.agent.api.auth; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import com.intergral.deep.agent.api.auth.AuthProvider.NoopProvider; import com.intergral.deep.agent.api.plugin.IPlugin; @@ -33,13 +32,6 @@ class AuthProviderTest { - @Test - void coverage() { - // for coverage - //noinspection ObviousNullCheck,InstantiationOfUtilityClass - assertNotNull(new AuthProvider()); - } - @Test void canProvide() { final ISettings settings = Mockito.mock(ISettings.class); diff --git a/agent/src/main/java/com/intergral/deep/agent/Agent.java b/agent/src/main/java/com/intergral/deep/agent/Agent.java index 2c3283a..bac01e1 100644 --- a/agent/src/main/java/com/intergral/deep/agent/Agent.java +++ b/agent/src/main/java/com/intergral/deep/agent/Agent.java @@ -27,13 +27,16 @@ import java.util.Map; import java.util.jar.JarFile; +/** + * This is the main entry point for the Deep agent. + */ public final class Agent { private Agent() { } /** - * This is called when the agent is dynamically attached to the VM + * This is called when the agent is dynamically attached to the VM. * * @param arg the agent args * @param inst a system instrumentation @@ -46,7 +49,7 @@ public static void agentmain(final String arg, final Instrumentation inst) { /** - * This is called when the agent is attached from the CLI + * This is called when the agent is attached from the CLI. * * @param arg the agent args * @param inst a system instrumentation @@ -58,7 +61,7 @@ public static void premain(final String arg, final Instrumentation inst) { } /** - * A common start for NV + * A common start for NV. * * @param args the NV args * @param inst a system instrumentation diff --git a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java index 5bd52c4..94c4bd2 100644 --- a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java +++ b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java @@ -27,7 +27,9 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; -@SuppressWarnings("unused") +/** + * This type is called from the {@link Agent} via reflection to load the agent after the jar we are in has been attached to the class path. + */ public class AgentImpl { private AgentImpl() { @@ -36,7 +38,12 @@ private AgentImpl() { private static final CountDownLatch LATCH = new CountDownLatch(1); private static DeepAgent deepAgent; - // called via reflection + /** + * Start the deep agent. + * + * @param inst the instrumentation object + * @param args the agent arguments + */ public static void startup(final Instrumentation inst, final Map args) { final Settings settings = Settings.build(args); Logger.configureLogging(settings); @@ -53,7 +60,7 @@ public static void startup(final Instrumentation inst, final Map } /** - * await the load of the dep api + * await the load of the dep api. * * @return the loaded api object * @throws InterruptedException if interupted @@ -64,6 +71,13 @@ public static Object awaitLoadAPI() throws InterruptedException { return loadDeepAPI(); } + /** + * Load the deep API to be used outside the agent. + *

    + * This method is defined as returning {@link Object} to not cause unwanted class loading of the type {@link IDeepHook}. + * + * @return a new instance of {@link IDeepHook} + */ public static Object loadDeepAPI() { if (deepAgent == null) { throw new IllegalStateException("Must start DEEP first"); diff --git a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java index 7483914..73a63cf 100644 --- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java +++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java @@ -41,6 +41,9 @@ import java.util.List; import java.util.Map; +/** + * This is the agent that is provided via the API, and is what holds all deep together. + */ public class DeepAgent implements IDeep { private final Settings settings; @@ -49,6 +52,12 @@ public class DeepAgent implements IDeep { private final TracepointConfigService tracepointConfig; private final PushService pushService; + /** + * Create a new deep agent. + * + * @param settings the settings + * @param tracepointInstrumentationService the tracepoint instrumentation service + */ public DeepAgent(final Settings settings, TracepointInstrumentationService tracepointInstrumentationService) { this.settings = settings; @@ -60,6 +69,9 @@ public DeepAgent(final Settings settings, Callback.init(settings, tracepointConfig, pushService); } + /** + * Start deep. + */ public void start() { final Resource resource = ResourceDetector.configureResource(settings, DeepAgent.class.getClassLoader()); @@ -75,6 +87,7 @@ public String getVersion() { return DeepVersion.VERSION; } + @Override public IPluginRegistration registerPlugin(final IPlugin plugin) { this.settings.addPlugin(plugin); final boolean isAuthProvider; diff --git a/agent/src/main/java/com/intergral/deep/agent/IDUtils.java b/agent/src/main/java/com/intergral/deep/agent/IDUtils.java index ff20a56..f06fc38 100644 --- a/agent/src/main/java/com/intergral/deep/agent/IDUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/IDUtils.java @@ -7,6 +7,9 @@ import java.util.concurrent.ThreadLocalRandom; +/** + * Utilities related to snapshot ids. + */ public final class IDUtils { private IDUtils() { @@ -16,6 +19,11 @@ private IDUtils() { private static final String ALPHABET = "0123456789abcdef"; private static final char[] ENCODING = buildEncodingArray(); + /** + * Create a new random id for a snapshot. + * + * @return the new id + */ public static String randomId() { final ThreadLocalRandom current = ThreadLocalRandom.current(); final long longId = current.nextLong(); diff --git a/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java b/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java index 58de829..bce76ad 100644 --- a/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/ReflectionUtils.java @@ -24,6 +24,9 @@ import java.util.Iterator; import java.util.Set; +/** + * A collection of utils that simplify the use of reflection. + */ public final class ReflectionUtils { private ReflectionUtils() { diff --git a/agent/src/main/java/com/intergral/deep/agent/Utils.java b/agent/src/main/java/com/intergral/deep/agent/Utils.java index b5fab74..b368a46 100644 --- a/agent/src/main/java/com/intergral/deep/agent/Utils.java +++ b/agent/src/main/java/com/intergral/deep/agent/Utils.java @@ -21,13 +21,16 @@ import java.util.HashMap; import java.util.Map; +/** + * Collection of utilities for general java related tasks. + */ public final class Utils { private Utils() { } /** - * Get the current version of Java running in this JVM + * Get the current version of Java running in this JVM. * * @return the java version number */ @@ -63,7 +66,7 @@ public static long[] currentTimeNanos() { } /** - * Create a new map from the input + * Create a new map from the input. * * @param map the input map * @param the key type @@ -78,11 +81,12 @@ public static Map newMap(final Map map) { /** - * FROM: view source + * Check if a string ends with a value, ignoring the case. * * @param str the string to search * @param suffix the value to serch for * @return true if {@code str} ends with {@code suffix}, disregarding case sensitivity + * @see Source */ public static boolean endsWithIgnoreCase(String str, String suffix) { int suffixLength = suffix.length(); @@ -113,7 +117,7 @@ public static String valueOf(final Object obj) { /** - * Trim a string from another string + * Trim a string from another string. * * @param str the target string * @param prefix the value to remove from the string @@ -128,7 +132,7 @@ public static String trimPrefix(String str, final String prefix) { /** - * Trim a string to a specified length + * Trim a string to a specified length. * * @param str the target string * @param maxLength the max length to make the string @@ -165,12 +169,12 @@ public boolean truncated() { /** - * The result of a trim operation + * The result of a trim operation. */ public interface ITrimResult { /** - * The value to use, might be truncated + * The value to use, might be truncated. * * @return the value */ @@ -178,7 +182,7 @@ public interface ITrimResult { /** - * Has the value been truncated + * Has the value been truncated. * * @return {@code true} if the value was truncated */ diff --git a/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java b/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java index f7bf002..a83326b 100644 --- a/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java +++ b/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java @@ -43,6 +43,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This service handles the grpc channel and attaching the metadata to the outbound services. + */ public class GrpcService { private static final Logger LOGGER = LoggerFactory.getLogger(GrpcService.class); @@ -54,6 +57,9 @@ public GrpcService(final Settings settings) { this.settings = settings; } + /** + * Start the grpc service and connect the channel. + */ public void start() { try { setupChannel(); @@ -116,6 +122,11 @@ private ManagedChannel getChannel() { return channel; } + /** + * Get the grpc service for polling configs. + * + * @return the service to use + */ public PollConfigGrpc.PollConfigBlockingStub pollService() { final PollConfigGrpc.PollConfigBlockingStub blockingStub = PollConfigGrpc.newBlockingStub( getChannel()); @@ -126,6 +137,11 @@ public PollConfigGrpc.PollConfigBlockingStub pollService() { metadata)); } + /** + * Get the grpc service for sending snapshots. + * + * @return the service to use + */ public SnapshotServiceGrpc.SnapshotServiceStub snapshotService() { final SnapshotServiceGrpc.SnapshotServiceStub snapshotServiceStub = SnapshotServiceGrpc.newStub( getChannel()); diff --git a/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java b/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java index ef94398..8c3f5c0 100644 --- a/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java +++ b/agent/src/main/java/com/intergral/deep/agent/logging/Logger.java @@ -26,11 +26,19 @@ import java.util.logging.SimpleFormatter; import org.slf4j.LoggerFactory; +/** + * Logger utility methods. + */ public final class Logger { private Logger() { } + /** + * Create and configure the java.util.logger for use with deep. + * + * @param settings the settings for deep + */ public static void configureLogging(final Settings settings) { final java.util.logging.Logger logger = java.util.logging.Logger.getLogger("com.intergral"); logger.setUseParentHandlers(false); diff --git a/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java b/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java index 5c364f4..de51ac3 100644 --- a/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java +++ b/agent/src/main/java/com/intergral/deep/agent/plugins/PluginLoader.java @@ -27,6 +27,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * this type deals with loading the plugins. + */ public final class PluginLoader { private PluginLoader() { @@ -34,6 +37,13 @@ private PluginLoader() { private static final Logger LOGGER = LoggerFactory.getLogger(PluginLoader.class); + /** + * Using the current config load as many plugins as we can. + * + * @param settings the settings for deep + * @param reflection the reflection service to use + * @return the list of active and loaded plugins + */ public static List loadPlugins(final Settings settings, final IReflection reflection) { final List plugins = settings.getAsList("plugins"); final List loadedPlugins = new ArrayList<>(); diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java b/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java index deb5275..e2dc444 100644 --- a/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java +++ b/agent/src/main/java/com/intergral/deep/agent/poll/DriftAwareThread.java @@ -21,6 +21,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * A thread that can run a {@link ITimerTask} accounting for drifting time. + */ public class DriftAwareThread extends Thread { private static final Logger LOGGER = LoggerFactory.getLogger(DriftAwareThread.class); @@ -34,6 +37,8 @@ public class DriftAwareThread extends Thread { /** + * Create a new thread. + * * @param name the name for the thread * @param runnable the {@link ITimerTask} to execute * @param interval the interval in ms between each execution @@ -178,7 +183,7 @@ protected long checkForEarlyWake(final long now, final long nextExecutionTime) } - public void stopTask() { + void stopTask() { synchronized (this.samplerLock) { this.stopSampler(); this.samplerLock.notifyAll(); diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/ITimerTask.java b/agent/src/main/java/com/intergral/deep/agent/poll/ITimerTask.java index 939747e..632f3ae 100644 --- a/agent/src/main/java/com/intergral/deep/agent/poll/ITimerTask.java +++ b/agent/src/main/java/com/intergral/deep/agent/poll/ITimerTask.java @@ -17,10 +17,15 @@ package com.intergral.deep.agent.poll; +/** + * A task to run in the timer. + * + * @see DriftAwareThread + */ public interface ITimerTask { /** - * This method is called by the {@link DriftAwareThread} at the end of each interval + * This method is called by the {@link DriftAwareThread} at the end of each interval. * * @param now the current time * @throws Exception if the task fails diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java index 538ae6d..1a23e2c 100644 --- a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java +++ b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java @@ -34,12 +34,21 @@ import java.util.List; import java.util.stream.Collectors; +/** + * This service deals with polling the remote service for tracepoint configs. + */ public class LongPollService implements ITimerTask { private final Settings settings; private final GrpcService grpcService; private final DriftAwareThread thread; private ITracepointConfig tracepointConfig; + /** + * Create a new service. + * + * @param settings the deep settings + * @param grpcService the deep grpc service + */ public LongPollService(final Settings settings, final GrpcService grpcService) { this.settings = settings; this.grpcService = grpcService; diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushService.java b/agent/src/main/java/com/intergral/deep/agent/push/PushService.java index e7958e9..6a39348 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushService.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushService.java @@ -30,6 +30,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This service deals with pushing the collected data to the remote services. + */ public class PushService { private static final Logger LOGGER = LoggerFactory.getLogger(PushService.class); @@ -42,6 +45,12 @@ public PushService(final Settings settings, final GrpcService grpcService) { this.grpcService = grpcService; } + /** + * Decorate and push the provided snapshot. + * + * @param snapshot the snapshot to push + * @param context the context of where the snapshot is collected + */ public void pushSnapshot(final EventSnapshot snapshot, final ISnapshotContext context) { decorate(snapshot, context); final Snapshot grpcSnapshot = PushUtils.convertToGrpc(snapshot); diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java index 40d8f32..33d9ca5 100644 --- a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java @@ -35,11 +35,20 @@ import java.util.Map; import java.util.stream.Collectors; +/** + * Utilities to convert to grpc snapshot types. + */ public final class PushUtils { private PushUtils() { } + /** + * Convert an internal snapshot into a grpc snapshot. + * + * @param snapshot the internal snapshot to convert + * @return the converted snapshot + */ public static Snapshot convertToGrpc(final EventSnapshot snapshot) { return Snapshot.newBuilder() .setID(ByteString.copyFromUtf8(snapshot.getID())) @@ -143,6 +152,12 @@ public static Collection cove return frameVariables.stream().map(PushUtils::convertVariableID).collect(Collectors.toList()); } + /** + * Convert a variable lookup into grpc variables. + * + * @param varLookup the lookup to convert + * @return the converted variables + */ public static Map convertVarLookup( final Map varLookup) { return varLookup.entrySet() diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java b/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java index 15474a9..ff250cf 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/JavaResourceDetector.java @@ -22,6 +22,9 @@ import com.intergral.deep.agent.api.spi.ResourceProvider; import java.util.Collections; +/** + * A resource provider that detects the hava version to add to the resource. + */ public class JavaResourceDetector implements ResourceProvider { @Override diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java index f9d4ee3..178de41 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/ResourceDetector.java @@ -20,6 +20,9 @@ import java.util.Map; import java.util.Set; +/** + * Utilities to create the resource for this agent. + */ public final class ResourceDetector { private ResourceDetector() { @@ -32,6 +35,13 @@ private ResourceDetector() { static final String ENABLED_PROVIDERS_KEY = "deep.java.enabled.resource.providers"; static final String DISABLED_PROVIDERS_KEY = "deep.java.disabled.resource.providers"; + /** + * Create and configure a resource for this agent. + * + * @param settings the settings for the agent + * @param serviceClassLoader the class loader to use to load the SPI services + * @return the loaded resource + */ public static Resource configureResource(Settings settings, ClassLoader serviceClassLoader) { Resource result = Resource.create(Collections.emptyMap()); diff --git a/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java b/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java index 49a1217..fe2bee7 100644 --- a/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java +++ b/agent/src/main/java/com/intergral/deep/agent/resource/SpiUtil.java @@ -11,6 +11,9 @@ import java.util.List; import java.util.ServiceLoader; +/** + * Utilities to load SPI services. + */ public final class SpiUtil { private SpiUtil() { diff --git a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java index 7190415..60f4b61 100644 --- a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java +++ b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java @@ -39,18 +39,27 @@ import java.util.logging.Level; import java.util.regex.Pattern; +/** + * A service that handles the general config of the deep agent. + */ public class Settings implements ISettings { private static final AtomicBoolean IS_ACTIVE = new AtomicBoolean(true); private final Properties properties; + private final Collection customPlugins = new ArrayList<>(); private Resource resource; private Collection plugins = Collections.emptyList(); - private final Collection customPlugins = new ArrayList<>(); private Settings(Properties properties) { this.properties = properties; } + /** + * Build a new settings service from the input arguments from the agent. + * + * @param agentArgs the agent input arguments + * @return the new settings service + */ public static Settings build(final Map agentArgs) { final String settingFile = readProperty("deep.settings", agentArgs); final InputStream propertiesStream; @@ -120,6 +129,14 @@ private static String readSystemProperty(final String key) { return System.getProperty("deep." + key); } + /** + * Coerce a value into a given type. + * + * @param str the value to coerce + * @param type the type to change to + * @param the type to return as + * @return the value as the given type, or {@code null} + */ @SuppressWarnings("unchecked") public static T coerc(final String str, final Class type) { if (str == null) { @@ -186,9 +203,10 @@ private static List makeList(final String str) { return Arrays.asList(split); } + @Override public T getSettingAs(String key, Class clazz) { // special handling for enabled key - if(key.equals(ISettings.KEY_ENABLED)){ + if (key.equals(ISettings.KEY_ENABLED)) { return coerc(String.valueOf(isActive()), clazz); } final String property = this.properties.getProperty(key); @@ -218,6 +236,12 @@ public Map getMap(String key) { return settingAs; } + /** + * Get the deep service host name. + * + * @return the service host name + * @throws InvalidConfigException if the value cannot be obtained + */ public String getServiceHost() { final String serviceUrl = getSettingAs(ISettings.KEY_SERVICE_URL, String.class); if (serviceUrl != null && serviceUrl.contains("://")) { @@ -233,6 +257,12 @@ public String getServiceHost() { throw new InvalidConfigException(ISettings.KEY_SERVICE_URL, serviceUrl); } + /** + * Get the deep service port number. + * + * @return the service port number + * @throws InvalidConfigException if the value cannot be obtained + */ public int getServicePort() { final String serviceUrl = getSettingAs(ISettings.KEY_SERVICE_URL, String.class); if (serviceUrl != null && serviceUrl.contains("://")) { @@ -253,10 +283,21 @@ public Resource getResource() { return this.resource; } + /** + * Get the resource value for this agent. + * + * @param resource the resource that describes this agent + */ public void setResource(Resource resource) { this.resource = resource; } + /** + * Get the value as a list. + * + * @param key the key for the setting + * @return the value as a list + */ public List getAsList(final String key) { final List settingAs = getSettingAs(key, List.class); if (settingAs == null) { @@ -265,28 +306,50 @@ public List getAsList(final String key) { return settingAs; } + /** + * Get all configured plugins. + * + * @return the full list on plugins + */ public Collection getPlugins() { final ArrayList actualPlugins = new ArrayList<>(this.plugins); actualPlugins.addAll(this.customPlugins); return actualPlugins; } + /** + * Set configured plugins. + * + * @param plugins the plugins to use + */ public void setPlugins(Collection plugins) { this.plugins = plugins; } + /** + * Add a custom plugin. + * + * @param plugin the plugin to add + * @see com.intergral.deep.agent.api.IDeep#registerPlugin(IPlugin) + */ public void addPlugin(final IPlugin plugin) { - final Optional first = this.customPlugins.stream().filter(iPlugin -> iPlugin.name().equals(plugin.name())).findFirst(); + final Optional first = this.customPlugins.stream().filter(current -> current.name().equals(plugin.name())).findFirst(); if (first.isPresent()) { throw new IllegalStateException(String.format("Cannot add duplicate named (%s) plugin", plugin.name())); } this.customPlugins.add(plugin); } + /** + * Remove a plugin that was added via {@link #addPlugin(IPlugin)}. + * + * @param plugin the plugin to remove + */ public void removePlugin(final IPlugin plugin) { - this.customPlugins.removeIf(iPlugin -> iPlugin.name().equals(plugin.name())); + this.customPlugins.removeIf(current -> current.name().equals(plugin.name())); } + @Override public IPlugin getPlugin(final String name) { final Collection allPlugins = this.getPlugins(); @@ -298,14 +361,29 @@ public IPlugin getPlugin(final String name) { return null; } + /** + * Is deep currently active. + * + * @return {@code true} if deep is active, else {@code false} + */ public boolean isActive() { return IS_ACTIVE.get(); } + /** + * Allows enabling or disabled deep. + *

    + * A disabled deep will remove installed tracepoints and stop polling. It will not remove itself. + * + * @param state the new state + */ public void setActive(boolean state) { IS_ACTIVE.set(state); } + /** + * Used to indicate an invalid config value. + */ public static class InvalidConfigException extends RuntimeException { public InvalidConfigException(String key, String value) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java index a7faf2b..04f1ccd 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java @@ -51,7 +51,7 @@ public interface ITracepointConfig { String currentHash(); /** - * Load the full configs for the given tracepoints ids + * Load the full configs for the given tracepoints ids. * * @param tracepointId the tracepoint ids * @return a collection of all the matched tracepoints diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java index 30e6795..70ac34b 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java @@ -29,6 +29,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This service deals with mapping the response from polls into actions to install tracepoints. + */ public class TracepointConfigService implements ITracepointConfig { private static final Logger LOGGER = LoggerFactory.getLogger(TracepointConfigService.class); @@ -76,6 +79,16 @@ public Collection loadTracepointConfigs(final Collection args, final Collection watches) { final TracePointConfig tracePointConfig = new TracePointConfig(UUID.randomUUID().toString(), path, line, args, watches); @@ -84,6 +97,12 @@ public TracePointConfig addCustom(final String path, final int line, final Map current.getId().equals(tracePointConfig.getId())); if (removed) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java index 23c876d..2a9566f 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointUtils.java @@ -21,6 +21,9 @@ import com.intergral.deep.agent.tracepoint.inst.InstUtils; import com.intergral.deep.agent.types.TracePointConfig; +/** + * Utilities for tracepoint configuration. + */ public final class TracepointUtils { private TracepointUtils() { @@ -31,7 +34,7 @@ private TracepointUtils() { * * @param tp the tracepoint to process * @return the internal class name to install the tracepoint in, or {@code cfm} or {@code jsp} if the computed class is a CFM or JSP - * class. + * class. */ public static String estimatedClassRoot(final TracePointConfig tp) { // we allow the class name to be sent for specific cases diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java index 90a9af6..b9c04ee 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFEvaluator.java @@ -25,6 +25,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * The evaluator to use when running a CF Callback. + */ public class CFEvaluator extends AbstractEvaluator { private static final Logger LOGGER = LoggerFactory.getLogger(CFEvaluator.class); @@ -49,6 +52,9 @@ public Object evaluateExpression(final String expression, final Map variables; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java index 82444a4..86078cb 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFFrameProcessor.java @@ -27,6 +27,10 @@ import java.util.HashMap; import java.util.Map; +/** + * We want to map the variables from the Java variables to the CF variables. So we have a custom processor for CF that lets us perform this + * mapping. + */ public class CFFrameProcessor extends FrameProcessor { public CFFrameProcessor(final Settings settings, @@ -128,7 +132,7 @@ Map mapCFScopes(final Map variables) { /** - * Special handling for lucee scopes + * Special handling for lucee scopes. * * @param variables the variables * @return the scopes for lucee diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java index 96efedb..e577807 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/cf/CFUtils.java @@ -29,18 +29,29 @@ import java.util.Map; import java.util.Set; +/** + * Utilities to help with CF related item. + */ public final class CFUtils { private CFUtils() { } - public static IEvaluator findCfEval(final Map map) { - final Object that = map.get("this"); + /** + * Find the evaluator to use for CF. + *

    + * Cf provides an {@code Evaluate} method on the page object that can be used to evaluate strings. This method tries to find that method. + * + * @param variables the variables to scan + * @return the evaluate to use, or {@code null} + */ + public static IEvaluator findCfEval(final Map variables) { + final Object that = variables.get("this"); if (isLucee(that)) { - return findLuceeEvaluator(map); + return findLuceeEvaluator(variables); } - final Object page = CFUtils.findPage(map); + final Object page = CFUtils.findPage(variables); if (page == null) { return null; } @@ -71,9 +82,17 @@ private static IEvaluator findLuceeEvaluator(final Map map) { } + /** + * CF doesn't use the java method name, so we look for the UDF method name in the variables. + * + * @param variables the variables to look in + * @param className the name of the class + * @param stackIndex the stack index + * @return the name of the UDF method, or {@code null} + */ public static String findUdfName(final Map variables, final String className, - final int i) { - if (i == 0) { + final int stackIndex) { + if (stackIndex == 0) { final Object aThis = variables.get("this"); if (aThis == null || !isUdfMethod(aThis)) { return null; @@ -102,6 +121,12 @@ static boolean isScope(final Object varScope) { } + /** + * Find the page object. + * + * @param localVars the variables to scan + * @return the page object or {@code null} + */ public static Object findPage(final Map localVars) { final Object aThis = localVars.get("this"); if (aThis == null) { @@ -115,6 +140,12 @@ public static Object findPage(final Map localVars) { } + /** + * Find the page context for cf. + * + * @param localVars the variables to search + * @return the page context or {@code null} + */ public static Object findPageContext(final Map localVars) { final Object page = findPage(localVars); if (page == null) { @@ -124,11 +155,23 @@ public static Object findPageContext(final Map localVars) { } + /** + * Is this class a possible cf class. + * + * @param classname the class name + * @return {@code true} if the class is cf + */ public static boolean isCfClass(final String classname) { return classname.startsWith("cf") || classname.endsWith("$cf"); } + /** + * Is the file a possible CF file. + * + * @param fileName the file name to check + * @return {@code true} if the file is a cf file, else {@code false} + */ public static boolean isCFFile(final String fileName) { if (fileName == null) { return false; @@ -137,7 +180,12 @@ public static boolean isCFFile(final String fileName) { || fileName.endsWith(".cfml"); } - + /** + * Are we a lucee page. + * + * @param that the object to check + * @return {@code true} if we are a lucee object + */ public static boolean isLucee(final Object that) { return that != null && that.getClass().getSuperclass() != null @@ -147,7 +195,7 @@ public static boolean isLucee(final Object that) { /** - * When running on Lucee servers we can guess the source from the class name + * When running on Lucee servers we can guess the source from the class name. * * @param classname the class we are processing * @return the source file name, or {@code null} @@ -164,7 +212,14 @@ public static String guessSource(final String classname) { } - public static Set loadCfBreakpoints(final URL location, + /** + * Load the CF tracepoints based on the location url. + * + * @param location the location to look for + * @param values the tracepoints to look at + * @return the set of tracepoints that match this location + */ + public static Set loadCfTracepoints(final URL location, final Map values) { final Set iBreakpoints = new HashSet<>(); final Collection breakpoints = values.values(); @@ -183,7 +238,14 @@ public static Set loadCfBreakpoints(final URL location, return iBreakpoints; } - public static Set loadCfBreakpoints( + /** + * Load the CF tracepoints based on the location string. + * + * @param location the location to look for + * @param values the tracepoints to look at + * @return the set of tracepoints that match this location + */ + public static Set loadCfTracepoints( final String location, final Map values) { if (location == null) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java index f1e4812..d165e1d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/EvaluatorService.java @@ -22,6 +22,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * General wrapper around non CF evaluators. + */ public final class EvaluatorService { private EvaluatorService() { @@ -31,6 +34,11 @@ private EvaluatorService() { private static final Exception NO_EVALUATOR_EXCEPTION = new RuntimeException( "No evaluator available."); + /** + * Create an evaluator. + * + * @return the new evaluator. + */ public static IEvaluator createEvaluator() { final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); final IEvaluator iEvaluator = NashornReflectEvaluator.loadEvaluator(contextClassLoader); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java index 4b49f00..b895472 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/evaluator/NashornReflectEvaluator.java @@ -46,6 +46,12 @@ private NashornReflectEvaluator(final Object engine) { this.engine = engine; } + /** + * Load the evaluator. + * + * @param loader the class loader to use + * @return the evaluator or {@code null} if Nashorn is not available. + */ public static IEvaluator loadEvaluator(final ClassLoader loader) { // this stops us from trying to load nashorn again if it failed if (!NASHORN_AVAILABLE) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java index f632957..ac86336 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/Callback.java @@ -37,20 +37,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This type is the main entry point that is used to callback from injected code. + */ public final class Callback { private Callback() { } // @SuppressWarnings("AnonymousHasLambdaAlternative") -// public static final ThreadLocal> CALLBACKS = new ThreadLocal>() -// { -// @Override -// protected Deque initialValue() -// { -// return new ArrayDeque<>(); -// } -// }; + // public static final ThreadLocal> CALLBACKS = new ThreadLocal>() + // { + // @Override + // protected Deque initialValue() + // { + // return new ArrayDeque<>(); + // } + // }; private static final Logger LOGGER = LoggerFactory.getLogger(Callback.class); @SuppressWarnings("AnonymousHasLambdaAlternative") private static final ThreadLocal FIRING = new ThreadLocal() { @@ -61,16 +64,23 @@ protected Boolean initialValue() { }; private static Settings SETTINGS; - private static TracepointConfigService BREAKPOINT_SERVICE; + private static TracepointConfigService TRACEPOINT_SERVICE; private static PushService PUSH_SERVICE; private static int offset; + /** + * Initialise the callback with the deep services. + * + * @param settings the deep settings + * @param tracepointConfigService the tracepoint service + * @param pushService the push service + */ public static void init( final Settings settings, - final TracepointConfigService breakpointService, + final TracepointConfigService tracepointConfigService, final PushService pushService) { Callback.SETTINGS = settings; - Callback.BREAKPOINT_SERVICE = breakpointService; + Callback.TRACEPOINT_SERVICE = tracepointConfigService; Callback.PUSH_SERVICE = pushService; if (Visitor.CALLBACK_CLASS == Callback.class) { offset = 3; @@ -81,7 +91,7 @@ public static void init( /** - * The main entry point for CF ASM injected breakpoints + * The main entry point for CF ASM injected breakpoints. * * @param bpIds the bp ids to trigger * @param filename the filename of the breakpoint hit @@ -102,7 +112,7 @@ public static void callBackCF(final List bpIds, /** - * The main entry point for non CF ASM injected breakpoints + * The main entry point for non CF ASM injected breakpoints. * * @param bpIds the bp ids to trigger * @param filename the filename of the breakpoint hit @@ -138,11 +148,11 @@ private static void commonCallback(final List tracepointIds, LOGGER.trace("callBack for {}:{} -> {}", filename, lineNo, tracepointIds); // possible race condition but unlikely - if (Callback.BREAKPOINT_SERVICE == null) { + if (Callback.TRACEPOINT_SERVICE == null) { return; } - final Collection tracePointConfigs = Callback.BREAKPOINT_SERVICE.loadTracepointConfigs( + final Collection tracePointConfigs = Callback.TRACEPOINT_SERVICE.loadTracepointConfigs( tracepointIds); StackTraceElement[] stack = Thread.currentThread().getStackTrace(); @@ -175,132 +185,133 @@ private static void commonCallback(final List tracepointIds, } } - // the below methods are here as they are called from instrumented classes. This is a throwback to NV that we will address in later releases + // the below methods are here as they are called from instrumented classes. + // This is a throwback to NV that we will address in later releases public static void callBackException(final Throwable t) { -// System.out.println( "callBackException" ); -// try -// { -// LOGGER.debug( "Capturing throwable", t ); -// final CallbackHook element = CALLBACKS.get().peekLast(); -// if( element != null && element.isHook()) -// { -// element.setThrowable( t ); -// } -// } -// catch( Throwable tt ) -// { -// LOGGER.debug( "Error processing callback", tt ); -// } + // System.out.println( "callBackException" ); + // try + // { + // LOGGER.debug( "Capturing throwable", t ); + // final CallbackHook element = CALLBACKS.get().peekLast(); + // if( element != null && element.isHook()) + // { + // element.setThrowable( t ); + // } + // } + // catch( Throwable tt ) + // { + // LOGGER.debug( "Error processing callback", tt ); + // } } public static void callBackFinally(final Set breakpointIds, final Map map) { -// System.out.println( "callBackFinally" ); -// for( String breakpointId : breakpointIds ) -// { -// try -// { -// LOGGER.debug( "{}: Processing finally", breakpointId ); -// final Deque hooks = CALLBACKS.get(); -// final CallbackHook pop = hooks.pollLast(); -// LOGGER.debug( "Dequeue state: {}", hooks ); -// if( pop == null || !pop.isHook() ) -// { -// LOGGER.debug( "No callback pending. {}", pop ); -// continue; -// } -// -// final boolean processFrameStack = (pop.value.getType().equals( ITracepoint.STACK_TYPE )) || pop.value.getType() -// .equals( ITracepoint.FRAME_TYPE ) || pop.value.getType().equals( ITracepoint.LOG_POINT_TYPE ) -// || pop.value.getType().equals( ITracepoint.NO_FRAME_TYPE ); -// -// final List frames; -// if( pop.value.getArg( ITracepoint.LINE_HOOK_ARG_KEY ).equals( ITracepoint.LINE_HOOK_DATA_RIGHT ) ) -// { -// frames = pop.snapshotHandler.processFrames( map, processFrameStack, System.currentTimeMillis() ); -// } -// else -// { -// frames = pop.frames; -// } -// -// // trim frames to our type -// @SuppressWarnings({ "RedundantTypeArguments", "Convert2Diamond" }) -// final IRequestDecorator iRequestDecorator = pop.snapshotHandler.generateSnapshotData( pop.watchValues, pop.value, -// frames, Collections.emptySet(), -// NVError.fromThrowable( pop.throwable, new HashMap() ), -// System.currentTimeMillis(), Callback.CLIENT_CONFIG.getTags() ); -// -// final EventSnapshot eventSnapshot = iRequestDecorator.getBody(); -// addDynamicTags( pop.value, eventSnapshot ); -// -// sendEvent( iRequestDecorator, eventSnapshot ); -// } -// catch( Throwable t ) -// { -// LOGGER.debug( "Error processing callback", t ); -// } -// } + // System.out.println( "callBackFinally" ); + // for( String breakpointId : breakpointIds ) + // { + // try + // { + // LOGGER.debug( "{}: Processing finally", breakpointId ); + // final Deque hooks = CALLBACKS.get(); + // final CallbackHook pop = hooks.pollLast(); + // LOGGER.debug( "Dequeue state: {}", hooks ); + // if( pop == null || !pop.isHook() ) + // { + // LOGGER.debug( "No callback pending. {}", pop ); + // continue; + // } + // + // final boolean processFrameStack = (pop.value.getType().equals( ITracepoint.STACK_TYPE )) || pop.value.getType() + // .equals( ITracepoint.FRAME_TYPE ) || pop.value.getType().equals( ITracepoint.LOG_POINT_TYPE ) + // || pop.value.getType().equals( ITracepoint.NO_FRAME_TYPE ); + // + // final List frames; + // if( pop.value.getArg( ITracepoint.LINE_HOOK_ARG_KEY ).equals( ITracepoint.LINE_HOOK_DATA_RIGHT ) ) + // { + // frames = pop.snapshotHandler.processFrames( map, processFrameStack, System.currentTimeMillis() ); + // } + // else + // { + // frames = pop.frames; + // } + // + // // trim frames to our type + // @SuppressWarnings({ "RedundantTypeArguments", "Convert2Diamond" }) + // final IRequestDecorator iRequestDecorator = pop.snapshotHandler.generateSnapshotData( pop.watchValues, pop.value, + // frames, Collections.emptySet(), + // NVError.fromThrowable( pop.throwable, new HashMap() ), + // System.currentTimeMillis(), Callback.CLIENT_CONFIG.getTags() ); + // + // final EventSnapshot eventSnapshot = iRequestDecorator.getBody(); + // addDynamicTags( pop.value, eventSnapshot ); + // + // sendEvent( iRequestDecorator, eventSnapshot ); + // } + // catch( Throwable t ) + // { + // LOGGER.debug( "Error processing callback", t ); + // } + // } } -// public static class CallbackHook -// { -// -// private final SnapshotHandler snapshotHandler; -// private final List watchValues; -// private final ITracepoint value; -// private final List frames; -// private Throwable throwable; -// -// -// public CallbackHook( final SnapshotHandler snapshotHandler, -// final List watchValues, -// final ITracepoint value, -// final List frames ) -// { -// this.snapshotHandler = snapshotHandler; -// this.watchValues = watchValues; -// this.value = value; -// this.frames = frames; -// } -// -// -// public CallbackHook() -// { -// this.snapshotHandler = null; -// this.watchValues = null; -// this.value = null; -// this.frames = null; -// } -// -// -// /** -// * This will return {@code true} when there is a live hook for this. -// * -// * @return {@code true} if this is a hook else {@code false}. -// */ -// public boolean isHook() -// { -// return this.snapshotHandler != null; -// } -// -// -// void setThrowable( final Throwable t ) -// { -// this.throwable = t; -// } -// -// -// @Override -// public String toString() -// { -// if( value == null ) -// { -// return "Marker for non callback"; -// } -// return String.format( "%s:%s", value.getRelPath(), value.getLineNo() ); -// } -// } + // public static class CallbackHook + // { + // + // private final SnapshotHandler snapshotHandler; + // private final List watchValues; + // private final ITracepoint value; + // private final List frames; + // private Throwable throwable; + // + // + // public CallbackHook( final SnapshotHandler snapshotHandler, + // final List watchValues, + // final ITracepoint value, + // final List frames ) + // { + // this.snapshotHandler = snapshotHandler; + // this.watchValues = watchValues; + // this.value = value; + // this.frames = frames; + // } + // + // + // public CallbackHook() + // { + // this.snapshotHandler = null; + // this.watchValues = null; + // this.value = null; + // this.frames = null; + // } + // + // + // /** + // * This will return {@code true} when there is a live hook for this. + // * + // * @return {@code true} if this is a hook else {@code false}. + // */ + // public boolean isHook() + // { + // return this.snapshotHandler != null; + // } + // + // + // void setThrowable( final Throwable t ) + // { + // this.throwable = t; + // } + // + // + // @Override + // public String toString() + // { + // if( value == null ) + // { + // return "Marker for non callback"; + // } + // return String.format( "%s:%s", value.getRelPath(), value.getLineNo() ); + // } + // } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java index f121e8f..5cb5e61 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java @@ -48,22 +48,22 @@ public class FrameCollector extends VariableProcessor { /** - * The current settings use my deep + * The current settings use my deep. */ protected final Settings settings; /** - * The evaluator that should be used by this callback + * The evaluator that should be used by this callback. */ protected final IEvaluator evaluator; /** - * The variables that have been captured by the callback + * The variables that have been captured by the callback. */ protected final Map variables; /** - * The stack trace elements captured by this callback + * The stack trace elements captured by this callback. */ private final StackTraceElement[] stack; @@ -78,7 +78,7 @@ public class FrameCollector extends VariableProcessor { private final List jspPackages; /** - * Create a frame collector to collect the frame data + * Create a frame collector to collect the frame data. * * @param settings the current settings being used by deep * @param evaluator the evaluator to use for this callback @@ -334,7 +334,7 @@ private String getFileName(final StackTraceElement stackTraceElement) { } /** - * Get the method name from the stack frame + * Get the method name from the stack frame. * * @param stackTraceElement the stack frame to process * @param variables the variables for the frame diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java index a7a26f9..c3bc06d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameConfig.java @@ -19,6 +19,10 @@ import com.intergral.deep.agent.types.TracePointConfig; +/** + * This config holds the general config to use when processing a callback. The config is defined from all the tracepoints that are trigger + * on a line. + */ public class FrameConfig { private static final int DEFAULT_MAX_VAR_DEPTH = 5; @@ -39,6 +43,11 @@ public class FrameConfig { private boolean cfRaw = false; + /** + * Process a tracepoint into the config. + * + * @param tracePointConfig the tracepoint to process + */ public void process(final TracePointConfig tracePointConfig) { maxVarDepth = Math.max(tracePointConfig.getArg("MAX_VAR_DEPTH", Integer.class, -1), maxVarDepth); @@ -78,6 +87,9 @@ public void process(final TracePointConfig tracePointConfig) { } } + /** + * When we have finished processing all the tracepoints we {@code close} the config to set the final config for this callback. + */ public void close() { maxVarDepth = maxVarDepth == -1 ? DEFAULT_MAX_VAR_DEPTH : maxVarDepth; maxVariables = maxVariables == -1 ? DEFAULT_MAX_VARIABLES : maxVariables; @@ -90,6 +102,12 @@ public void close() { stackType = stackType == null ? TracePointConfig.STACK : stackType; } + /** + * Using the {@link #frameType} should we collect the variables on this frame. + * + * @param currentFrameIndex the current frame index. + * @return {@code true} if we should collect the variables. + */ public boolean shouldCollectVars(final int currentFrameIndex) { if (this.frameType.equals(TracePointConfig.NO_FRAME_TYPE)) { return false; @@ -102,22 +120,48 @@ public boolean shouldCollectVars(final int currentFrameIndex) { return this.frameType.equals(TracePointConfig.ALL_FRAME_TYPE); } + /** + * The max number of variables this callback should collect. + * + * @return the max variables + */ public int maxVariables() { return this.maxVariables; } + /** + * The max length of any string. + * + * @return the max string length + */ public int maxStringLength() { return this.maxStrLength; } + /** + * The max depth of variables to collect. + * + * @return the max variable depth + */ public int maxDepth() { return this.maxVarDepth; } + /** + * The max number of items in a collection we should collect. + * + * @return the max collection size + */ public int maxCollectionSize() { return this.maxCollectionSize; } + /** + * Is this frame set to cf raw. + * cf raw allows the collection of the java variables instead of the mapped cf variables. + * + * @return {@code true} if we want to collect the raw cf variables. + */ public boolean isCfRaw() { return this.cfRaw; } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java index 23d3fa3..a36e697 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java @@ -36,7 +36,7 @@ public class FrameProcessor extends FrameCollector implements ISnapshotContext { /** - * The tracepoints that have been triggered by the Callback + * The tracepoints that have been triggered by the Callback. */ private final Collection tracePointConfigs; /** @@ -52,7 +52,7 @@ public class FrameProcessor extends FrameCollector implements ISnapshotContext { private Collection filteredTracepoints; /** - * Create a new processor for this Callback + * Create a new processor for this Callback. * * @param settings the current settings being used * @param evaluator the evaluator to use for watchers and conditions @@ -89,7 +89,7 @@ public boolean canCollect() { } /** - * Process the tracepoints condition with the evaluator to see if the condition is {@code true} + * Process the tracepoints condition with the evaluator to see if the condition is {@code true}. * * @param tracePointConfig the config to check * @return {@code false} if the condition on the tracepoint evaluates to a false @@ -163,12 +163,12 @@ public String evaluateExpression(final String expression) throws EvaluationExcep } /** - * This defines a functional interface to allow for creating difference processors in the Callback + * This defines a functional interface to allow for creating difference processors in the Callback. */ public interface IFactory { /** - * Create a new processor + * Create a new processor. * * @param settings the current settings being used * @param evaluator the evaluator to use for watchers and conditions diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java index 00c6b66..2ed6d08 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/VariableProcessor.java @@ -127,6 +127,11 @@ protected Map closeLookup() { return lookup; } + /** + * We need to configure the {@link #frameConfig} based on the tracepoints we are capturing. + * + * @param configs the tracepoints to configure with + */ public void configureSelf(final Iterable configs) { for (TracePointConfig tracePointConfig : configs) { this.frameConfig.process(tracePointConfig); @@ -224,7 +229,7 @@ private boolean mapType(final Object value) { } /** - * Is the object a type of array + * Is the object a type of array. * * @param value the object to check * @return {@code true} if the object is a form of array, else {@code false} @@ -235,7 +240,7 @@ private boolean arrayType(final Object value) { } /** - * Is the object a type of collection, not including array + * Is the object a type of collection, not including array. * * @param value the object to check * @return {@code true} if the object is a {@link Collection}, else {@code false}. @@ -271,7 +276,7 @@ protected VariableResponse processVariable(final Node.NodeValue value) { if (cacheId != null) { return new VariableResponse(new VariableID(cacheId, - value.getKey(), + value.getName(), value.getModifiers(), value.getOriginalName()), false); } @@ -279,7 +284,7 @@ protected VariableResponse processVariable(final Node.NodeValue value) { // if we do not have a cache_id - then create one final String varId = newVarId(identityCode); final VariableID variableId = new VariableID(varId, - value.getKey(), + value.getName(), value.getModifiers(), value.getOriginalName()); @@ -422,7 +427,7 @@ public Set modifiers() { } /** - * The interface of an item named by the named iterator + * The interface of an item named by the named iterator. */ public interface INamedItem { @@ -437,7 +442,7 @@ public interface INamedItem { } /** - * Use the named iterator to name map values with the map key + * Use the named iterator to name map values with the map key. */ private static class NamedMapIterator extends NamedIterable> { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java index c12e072..8d99a34 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/bfs/Node.java @@ -24,6 +24,9 @@ import java.util.List; import java.util.Set; +/** + * A node is a value to process in the BFS. It also links children to parents to ensure hierarchy in variables. + */ public class Node { final NodeValue value; @@ -32,17 +35,36 @@ public class Node { int depth = 0; + /** + * Create a new node for the BFS. + * + * @param value the value to wrap + * @param parent the parent of this node + */ public Node(final NodeValue value, final IParent parent) { this(value, new HashSet<>(), parent); } + /** + * Create a new node for the BFS. + * + * @param value the value to wrap + * @param children the children of this node + * @param parent the parent of this node + */ public Node(final NodeValue value, final Set children, final IParent parent) { this.value = value; this.parent = parent; this.children = children; } + /** + * Start the breadth first search of the nodes. + * + * @param root the root node to start from + * @param consumer the consumer to use to collect more nodes. + */ public static void breadthFirstSearch(final Node root, final IConsumer consumer) { final List queue = new LinkedList<>(); @@ -60,6 +82,11 @@ public static void breadthFirstSearch(final Node root, final IConsumer consumer) } } + /** + * Add child nodes. + * + * @param children the children to add + */ public void addChildren(final Set children) { for (Node child : children) { child.depth = this.depth + 1; @@ -82,16 +109,22 @@ public int depth() { } + /** + * The parent of a processed node. + */ public interface IParent { void addChild(final VariableID child); - default boolean isCollection(){ + default boolean isCollection() { return false; } } + /** + * The consumer of the nodes when running a BFS. + */ public interface IConsumer { boolean processNode(Node node); @@ -103,7 +136,7 @@ public interface IConsumer { */ public static class NodeValue { - private final String key; + private final String name; private final Object value; private final String originalName; private final Set modifiers; @@ -112,16 +145,24 @@ public NodeValue(final String key, final Object value) { this(key, value, null, Collections.emptySet()); } - public NodeValue(final String key, final Object value, final String originalName, + /** + * Create a new node value. + * + * @param name the name of the value + * @param value the value to wrap + * @param originalName the original name of the value + * @param modifiers the value modifiers + */ + public NodeValue(final String name, final Object value, final String originalName, final Set modifiers) { - this.key = key; + this.name = name; this.value = value; this.originalName = originalName; this.modifiers = modifiers; } - public String getKey() { - return key; + public String getName() { + return name; } public Object getValue() { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java index be0843d..df35782 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CFClassScanner.java @@ -26,6 +26,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Scans the classes for CF classes we want to modify. + */ public class CFClassScanner implements IClassScanner { private static final Logger LOGGER = LoggerFactory.getLogger(CFClassScanner.class); @@ -83,13 +86,13 @@ private Set loadCfTracepoint( // Lucee will not give us the code location using protection domain if (location == null) { // if we cannot get the code source then we guess the source using the provided path name - return CFUtils.loadCfBreakpoints( + return CFUtils.loadCfTracepoints( CFUtils.guessSource(loadedClass.getName()), values); } // Adobe CF should provide the location to the source, so we can use that. // todo it is possible to run precompiled CF code which would possibly have a different source location - return CFUtils.loadCfBreakpoints(location, values); + return CFUtils.loadCfTracepoints(location, values); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java index 9c81e91..efbde89 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/CompositeClassScanner.java @@ -25,6 +25,9 @@ import java.util.List; import java.util.Set; +/** + * A collection of scanners to run to collect classes to modify. + */ public class CompositeClassScanner implements IClassScanner { private final Set scanner = new HashSet<>(); @@ -46,6 +49,12 @@ public boolean scanClass(final Class clazz) { } + /** + * Scan the loaded classes for classes we should modify. + * + * @param inst the instrumentation + * @return an array of classes to modify + */ public Class[] scanAll(final Instrumentation inst) { final List> classes = new ArrayList<>(); final Class[] allLoadedClasses = inst.getAllLoadedClasses(); @@ -65,8 +74,8 @@ && scanClass(allLoadedClass)) { @Override public boolean isComplete() { - for (IClassScanner iClassScanner : scanner) { - if (!iClassScanner.isComplete()) { + for (IClassScanner classScanner : scanner) { + if (!classScanner.isComplete()) { return false; } } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java index dc5d893..394b39a 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/IClassScanner.java @@ -17,9 +17,23 @@ package com.intergral.deep.agent.tracepoint.inst; +/** + * Used to define a method to scan the loaded classes. + */ public interface IClassScanner { + /** + * Scan this class. + * + * @param clazz the class to sacn + * @return {@code true} if we should include this class + */ boolean scanClass(final Class clazz); + /** + * Is this class scanner complete. + * + * @return {@code true} if this scanner has nothing more to find + */ boolean isComplete(); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java index 96f44cd..ea1f551 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/InstUtils.java @@ -17,16 +17,31 @@ package com.intergral.deep.agent.tracepoint.inst; +/** + * Utilities to help with instrumentation. + */ public final class InstUtils { private InstUtils() { } + /** + * Get the external class name. + * + * @param className the class name + * @return the external class name + */ public static String externalClassName(final String className) { return className.replaceAll("/", "."); } + /** + * Get the name of the file from the path. + * + * @param relPath the path to a file + * @return the name of the file + */ public static String fileName(final String relPath) { final int lastIndexOf = relPath.lastIndexOf('/'); if (lastIndexOf == -1) { @@ -36,27 +51,59 @@ public static String fileName(final String relPath) { } - public static String internalClassStripInner(final Class allLoadedClass) { - return internalClassStripInner(allLoadedClass.getName()); + /** + * Convert the class name to the internal class name, remove any inner class names. + * + * @param clazz the class + * @return the internal class name without the inner classes + */ + public static String internalClassStripInner(final Class clazz) { + return internalClassStripInner(clazz.getName()); } - public static String internalClassStripInner(final String allLoadedClass) { - final int index = allLoadedClass.indexOf('$'); + /** + * Convert the class name to the internal class name, remove any inner class names. + * + * @param className the name of the class + * @return the internal class name without the inner classes + */ + public static String internalClassStripInner(final String className) { + final int index = className.indexOf('$'); if (index == -1) { - return internalClass(allLoadedClass); + return internalClass(className); } - return internalClass(allLoadedClass.substring(0, index)); + return internalClass(className.substring(0, index)); } + /** + * Get the internal class name. + * + * @param clazz the name of the class + * @return the internal name of the class + */ public static String internalClass(final String clazz) { return clazz.replaceAll("\\.", "/"); } + /** + * Get the internal class name. + * + * @param clazz the class + * @return the internal name of the class + */ public static String internalClass(final Class clazz) { return internalClass(clazz.getName()); } + /** + * Get the short version of the class name. + *

    + * Sometimes {@link Class#getSimpleName()} doesn't return a name. So we need one that always returns a name. + * + * @param className the class name + * @return the name of the class without the package + */ public static String shortClassName(final String className) { return className.substring(className.lastIndexOf('.') + 1); } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java index dc76750..1425dae 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/JSPClassScanner.java @@ -35,6 +35,13 @@ public class JSPClassScanner implements IClassScanner { private final List jspPackages; + /** + * Create a new JSP scanner. + * + * @param tracepoints the tracepoints + * @param jspSuffix the jsp suffix + * @param jspPackages the jsp packages + */ public JSPClassScanner(final Map tracepoints, final String jspSuffix, final List jspPackages) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java index c303953..066d070 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/SetClassScanner.java @@ -20,6 +20,9 @@ import java.util.Set; +/** + * Scans a set of classes for classes we want to modify. + */ public class SetClassScanner implements IClassScanner { private final Set classNames; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java index 093df60..dc0a10e 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationService.java @@ -48,6 +48,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This service deals with detecting which classes need to be transformed and uses the visitor to instrument the classes as needed. + */ public class TracepointInstrumentationService implements ClassFileTransformer { public static final long COMPUTE_ON_CLASS_VERSION = Long.getLong("nv.compute.class.version", 50L); @@ -60,18 +63,24 @@ public class TracepointInstrumentationService implements ClassFileTransformer { private final List jspPackages; /** - * We process JSP classes specially, so we collect them all under this class key + * We process JSP classes specially, so we collect them all under this class key. */ - private final String JSP_CLASS_KEY = "jsp"; + private static final String JSP_CLASS_KEY = "jsp"; /** - * We process CFM classes specially, so we collect them all under this class key + * We process CFM classes specially, so we collect them all under this class key. */ - private final String CFM_CLASS_KEY = "cfm"; + private static final String CFM_CLASS_KEY = "cfm"; private Map> classPrefixTracepoints = new ConcurrentHashMap<>(); + /** + * Create a new service. + * + * @param inst the instrumentation service + * @param settings the deep settings + */ public TracepointInstrumentationService(final Instrumentation inst, final Settings settings) { this.inst = inst; this.disPath = settings.getSettingAs("transform.path", String.class); @@ -80,6 +89,13 @@ public TracepointInstrumentationService(final Instrumentation inst, final Settin this.jspSuffix = settings.getSettingAs("jsp.suffix", String.class); } + /** + * Initialise the tracepoint service with the deep services. + * + * @param inst the instrumentation to use + * @param settings the settings for deep + * @return the configured service + */ public static TracepointInstrumentationService init(final Instrumentation inst, final Settings settings) { final TracepointInstrumentationService tracepointInstrumentationService = new TracepointInstrumentationService( @@ -92,8 +108,7 @@ public static TracepointInstrumentationService init(final Instrumentation inst, } /** - * Process the new config from the services and determine which classes need to be transformed, - * and trigger transformation. + * Process the new config from the services and determine which classes need to be transformed, and trigger transformation. * * @param breakpointResponse the new list of tracepoints that have been received from the server. */ @@ -144,9 +159,9 @@ public synchronized void processBreakpoints( // scanner to handle JSP classes if (this.classPrefixTracepoints.containsKey(JSP_CLASS_KEY) || existingTracepoints.containsKey(JSP_CLASS_KEY)) { - final Map jsp = this.classPrefixTracepoints.get(this.JSP_CLASS_KEY); + final Map jsp = this.classPrefixTracepoints.get(JSP_CLASS_KEY); final IClassScanner jspScanner = reTransFormJSPClasses(Utils.newMap(jsp), - Utils.newMap(existingTracepoints.get(this.JSP_CLASS_KEY))); + Utils.newMap(existingTracepoints.get(JSP_CLASS_KEY))); compositeClassScanner.addScanner(jspScanner); } @@ -172,7 +187,7 @@ public synchronized void processBreakpoints( } /** - * Calculate the classes to scan for JSP + * Calculate the classes to scan for JSP. * * @param newJSPState the new JSP state * @param previousJSPState the previous JSP state @@ -208,7 +223,7 @@ private Map withRemoved( } /** - * Calculate the classes to scan for CFM + * Calculate the classes to scan for CFM. * * @param newCFMState the new CFM state * @param previousCFMState the previous CFM state @@ -284,11 +299,11 @@ public byte[] transform(final ClassLoader loader, if (matchedTracepoints.isEmpty() && !this.classPrefixTracepoints.containsKey(CFM_CLASS_KEY) && !this.classPrefixTracepoints.containsKey(JSP_CLASS_KEY)) { return null; - } - // no breakpoints for this class, but we have a cfm breakpoints, and this is a cfm class - else if (matchedTracepoints.isEmpty() + } else if (matchedTracepoints.isEmpty() && this.classPrefixTracepoints.containsKey(CFM_CLASS_KEY) && CFUtils.isCfClass(classNameP)) { + // no breakpoints for this class, but we have a cfm breakpoints, and this is a cfm class + final Map cfm = this.classPrefixTracepoints.get(CFM_CLASS_KEY); final URL location = getLocation(protectionDomain); if (location == null) { @@ -297,20 +312,19 @@ else if (matchedTracepoints.isEmpty() // no need to expand frames here as we only need the version and source file reader.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE); final String sourceFile = cn.sourceFile; - iBreakpoints = CFUtils.loadCfBreakpoints(sourceFile, cfm); + iBreakpoints = CFUtils.loadCfTracepoints(sourceFile, cfm); } else { - iBreakpoints = CFUtils.loadCfBreakpoints(location, cfm); + iBreakpoints = CFUtils.loadCfTracepoints(location, cfm); } if (iBreakpoints.isEmpty()) { return null; } isCf = true; - } - // no breakpoints for this class, but we have a jsp breakpoints, and this is a jsp class - else if (matchedTracepoints.isEmpty() + } else if (matchedTracepoints.isEmpty() && this.classPrefixTracepoints.containsKey(JSP_CLASS_KEY) && JSPUtils.isJspClass(this.jspSuffix, this.jspPackages, InstUtils.externalClassName(className))) { + // no breakpoints for this class, but we have a jsp breakpoints, and this is a jsp class isCf = false; final SourceMap sourceMap = JSPUtils.getSourceMap(classfileBuffer); if (sourceMap == null) { @@ -332,13 +346,13 @@ else if (matchedTracepoints.isEmpty() if (mappedLines.isEmpty()) { continue; } + // todo should we install on all lines? final int start = mappedLines.get(0).getStart(); iBreakpoints.add(new JSPMappedBreakpoint(rawBreakpoint, start)); } } - } - // else there is a tracepoint for this class - else { + } else { + // else there is a tracepoint for this class isCf = false; iBreakpoints = matchedTracepoints; } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java index 5f7eb0e..258c3d9 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfo.java @@ -31,6 +31,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * We need to load classes without loading classes, so we have this type that lets us do this. + */ public class ClassInfo { private static final Logger LOGGER = LoggerFactory.getLogger(ClassInfo.class); @@ -45,6 +48,13 @@ public class ClassInfo { private final ClassLoader loader; + /** + * Create a new Class Info. + * + * @param type the type to load + * @param loader the loader to use + * @param classReader the class reader to use + */ public ClassInfo(final String type, final ClassLoader loader, final ClassReader classReader) { this.loader = loader; this.type = Type.getObjectType(type); @@ -52,16 +62,15 @@ public ClassInfo(final String type, final ClassLoader loader, final ClassReader } /** - * This method should not be used out side of this type! + * This method should not be used outside of this type! *

    * The intention for this method is to reduce the number of exceptions being generated. * * @param type the internal type, or class, name to look for. * @param loader the class loader to start from - * @param thro whether or not to throw the exception on a error. + * @param thro whether to throw the exception on a error. * @return either the {@link ClassInfo} for the given type, or null if it could not be found. If - * {@code thro} is {@code true} then a {@link ClassInfoNotFoundException} is throw when the class - * cannot be found. + * {@code thro} is {@code true} then a {@link ClassInfoNotFoundException} is throw when the class cannot be found. */ @SuppressWarnings("Duplicates") static ClassInfo loadClassInfo(final String type, final ClassLoader loader, final boolean thro) { @@ -204,7 +213,7 @@ boolean isInterface() { } - public boolean implementsInterface(final ClassInfo that) { + boolean implementsInterface(final ClassInfo that) { for (ClassInfo c = this; c != null; c = c.getSuperclass()) { ClassInfo[] tis = c.getInterfaces(); for (ClassInfo ti : tis) { @@ -227,7 +236,7 @@ private boolean isSubclassOf(final ClassInfo that) { } - public boolean isAssignableFrom(final ClassInfo that) { + boolean isAssignableFrom(final ClassInfo that) { if (this == that) { return true; } diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundException.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundException.java index f2782ff..8aada8d 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundException.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassInfoNotFoundException.java @@ -17,6 +17,9 @@ package com.intergral.deep.agent.tracepoint.inst.asm; +/** + * Used to indicate that a {@link ClassInfo} could not be loaded for a give type. + */ public class ClassInfoNotFoundException extends RuntimeException { private final String type; diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassLoaderAwareClassWriter.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassLoaderAwareClassWriter.java index d7abe93..6021490 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassLoaderAwareClassWriter.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/ClassLoaderAwareClassWriter.java @@ -23,6 +23,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This type helps improve the ability to {@link #getCommonSuperClass}. Essentially adding support to use the class loader used to load the + * class and dealing with some known cases of optional types. + */ public class ClassLoaderAwareClassWriter extends ClassWriter { private static final Logger LOGGER = LoggerFactory.getLogger(ClassLoaderAwareClassWriter.class); diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/SkipException.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/SkipException.java index cfb25b8..8b905f3 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/SkipException.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/SkipException.java @@ -17,6 +17,9 @@ package com.intergral.deep.agent.tracepoint.inst.asm; +/** + * Used to force ASM to skip a class, if we could not get the source information. + */ public class SkipException extends Error { public SkipException() { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java index d3ff9fe..4b6799c 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/TransformerUtils.java @@ -18,12 +18,14 @@ package com.intergral.deep.agent.tracepoint.inst.asm; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Collections; import java.util.List; +/** + * Utilities for transforming the classes. + */ public final class TransformerUtils { private TransformerUtils() { @@ -33,6 +35,15 @@ private TransformerUtils() { private static final List EXCLUDE_CONTAINS = Collections.emptyList(); + /** + * Store the transformed bytes to allow us to debug them in enabled. + * + * @param path the path to store the bytes + * @param original the unmodified bytes + * @param transformed the modified bytes + * @param className the class name being modified + * @return {@code true} if we stored the data + */ public static boolean storeUnsafe(String path, byte[] original, byte[] transformed, String className) { try { @@ -41,15 +52,15 @@ public static boolean storeUnsafe(String path, byte[] original, byte[] transform } store(path, original, transformed, className); return true; - } catch (IOException ex) { + } catch (Exception ex) { return false; } } - public static void store(String path, byte[] originalBytes, byte[] transformedBytes, + static void store(String path, byte[] originalBytes, byte[] transformedBytes, String className) - throws FileNotFoundException, IOException { + throws Exception { if (path == null) { return; } @@ -61,41 +72,43 @@ public static void store(String path, byte[] originalBytes, byte[] transformedBy String filename = className.replace("/", "_"); - { - File org = new File(dir, filename + ".class"); - if (org.exists()) { - org.delete(); - } - FileOutputStream out = new FileOutputStream(org, false); - out.write(originalBytes); - out.close(); - - org.setReadable(true, false); - org.setWritable(true, false); - org.setExecutable(true, false); - } + writeToFile(originalBytes, dir, filename + ".class"); - { - File transformed = new File(dir, filename + "_trnfm.class"); - if (transformed.exists()) { - transformed.delete(); - } - FileOutputStream out = new FileOutputStream(transformed, false); - out.write(transformedBytes); - out.close(); + writeToFile(transformedBytes, dir, filename + "_trnfm.class"); + } - transformed.setReadable(true, false); - transformed.setWritable(true, false); - transformed.setExecutable(true, false); + private static void writeToFile(final byte[] originalBytes, final File dir, final String filename) throws IOException { + File org = new File(dir, filename); + if (org.exists()) { + org.delete(); } + FileOutputStream out = new FileOutputStream(org, false); + out.write(originalBytes); + out.close(); + + org.setReadable(true, false); + org.setWritable(true, false); + org.setExecutable(true, false); } - public static boolean isExcludedClass(final Class loadedClass) { - return isExcludedClass(loadedClass.getName()); + /** + * Is the class excluded from transformation. + * + * @param clazz the class + * @return {@code true} if we should not transform this class. + */ + public static boolean isExcludedClass(final Class clazz) { + return isExcludedClass(clazz.getName()); } + /** + * Is the class excluded from transformation. + * + * @param classname the name of the class + * @return {@code true} if we should not transform this class. + */ public static boolean isExcludedClass(final String classname) { for (final String pkg : EXCLUDE_PACKAGES) { if (classname.startsWith(pkg)) { diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java index ba0d372..79e6c91 100644 --- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java +++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/asm/Visitor.java @@ -80,6 +80,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +/** + * This visitor is the main magic of deep. It deals with install the callbacks into the user code, based on the tracepoints. + */ @SuppressWarnings({"DuplicatedCode", "CommentedOutCode"}) public class Visitor extends ClassVisitor { @@ -125,6 +129,13 @@ public class Visitor extends ClassVisitor { } } + /** + * Create a new visitor. + * + * @param v the asm visitor that calls this + * @param bps the tracepoints we want to install + * @param isCf is this a cf class + */ public Visitor(final ClassVisitor v, final Collection bps, final boolean isCf) { super(ASM8, v); this.bps = bps; @@ -172,6 +183,142 @@ public void visitSource(String source, String debug) { filename = source; } + /** + * Converts primatives to objects so we can put them in the map. + * + * @param index variable index + * @param clazz variable class + * @param loadOpcode opcode + * @param primitive is primitive variable + * @return the instruction list + */ + private static InsnList box(final int index, final Type primitive, final Class clazz, + final int loadOpcode) { + final InsnList boxOperations = new InsnList(); + boxOperations.add(new VarInsnNode(loadOpcode, index)); + // Call Double.valueOf or Long.valueOf etc + boxOperations.add(new MethodInsnNode(INVOKESTATIC, Type.getInternalName(clazz), "valueOf", + Type.getMethodDescriptor(Type.getType(clazz), primitive), false)); + return boxOperations; + } + + /** + * Generates a XXLOAD operation for a specific variable slot type. + * + * @param t variable type + * @param index variable index + * @return the instruction list + */ + static InsnList loadVariable(final Type t, final int index) { + final int sort = t.getSort(); + switch (sort) { + case Type.BYTE: + return box(index, Type.BYTE_TYPE, Byte.class, ILOAD); + case Type.CHAR: + return box(index, Type.CHAR_TYPE, Character.class, ILOAD); + case Type.DOUBLE: + return box(index, Type.DOUBLE_TYPE, Double.class, DLOAD); + case Type.FLOAT: + return box(index, Type.FLOAT_TYPE, Float.class, FLOAD); + case Type.INT: + return box(index, Type.INT_TYPE, Integer.class, ILOAD); + case Type.LONG: + return box(index, Type.LONG_TYPE, Long.class, LLOAD); + case Type.SHORT: + return box(index, Type.SHORT_TYPE, Short.class, ILOAD); + case Type.BOOLEAN: + return box(index, Type.BOOLEAN_TYPE, Boolean.class, ILOAD); + case Type.ARRAY: + case Type.OBJECT: + final InsnList ops = new InsnList(); + ops.add(new VarInsnNode(ALOAD, index)); + return ops; + default: + // Something is very strange + LOGGER.error("loadVariable - Unknown type : {}", t); + final InsnList valueGetter = new InsnList(); + valueGetter.add(new LdcInsnNode("Unknown type :" + t)); + return valueGetter; + } + } + + private boolean isSuperCall(final AbstractInsnNode node) { + // need a method node + if (!(node instanceof MethodInsnNode)) { + return false; + } + + // we need to use INVOKESPECIAL on constructors + if (node.getOpcode() != INVOKESPECIAL) { + return false; + } + + // all constructors have the same name + if (!((MethodInsnNode) node).name.equals("")) { + return false; + } + + // check the super name against this call + return ((MethodInsnNode) node).owner.equals(this.superName); + } + + private boolean isThrow(final AbstractInsnNode node, final LabelNode start) { + if (start == null) { + return false; + } + + if (!(node instanceof InsnNode)) { + return false; + } + + final int opcode = node.getOpcode(); + return opcode == ATHROW; + } + + private boolean isNextLine(final AbstractInsnNode node, final LabelNode start) { + if (start == null) { + return false; + } + + return node.getType() == AbstractInsnNode.LINE; + } + + private boolean isReturn(final AbstractInsnNode node, final LabelNode start) { + if (start == null) { + return false; + } + + if (!(node instanceof InsnNode)) { + return false; + } + + final int opcode = node.getOpcode(); + switch (opcode) { + case Opcodes.RETURN: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.DRETURN: + return true; + default: + return false; + } + } + + private boolean isStatic(final int access) { + return (access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC; + } + + private boolean noneOrJustThis(final List localVariables) { + if (localVariables.isEmpty()) { + return true; + } + if (localVariables.size() == 1) { + return localVariables.get(0).name.equals("this"); + } + return false; + } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, @@ -515,13 +662,13 @@ public void visitEnd() { } // Use this to debug the raw byte code instruction changes in the even the visitors fail -// if(changed) -// { -// for( AbstractInsnNode instruction : instructions ) -// { -// System.out.println(InsnPrinter.prettyPrint( instruction )); -// } -// } + // if(changed) + // { + // for( AbstractInsnNode instruction : instructions ) + // { + // System.out.println(InsnPrinter.prettyPrint( instruction )); + // } + // } this.accept(jsrInlinerAdapter); @@ -667,154 +814,8 @@ private void processLocalVariables(final Set

    * This test can be used to play with visitor, it uses the {@link BPTestTarget} class to install tracepoints into *

    * To run this use the 'VisitorTest' saved config for idea, or add @@ -240,7 +241,8 @@ void constructor_end_line() throws Exception { assertEquals("my test", thisName.variable().getValue()); // there should however be a value for the 'super.name' field that is set to the known value - final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), snapshot.getVarLookupMap()); + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); assertTrue(superName.found()); assertEquals("i am a namemy test", superName.variable().getValue()); @@ -305,7 +307,8 @@ void constructor_start_line() throws Exception { assertEquals("null", thisName.variable().getValue()); // there should however be a value for the 'super.name' field that is set to the known value - final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), snapshot.getVarLookupMap()); + final IVariableScan superName = SnapshotUtils.findVarByName("BPSuperClass.name", variable.getChildrenList(), + snapshot.getVarLookupMap()); assertTrue(superName.found()); assertEquals("i am a namemy test", superName.variable().getValue()); @@ -1308,26 +1311,27 @@ void cfVisitor() throws Exception { // we cannot load the cf class in test case for some reason // consistent issues with verifier -// byteClassLoader.setBytes(name, transformed); -// final List classes = Arrays.asList("java.lang.Object", "coldfusion.tagext.io.OutputTag", "coldfusion.runtime.NeoPageContext", -// "coldfusion.runtime.CfJspPage", "coldfusion.runtime.CFPage"); -// for (String s : classes) { -// final Class aClass = byteClassLoader.loadClass(s); -// assertNotNull(aClass); -// assertEquals(aClass.getName(), s); -// } -// final Class aClass = byteClassLoader.loadClass(name); -// -// final Constructor constructor = aClass.getConstructor(); -// final Object instance = constructor.newInstance(); -// final Method runPage = aClass.getDeclaredMethod("runPage"); -// runPage.setAccessible(true); -// runPage.invoke(instance); -// -// final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); -// -// Mockito.verify(pushService, Mockito.times(1)) -// .pushSnapshot(argumentCaptor.capture(), Mockito.any()); + // byteClassLoader.setBytes(name, transformed); + // final List classes = Arrays.asList("java.lang.Object", "coldfusion.tagext.io.OutputTag", + // "coldfusion.runtime.NeoPageContext", + // "coldfusion.runtime.CfJspPage", "coldfusion.runtime.CFPage"); + // for (String s : classes) { + // final Class aClass = byteClassLoader.loadClass(s); + // assertNotNull(aClass); + // assertEquals(aClass.getName(), s); + // } + // final Class aClass = byteClassLoader.loadClass(name); + // + // final Constructor constructor = aClass.getConstructor(); + // final Object instance = constructor.newInstance(); + // final Method runPage = aClass.getDeclaredMethod("runPage"); + // runPage.setAccessible(true); + // runPage.invoke(instance); + // + // final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(EventSnapshot.class); + // + // Mockito.verify(pushService, Mockito.times(1)) + // .pushSnapshot(argumentCaptor.capture(), Mockito.any()); } @@ -1392,8 +1396,7 @@ void jspVisitorTest() throws Exception { } @Test - void luceeVisitorTest() - throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + void luceeVisitorTest() throws Exception { final MockTracepointConfig tracepointConfig = new MockTracepointConfig("/tests/testFile.cfm", 3); tracepointRef.set(Collections.singletonList(tracepointConfig)); // we need to process the jsp tracepoints diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java index 27695bf..de48c26 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPUtilsTest.java @@ -17,7 +17,10 @@ package com.intergral.deep.agent.tracepoint.inst.jsp; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.intergral.deep.agent.settings.Settings; import com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap.SmapUtils; @@ -32,66 +35,62 @@ import org.objectweb.asm.ClassReader; class JSPUtilsTest { + @Test - void isJspClass() - { - assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), - "org.apache.jsp.jdbc.views.companies_jsp" ) ); - assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), - "org.apache.jsp.views.companies_jsp" ) ); - assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), - "org.apache.jsp.companies_jsp" ) ); - assertTrue( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), - "jsp.companies_jsp" ) ); - - assertFalse( JSPUtils.isJspClass( "_jsp", Settings.coerc( "org.apache.jsp,jsp", List.class ), - "companies_jsp" ) ); + void isJspClass() { + assertTrue(JSPUtils.isJspClass("_jsp", Settings.coerc("org.apache.jsp,jsp", List.class), + "org.apache.jsp.jdbc.views.companies_jsp")); + assertTrue(JSPUtils.isJspClass("_jsp", Settings.coerc("org.apache.jsp,jsp", List.class), + "org.apache.jsp.views.companies_jsp")); + assertTrue(JSPUtils.isJspClass("_jsp", Settings.coerc("org.apache.jsp,jsp", List.class), + "org.apache.jsp.companies_jsp")); + assertTrue(JSPUtils.isJspClass("_jsp", Settings.coerc("org.apache.jsp,jsp", List.class), + "jsp.companies_jsp")); + + assertFalse(JSPUtils.isJspClass("_jsp", Settings.coerc("org.apache.jsp,jsp", List.class), + "companies_jsp")); } @Test - void sourceMap() throws Exception - { - final File file = new File( "src/test/resources" ); + void sourceMap() throws Exception { + final File file = new File("src/test/resources"); final URL resourceUrl = file.toURI().toURL(); - final URL[] classUrls = { resourceUrl }; - final URLClassLoader ucl = new URLClassLoader( classUrls ); - final Class c = ucl.loadClass( "org.apache.jsp.jdbc.views.companies_jsp" ); + final URL[] classUrls = {resourceUrl}; + final URLClassLoader ucl = new URLClassLoader(classUrls); + final Class c = ucl.loadClass("org.apache.jsp.jdbc.views.companies_jsp"); - final SourceMap sourceMap = JSPUtils.getSourceMap( c ); + final SourceMap sourceMap = JSPUtils.getSourceMap(c); - assertNotNull( sourceMap ); + assertNotNull(sourceMap); final List filenames = sourceMap.getFilenames(); - assertTrue( filenames.containsAll( Arrays.asList( "companies.jsp", "header.jsp", "setup.jsp", "footer.jsp" ) ) ); + assertTrue(filenames.containsAll(Arrays.asList("companies.jsp", "header.jsp", "setup.jsp", "footer.jsp"))); } @Test - void sourceMap_bytes() throws Exception - { - final InputStream resourceAsStream = getClass().getResourceAsStream( "/org/apache/jsp/jdbc/views/companies_jsp.class" ); + void sourceMap_bytes() throws Exception { + final InputStream resourceAsStream = getClass().getResourceAsStream("/org/apache/jsp/jdbc/views/companies_jsp.class"); final byte[] bytes = new byte[resourceAsStream.available()]; - resourceAsStream.read( bytes ); + resourceAsStream.read(bytes); - final SourceMap sourceMap = JSPUtils.getSourceMap( bytes ); + final SourceMap sourceMap = JSPUtils.getSourceMap(bytes); - assertNotNull( sourceMap ); + assertNotNull(sourceMap); final List filenames = sourceMap.getFilenames(); - assertTrue( filenames.containsAll( Arrays.asList( "companies.jsp", "header.jsp", "setup.jsp", "footer.jsp" ) ) ); + assertTrue(filenames.containsAll(Arrays.asList("companies.jsp", "header.jsp", "setup.jsp", "footer.jsp"))); } @Test - void sourceMap_bytes_src() throws Exception - { - final InputStream resourceAsStream = getClass().getResourceAsStream( "/org/apache/jsp/jdbc/views/companies_jsp.class" ); + void sourceMap_bytes_src() throws Exception { + final InputStream resourceAsStream = getClass().getResourceAsStream("/org/apache/jsp/jdbc/views/companies_jsp.class"); final byte[] bytes = new byte[resourceAsStream.available()]; - resourceAsStream.read( bytes ); - + resourceAsStream.read(bytes); - final String src = SmapUtils.scanSource( new ClassReader( bytes ) ); + final String src = SmapUtils.scanSource(new ClassReader(bytes)); - assertNotNull( src ); - assertEquals( "companies_jsp.java", src ); + assertNotNull(src); + assertEquals("companies_jsp.java", src); } } \ No newline at end of file diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java index 448975c..9b920e9 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/FileSectionEntryTest.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -5,45 +22,39 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class FileSectionEntryTest -{ +class FileSectionEntryTest { - private FileSectionEntry fileSectionEntry; + private FileSectionEntry fileSectionEntry; - @BeforeEach - void setUp() - { - new FileSectionEntry( 10, "source" ); - fileSectionEntry = new FileSectionEntry( 10, "source", "path" ); - } + @BeforeEach + void setUp() { + new FileSectionEntry(10, "source"); + fileSectionEntry = new FileSectionEntry(10, "source", "path"); + } - @Test - void getSourceName() - { - assertEquals( "source", fileSectionEntry.getSourceName() ); - } + @Test + void getSourceName() { + assertEquals("source", fileSectionEntry.getSourceName()); + } - @Test - void getId() - { - assertEquals( 10, fileSectionEntry.getId() ); - } + @Test + void getId() { + assertEquals(10, fileSectionEntry.getId()); + } - @Test - void getSourcePath() - { - assertEquals( "path", fileSectionEntry.getSourcePath() ); - } + @Test + void getSourcePath() { + assertEquals("path", fileSectionEntry.getSourcePath()); + } - @Test - void toStringTest() - { - assertEquals( "FileSectionEntry#10:source:path", fileSectionEntry.toString() ); - } + @Test + void toStringTest() { + assertEquals("FileSectionEntry#10:source:path", fileSectionEntry.toString()); + } } diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java index 2e61d8c..2f50abb 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/LineSectionEntryTest.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -5,23 +22,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class LineSectionEntryTest -{ +class LineSectionEntryTest { - private LineSectionEntry lineSectionEntry; + private LineSectionEntry lineSectionEntry; - @BeforeEach - void setUp() - { - lineSectionEntry = new LineSectionEntry( 1, 101, 10, 202, 2 ); - } + @BeforeEach + void setUp() { + lineSectionEntry = new LineSectionEntry(1, 101, 10, 202, 2); + } - @Test - void testToString() - { - assertEquals( "LineSectionEntry#101 1 10 202 2", lineSectionEntry.toString() ); - } + @Test + void testToString() { + assertEquals("LineSectionEntry#101 1 10 202 2", lineSectionEntry.toString()); + } } diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java deleted file mode 100644 index 602be01..0000000 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SouceMapParserTest.java +++ /dev/null @@ -1,283 +0,0 @@ -/** - * Copyright (C) 2019 Intergral Information Solutions GmbH. All Rights Reserved - */ -package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.List; -import org.junit.jupiter.api.Test; - -public class SouceMapParserTest { - - public SouceMapParserTest() { - } - - - private String fileToString(final String filename) throws IOException { - final byte[] bytes; - try (InputStream resourceAsStream = getClass().getResourceAsStream(filename)) { - bytes = new byte[resourceAsStream.available()]; - resourceAsStream.read(bytes); - } - return new String(bytes); - } - - - @Test - public void testIndexJsp() throws Exception { - final SourceMapParser parser = new SourceMapParser(fileToString("/index_jsp.smap")); - final SourceMap smap = parser.parse(); - - //smap.dumpInformation(); - { - final List found = smap.map("header.jsp", 1); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(71, entry.getStart(), "Should start @ line 71"); - assertEquals(71, entry.getEnd(), "Should end @ line 71"); - } - - { - final List found = smap.map("header.jsp", 11); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(83, entry.getStart(), "Should start @ line 83"); - assertEquals(87, entry.getEnd(), "Should end @ line 87"); - } - - { - final List found = smap.map("footer.jsp", 4); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(138, entry.getStart(), "Should start @ line 138"); - assertEquals(140, entry.getEnd(), "Should end @ line 140"); - } - - { - final List found = smap.map("index.jsp", 4); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(92, entry.getStart(), "Should start @ line 92"); - assertEquals(92, entry.getEnd(), "Should end @ line 92"); - } - } - - - /* Test based on example table from JSR-045 document - Input Source Output Source - Line Begin Line End Line - 123 207 207 - 130 210 210 - 131 211 211 - 132 212 212 - 140 250 256 - 160 300 301 - 161 302 303 - 162 304 305*/ - @Test - public void testIndex2Jsp() throws Exception { - final SourceMapParser parser = new SourceMapParser(fileToString("/index2_jsp.smap")); - final SourceMap smap = parser.parse(); - - // smap.dumpInformation(); - { - final List found = smap.map("index2.jsp", 123); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(207, entry.getStart(), "Should start @ line 207"); - assertEquals(207, entry.getEnd(), "Should end @ line 207"); - } - - { - final List found = smap.map("index2.jsp", 140); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(250, entry.getStart(), "Should start @ line 250"); - assertEquals(256, entry.getEnd(), "Should end @ line 256"); - } - - { - final List found = smap.map("index2.jsp", 160); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(300, entry.getStart(), "Should start @ line 300"); - assertEquals(301, entry.getEnd(), "Should end @ line 301"); - } - - { - final List found = smap.map("index2.jsp", 162); - assertEquals(1, found.size(), "Should fine 1 line"); - - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(304, entry.getStart(), "Should start @ line 304"); - assertEquals(305, entry.getEnd(), "Should end @ line 305"); - } - } - - - @Test - public void testParseIncludeTimeJsp() throws Exception { - // Test with a page including the same jsp twice. - - final SourceMapParser parser = new SourceMapParser(fileToString("/include_time.smap")); - final SourceMap smap = parser.parse(); - - // smap.dumpInformation(); - { - final List found = smap.map("time.jsp", 1); - assertEquals(2, found.size(), "Should fine 2 line"); - { - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(90, entry.getStart(), "Should start @ line 90"); - assertEquals(91, entry.getEnd(), "Should end @ line 91"); - } - { - final SourceMapLineStartEnd entry = found.get(1); - assertEquals(99, entry.getStart(), "Should start @ line 99"); - assertEquals(100, entry.getEnd(), "Should end @ line 100"); - } - } - - { - final List found = smap.map("footer.jsp", 29); - assertEquals(1, found.size(), "Should fine 1 line"); - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(140, entry.getStart(), "Should start @ line 140"); - assertEquals(140, entry.getEnd(), "Should end @ line 140"); - } - } - - - @Test - public void testParseIncludeTime2Jsp() throws Exception { - final SourceMapParser parser = new SourceMapParser(fileToString("/include_time2.smap")); - final SourceMap smap = parser.parse(); - - //smap.dumpInformation(); - - final List found = smap.map("time.jsp", 1); - assertEquals(4, found.size(), "Should contain 4 entries"); - { - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(91, entry.getStart(), "Should start @ line 91"); - assertEquals(92, entry.getEnd(), "Should end @ line 92"); - } - { - final SourceMapLineStartEnd entry = found.get(1); - assertEquals(102, entry.getStart(), "Should start @ line 102"); - assertEquals(103, entry.getEnd(), "Should end @ line 103"); - } - { - final SourceMapLineStartEnd entry = found.get(2); - assertEquals(114, entry.getStart(), "Should start @ line 114"); - assertEquals(115, entry.getEnd(), "Should end @ line 115"); - } - { - final SourceMapLineStartEnd entry = found.get(3); - assertEquals(117, entry.getStart(), "Should start @ line 117"); - assertEquals(118, entry.getEnd(), "Should end @ line 119"); - } - } - - - @Test - public void testIncludeTime3Jsp() throws Exception { - final SourceMapParser parser = new SourceMapParser(fileToString("/include_time3.smap")); - final SourceMap smap = parser.parse(); - - final List found = smap.map("time.jsp", 1); - assertEquals(3, found.size(), "Should contain 3 entries"); - { - final SourceMapLineStartEnd entry = found.get(0); - assertEquals(91, entry.getStart(), "Should start @ line 91"); - assertEquals(92, entry.getEnd(), "Should end @ line 92"); - } - { - final SourceMapLineStartEnd entry = found.get(1); - assertEquals(102, entry.getStart(), "Should start @ line 102"); - assertEquals(103, entry.getEnd(), "Should end @ line 103"); - } - { - final SourceMapLineStartEnd entry = found.get(2); - assertEquals(113, entry.getStart(), "Should start @ line 113"); - assertEquals(116, entry.getEnd(), "Should end @ line 116"); - } - } - - - @Test - public void testIncludeTime3JspLookup() throws Exception { - final String s = fileToString("/include_time3.smap"); - final SourceMapParser parser = new SourceMapParser(s); - final SourceMap smap = parser.parse(); - - //smap.dumpInformation(); - // line 91 doesnt map back to line 1 as there 2 files which match to line 91 - // and the first include_time.jsp should take precident according to the spec. - // 1 include_time.jsp 4 -> 91 - // 2 time.jsp 1 -> 91 - final SourceMapLookup lookup = smap.lookup(92); - - assertEquals("time.jsp", lookup.getFilename(), "Should find time.jsp line 1"); - assertEquals(1, lookup.getLineNumber(), "Should find time.jsp line 1"); - } - - - @Test - public void testCompaniesJsp() throws Exception { - final URL resourceUrl = getClass().getResource("/org/apache/jsp/jdbc/views/companies_jsp.class"); - final URL[] classUrls = {resourceUrl}; - final URLClassLoader ucl = new URLClassLoader(classUrls); - final Class c = ucl.loadClass("org.apache.jsp.jdbc.views.companies_jsp"); - - final String sourceDebugExtension = SmapUtils.lookUp(c); - assertNotNull(sourceDebugExtension, "smap should not be null"); - assertFalse(sourceDebugExtension.isEmpty(), "smap should not be emtpy"); - - assertTrue(sourceDebugExtension.startsWith("SMAP"), "smap should start with SMAP"); - assertTrue(sourceDebugExtension.trim().endsWith("*E"), "smap should end with *E"); - - final SourceMapParser parser = new SourceMapParser(sourceDebugExtension); - final SourceMap smap = parser.parse(); - - //smap.dumpInformation(); - - final SourceMapLookup lookup = smap.lookup(458); - assertNull(lookup, "Should not find a line for 458"); - - assertEquals(71, smap.lookup(457).getLineNumber()); - } - - - @Test - public void testNoopJspBug() throws Exception { - // This code will cause a Null pointer exception FR-5116 - // as the SMAP is corrupt - final String s = fileToString("/noop.smap"); - final SourceMapParser parser = new SourceMapParser(s); - try { - final SourceMap smap = parser.parse(); - fail("Should not parse"); - } catch (IOException ioe) { - assertEquals("Cannot find a Stratum Section in SMAP", ioe.getMessage()); - } - } -} diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParserTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParserTest.java new file mode 100644 index 0000000..865a7a1 --- /dev/null +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/jsp/sourcemap/SourceMapParserTest.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.intergral.deep.agent.tracepoint.inst.jsp.sourcemap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class SourceMapParserTest { + + public SourceMapParserTest() { + } + + + private String fileToString(final String filename) throws IOException { + final byte[] bytes; + try (InputStream resourceAsStream = getClass().getResourceAsStream(filename)) { + bytes = new byte[resourceAsStream.available()]; + resourceAsStream.read(bytes); + } + return new String(bytes); + } + + + @Test + public void testIndexJsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/index_jsp.smap")); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + { + final List found = smap.map("header.jsp", 1); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(71, entry.getStart(), "Should start @ line 71"); + assertEquals(71, entry.getEnd(), "Should end @ line 71"); + } + + { + final List found = smap.map("header.jsp", 11); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(83, entry.getStart(), "Should start @ line 83"); + assertEquals(87, entry.getEnd(), "Should end @ line 87"); + } + + { + final List found = smap.map("footer.jsp", 4); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(138, entry.getStart(), "Should start @ line 138"); + assertEquals(140, entry.getEnd(), "Should end @ line 140"); + } + + { + final List found = smap.map("index.jsp", 4); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(92, entry.getStart(), "Should start @ line 92"); + assertEquals(92, entry.getEnd(), "Should end @ line 92"); + } + } + + + /* Test based on example table from JSR-045 document + Input Source Output Source + Line Begin Line End Line + 123 207 207 + 130 210 210 + 131 211 211 + 132 212 212 + 140 250 256 + 160 300 301 + 161 302 303 + 162 304 305*/ + @Test + public void testIndex2Jsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/index2_jsp.smap")); + final SourceMap smap = parser.parse(); + + // smap.dumpInformation(); + { + final List found = smap.map("index2.jsp", 123); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(207, entry.getStart(), "Should start @ line 207"); + assertEquals(207, entry.getEnd(), "Should end @ line 207"); + } + + { + final List found = smap.map("index2.jsp", 140); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(250, entry.getStart(), "Should start @ line 250"); + assertEquals(256, entry.getEnd(), "Should end @ line 256"); + } + + { + final List found = smap.map("index2.jsp", 160); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(300, entry.getStart(), "Should start @ line 300"); + assertEquals(301, entry.getEnd(), "Should end @ line 301"); + } + + { + final List found = smap.map("index2.jsp", 162); + assertEquals(1, found.size(), "Should fine 1 line"); + + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(304, entry.getStart(), "Should start @ line 304"); + assertEquals(305, entry.getEnd(), "Should end @ line 305"); + } + } + + + @Test + public void testParseIncludeTimeJsp() throws Exception { + // Test with a page including the same jsp twice. + + final SourceMapParser parser = new SourceMapParser(fileToString("/include_time.smap")); + final SourceMap smap = parser.parse(); + + // smap.dumpInformation(); + { + final List found = smap.map("time.jsp", 1); + assertEquals(2, found.size(), "Should fine 2 line"); + { + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(90, entry.getStart(), "Should start @ line 90"); + assertEquals(91, entry.getEnd(), "Should end @ line 91"); + } + { + final SourceMapLineStartEnd entry = found.get(1); + assertEquals(99, entry.getStart(), "Should start @ line 99"); + assertEquals(100, entry.getEnd(), "Should end @ line 100"); + } + } + + { + final List found = smap.map("footer.jsp", 29); + assertEquals(1, found.size(), "Should fine 1 line"); + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(140, entry.getStart(), "Should start @ line 140"); + assertEquals(140, entry.getEnd(), "Should end @ line 140"); + } + } + + + @Test + public void testParseIncludeTime2Jsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/include_time2.smap")); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + + final List found = smap.map("time.jsp", 1); + assertEquals(4, found.size(), "Should contain 4 entries"); + { + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(91, entry.getStart(), "Should start @ line 91"); + assertEquals(92, entry.getEnd(), "Should end @ line 92"); + } + { + final SourceMapLineStartEnd entry = found.get(1); + assertEquals(102, entry.getStart(), "Should start @ line 102"); + assertEquals(103, entry.getEnd(), "Should end @ line 103"); + } + { + final SourceMapLineStartEnd entry = found.get(2); + assertEquals(114, entry.getStart(), "Should start @ line 114"); + assertEquals(115, entry.getEnd(), "Should end @ line 115"); + } + { + final SourceMapLineStartEnd entry = found.get(3); + assertEquals(117, entry.getStart(), "Should start @ line 117"); + assertEquals(118, entry.getEnd(), "Should end @ line 119"); + } + } + + + @Test + public void testIncludeTime3Jsp() throws Exception { + final SourceMapParser parser = new SourceMapParser(fileToString("/include_time3.smap")); + final SourceMap smap = parser.parse(); + + final List found = smap.map("time.jsp", 1); + assertEquals(3, found.size(), "Should contain 3 entries"); + { + final SourceMapLineStartEnd entry = found.get(0); + assertEquals(91, entry.getStart(), "Should start @ line 91"); + assertEquals(92, entry.getEnd(), "Should end @ line 92"); + } + { + final SourceMapLineStartEnd entry = found.get(1); + assertEquals(102, entry.getStart(), "Should start @ line 102"); + assertEquals(103, entry.getEnd(), "Should end @ line 103"); + } + { + final SourceMapLineStartEnd entry = found.get(2); + assertEquals(113, entry.getStart(), "Should start @ line 113"); + assertEquals(116, entry.getEnd(), "Should end @ line 116"); + } + } + + + @Test + public void testIncludeTime3JspLookup() throws Exception { + final String s = fileToString("/include_time3.smap"); + final SourceMapParser parser = new SourceMapParser(s); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + // line 91 doesnt map back to line 1 as there 2 files which match to line 91 + // and the first include_time.jsp should take precident according to the spec. + // 1 include_time.jsp 4 -> 91 + // 2 time.jsp 1 -> 91 + final SourceMapLookup lookup = smap.lookup(92); + + assertEquals("time.jsp", lookup.getFilename(), "Should find time.jsp line 1"); + assertEquals(1, lookup.getLineNumber(), "Should find time.jsp line 1"); + } + + + @Test + public void testCompaniesJsp() throws Exception { + final URL resourceUrl = getClass().getResource("/org/apache/jsp/jdbc/views/companies_jsp.class"); + final URL[] classUrls = {resourceUrl}; + final URLClassLoader ucl = new URLClassLoader(classUrls); + final Class c = ucl.loadClass("org.apache.jsp.jdbc.views.companies_jsp"); + + final String sourceDebugExtension = SmapUtils.lookUp(c); + assertNotNull(sourceDebugExtension, "smap should not be null"); + assertFalse(sourceDebugExtension.isEmpty(), "smap should not be emtpy"); + + assertTrue(sourceDebugExtension.startsWith("SMAP"), "smap should start with SMAP"); + assertTrue(sourceDebugExtension.trim().endsWith("*E"), "smap should end with *E"); + + final SourceMapParser parser = new SourceMapParser(sourceDebugExtension); + final SourceMap smap = parser.parse(); + + //smap.dumpInformation(); + + final SourceMapLookup lookup = smap.lookup(458); + assertNull(lookup, "Should not find a line for 458"); + + assertEquals(71, smap.lookup(457).getLineNumber()); + } + + + @Test + public void testNoopJspBug() throws Exception { + // This code will cause a Null pointer exception FR-5116 + // as the SMAP is corrupt + final String s = fileToString("/noop.smap"); + final SourceMapParser parser = new SourceMapParser(s); + try { + final SourceMap smap = parser.parse(); + fail("Should not parse"); + } catch (IOException ioe) { + assertEquals("Cannot find a Stratum Section in SMAP", ioe.getMessage()); + } + } +} diff --git a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java index 08cbcd7..5844623 100644 --- a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java +++ b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java @@ -22,6 +22,9 @@ import java.util.Arrays; import java.util.HashMap; +/** + * A mock tracepoint config to make it easier to create during testing. + */ public class MockTracepointConfig extends TracePointConfig { public MockTracepointConfig() { diff --git a/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java b/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java index 7b04984..a14118b 100644 --- a/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java +++ b/agent/src/test/java/com/intergral/deep/test/target/BPSuperClass.java @@ -3,10 +3,10 @@ @SuppressWarnings({"FieldCanBeLocal", "unused"}) public class BPSuperClass { - private final String name; - private final int notOnSubClass = 11; + private final String name; + private final int notOnSubClass = 11; - public BPSuperClass(final String name) { - this.name = name; - } + public BPSuperClass(final String name) { + this.name = name; + } } diff --git a/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java b/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java index efea5ac..b01b2ab 100644 --- a/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java +++ b/agent/src/test/java/com/intergral/deep/test/target/ConditionTarget.java @@ -18,5 +18,6 @@ package com.intergral.deep.test.target; public class ConditionTarget { + public int i = 100; } diff --git a/agent/src/test/java/com/intergral/deep/Person.java b/agent/src/test/java/com/intergral/deep/test/target/Person.java similarity index 96% rename from agent/src/test/java/com/intergral/deep/Person.java rename to agent/src/test/java/com/intergral/deep/test/target/Person.java index 5b3f1e4..536f1e4 100644 --- a/agent/src/test/java/com/intergral/deep/Person.java +++ b/agent/src/test/java/com/intergral/deep/test/target/Person.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package com.intergral.deep; +package com.intergral.deep.test.target; public class Person { diff --git a/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java b/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java index 2fdba45..01af4a7 100644 --- a/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java +++ b/agent/src/test/java/com/intergral/deep/test/target/VariableSuperSuperTest.java @@ -19,5 +19,6 @@ @SuppressWarnings("ALL") public class VariableSuperSuperTest { + public static int i = 11; } diff --git a/agent/src/test/java/lucee/commons/color/ConstantsDouble.java b/agent/src/test/java/lucee/commons/color/ConstantsDouble.java index a7d0fb5..e1d34bb 100644 --- a/agent/src/test/java/lucee/commons/color/ConstantsDouble.java +++ b/agent/src/test/java/lucee/commons/color/ConstantsDouble.java @@ -19,5 +19,6 @@ @SuppressWarnings("ALL") public class ConstantsDouble { -public static Double _100 = Double.valueOf(100.0d); + + public static Double _100 = Double.valueOf(100.0d); } diff --git a/agent/src/test/java/lucee/runtime/PageContext.java b/agent/src/test/java/lucee/runtime/PageContext.java index 52a0d99..15c728a 100644 --- a/agent/src/test/java/lucee/runtime/PageContext.java +++ b/agent/src/test/java/lucee/runtime/PageContext.java @@ -23,51 +23,54 @@ @SuppressWarnings("ALL") public class PageContext { - private Object server = new Scope("server"); - private Object variables = new Scope("variables"); - private Object local = new Scope("local"); - private Object cookie = new Scope("cookie"); - private Object session = new Scope("session"); - private Object application = new Scope("application"); - private Object cgiR = new Scope("cgiR"); - private Object request = new Scope("request"); - private Object _form = new Scope("_form"); - private Object _url = new Scope("_url"); - private Object client = new Scope("client"); - private Object threads = new Scope("threads"); + private Object server = new Scope("server"); + private Object variables = new Scope("variables"); + private Object local = new Scope("local"); + private Object cookie = new Scope("cookie"); + private Object session = new Scope("session"); + private Object application = new Scope("application"); + private Object cgiR = new Scope("cgiR"); + private Object request = new Scope("request"); + private Object _form = new Scope("_form"); + private Object _url = new Scope("_url"); + private Object client = new Scope("client"); + private Object threads = new Scope("threads"); - public void write(String str) { + public void write(String str) { - } + } - public Undefined us() { - return new Undefined(){ - @Override - public Object get(final Key k) { - return new Object(); - } + public Undefined us() { + return new Undefined() { + @Override + public Object get(final Key k) { + return new Object(); + } - @Override - public Object set(final Key ket, final Object obj) { - return obj; - } - }; - } + @Override + public Object set(final Key ket, final Object obj) { + return obj; + } + }; + } - public void outputStart(){} - public void outputEnd(){} + public void outputStart() { + } - public static class Scope { + public void outputEnd() { + } - private final String threads; + public static class Scope { + private final String threads; - public Scope(final String threads) { - this.threads = threads; - } - public boolean isInitalized() { - return false; - } + public Scope(final String threads) { + this.threads = threads; + } + + public boolean isInitalized() { + return false; } + } } diff --git a/agent/src/test/java/lucee/runtime/PageContextImpl.java b/agent/src/test/java/lucee/runtime/PageContextImpl.java index 8bd0f14..6f90fbe 100644 --- a/agent/src/test/java/lucee/runtime/PageContextImpl.java +++ b/agent/src/test/java/lucee/runtime/PageContextImpl.java @@ -18,10 +18,9 @@ package lucee.runtime; @SuppressWarnings("ALL") -public class PageContextImpl -{ - public String evaluate( final String expression ) - { - return expression; - } +public class PageContextImpl { + + public String evaluate(final String expression) { + return expression; + } } diff --git a/agent/src/test/java/lucee/runtime/PageImpl.java b/agent/src/test/java/lucee/runtime/PageImpl.java index 4237048..c555464 100644 --- a/agent/src/test/java/lucee/runtime/PageImpl.java +++ b/agent/src/test/java/lucee/runtime/PageImpl.java @@ -20,13 +20,14 @@ import lucee.runtime.type.UDFProperties; @SuppressWarnings("ALL") -public class PageImpl -{ - protected UDFProperties[] udfs; +public class PageImpl { - protected void setPageSource(PageSource source){} + protected UDFProperties[] udfs; - public static class SomePageImpl extends PageImpl { + protected void setPageSource(PageSource source) { + } - } + public static class SomePageImpl extends PageImpl { + + } } diff --git a/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java b/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java index b17db32..864def5 100644 --- a/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java +++ b/agent/src/test/java/lucee/runtime/interpreter/VariableInterpreter.java @@ -22,7 +22,7 @@ @SuppressWarnings("ALL") public class VariableInterpreter { - public static VariableReference getVariableReference(lucee.runtime.PageContext ctx, String name){ + public static VariableReference getVariableReference(lucee.runtime.PageContext ctx, String name) { return new VariableReference(); } } diff --git a/agent/src/test/java/lucee/runtime/op/Caster.java b/agent/src/test/java/lucee/runtime/op/Caster.java index 9037a47..6b8805e 100644 --- a/agent/src/test/java/lucee/runtime/op/Caster.java +++ b/agent/src/test/java/lucee/runtime/op/Caster.java @@ -19,14 +19,16 @@ @SuppressWarnings("ALL") public class Caster { - public static Double toDouble(double d){ + + public static Double toDouble(double d) { return d; } - public static double toDoubleValue(Double d){ + + public static double toDoubleValue(Double d) { return d; } - public static String toString(Object obj){ + public static String toString(Object obj) { return ""; } } diff --git a/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java b/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java index d3e2ea5..ffda8d4 100644 --- a/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java +++ b/agent/src/test/java/lucee/runtime/scope/MockLuceeScope.java @@ -20,6 +20,6 @@ import java.util.HashMap; @SuppressWarnings("ALL") -public class MockLuceeScope extends HashMap -{ +public class MockLuceeScope extends HashMap { + } diff --git a/agent/src/test/java/lucee/runtime/type/Collection.java b/agent/src/test/java/lucee/runtime/type/Collection.java index 64fa2e5..c8dd4d5 100644 --- a/agent/src/test/java/lucee/runtime/type/Collection.java +++ b/agent/src/test/java/lucee/runtime/type/Collection.java @@ -19,7 +19,8 @@ @SuppressWarnings("ALL") public class Collection { -public static class Key { -} + public static class Key { + + } } diff --git a/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java b/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java index 4df1aac..375fe64 100644 --- a/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java +++ b/agent/src/test/java/lucee/runtime/type/ref/VariableReference.java @@ -20,5 +20,6 @@ @SuppressWarnings("ALL") public class VariableReference { - public void set(double d){} + public void set(double d) { + } } diff --git a/agent/src/test/java/railo/commons/lang/PCLBlock.java b/agent/src/test/java/railo/commons/lang/PCLBlock.java index 5bfeae4..487ff5c 100644 --- a/agent/src/test/java/railo/commons/lang/PCLBlock.java +++ b/agent/src/test/java/railo/commons/lang/PCLBlock.java @@ -18,6 +18,6 @@ package railo.commons.lang; @SuppressWarnings("ALL") -public class PCLBlock extends ClassLoader{ +public class PCLBlock extends ClassLoader { } diff --git a/agent/src/test/resources/logging.properties b/agent/src/test/resources/logging.properties index 68285a1..8d1a982 100644 --- a/agent/src/test/resources/logging.properties +++ b/agent/src/test/resources/logging.properties @@ -1,3 +1,20 @@ +# +# Copyright (C) 2023 Intergral GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# + .level=FINEST handlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=FINEST diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index d63945c..71fbc26 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -20,7 +20,36 @@ "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/checkstyle.xml b/checkstyle.xml index c0e09e2..2945096 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -344,7 +344,6 @@ - @@ -405,6 +404,7 @@ + \ No newline at end of file diff --git a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java index 40d6bb4..5925a64 100644 --- a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java +++ b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java @@ -17,12 +17,17 @@ package com.intergral.deep.plugins.cf; -import com.intergral.deep.agent.api.plugin.ISnapshotContext; import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.api.settings.ISettings; import java.util.HashMap; +/** + * This plugin is activated when we are running on an adobe CF server. + *

    + * This plugin will attach the cf version and the cf app name to the captured snapshots. + */ public class CFPlugin implements IPlugin { @Override diff --git a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java index aee9f15..64c30cf 100644 --- a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java +++ b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/Utils.java @@ -17,15 +17,30 @@ package com.intergral.deep.plugins.cf; +/** + * A small collection of utils used to capture the adobe coldfusion version number. + */ public final class Utils { private Utils() { } + /** + * Are we running on a CF server. + *

    + * By looking at the java start up command we can tell if this is a CF server. + * + * @return {@code true} if we are on a coldfusion server. + */ public static boolean isCFServer() { return System.getProperty("sun.java.command").contains("coldfusion"); } + /** + * Try to load the coldfusion version number. + * + * @return the major version of adobe coldfusion. + */ public static String loadCFVersion() { try { return String.valueOf(Thread.currentThread() diff --git a/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java b/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java index 0d3e5e7..8f8c049 100644 --- a/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java +++ b/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java @@ -17,13 +17,16 @@ package com.intergral.deep.plugin; -import com.intergral.deep.agent.api.plugin.ISnapshotContext; import com.intergral.deep.agent.api.plugin.IPlugin; +import com.intergral.deep.agent.api.plugin.ISnapshotContext; import com.intergral.deep.agent.api.resource.Resource; import com.intergral.deep.agent.api.settings.ISettings; import java.util.HashMap; import java.util.Map; +/** + * This plugin captures the thread name of the thread the snapshot was cpatured on. + */ public class JavaPlugin implements IPlugin { private final Map basic = new HashMap<>(); diff --git a/pom.xml b/pom.xml index 1b8641c..7bde4ef 100644 --- a/pom.xml +++ b/pom.xml @@ -292,6 +292,7 @@ true true false + warning diff --git a/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java b/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java index 1bc1821..288e8e2 100644 --- a/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java +++ b/reflect-api/src/main/java/com/intergral/deep/reflect/ReflectionImpl.java @@ -32,6 +32,9 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * The version of reflection used before modules where added. + */ public class ReflectionImpl implements IReflection { @Override @@ -108,7 +111,7 @@ public Method findMethod(final Class originalClazz, final String methodName, return null; } - + @Override public Field getField(final Object obj, final String fieldName) { Class clazz = obj.getClass(); while (clazz != null) { diff --git a/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java b/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java index ae0e4d5..577da4a 100644 --- a/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java +++ b/reflect-java-9/src/main/java/com/intergral/deep/reflect/Java9ReflectionImpl.java @@ -21,6 +21,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +/** + * The version of reflection that deals with modules. + */ public class Java9ReflectionImpl extends ReflectionImpl { @Override diff --git a/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java b/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java index d9082cc..83dfa19 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/AssertUtils.java @@ -23,6 +23,14 @@ public class AssertUtils { + /** + * Assert that a collection contains an item that matches the function. + * + * @param list the collection to scan + * @param compareFunction the function to run + * @param the type of items in the collection + * @return the result of the compare + */ public static int assertContains(final Collection list, final ICompareFunction compareFunction) { int index = -1; for (final T listItem : list) { diff --git a/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java b/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java index fd7c274..e398e6b 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/grpc/TestInterceptor.java @@ -36,7 +36,7 @@ public TestInterceptor(final String key) { } public Context.Key contextKey() { - if(contextKey == null){ + if (contextKey == null) { contextKey = Context.key(key); } return contextKey; diff --git a/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java b/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java index b57cf1f..0e7322a 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/inst/ByteClassLoader.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2023 Intergral GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package com.intergral.deep.tests.inst; import java.io.IOException; @@ -7,47 +24,47 @@ public class ByteClassLoader extends ClassLoader { - private final Map bytes = new HashMap<>(); + private final Map bytes = new HashMap<>(); - public static byte[] loadBytes(final String name) throws IOException { - final byte[] bytes; - try (InputStream resourceAsStream = ByteClassLoader.class.getResourceAsStream("/" + name + ".class")) { - bytes = new byte[resourceAsStream.available()]; - resourceAsStream.read(bytes); - } - return bytes; + public static byte[] loadBytes(final String name) throws IOException { + final byte[] bytes; + try (InputStream resourceAsStream = ByteClassLoader.class.getResourceAsStream("/" + name + ".class")) { + bytes = new byte[resourceAsStream.available()]; + resourceAsStream.read(bytes); } + return bytes; + } - public static ByteClassLoader forFile(final String name) throws IOException { - final byte[] loadedBytes = loadBytes(name); - final ByteClassLoader byteClassLoader = new ByteClassLoader(); - byteClassLoader.setBytes(name, loadedBytes); - return byteClassLoader; - } + public static ByteClassLoader forFile(final String name) throws IOException { + final byte[] loadedBytes = loadBytes(name); + final ByteClassLoader byteClassLoader = new ByteClassLoader(); + byteClassLoader.setBytes(name, loadedBytes); + return byteClassLoader; + } - public void setBytes(final String name, final byte[] bytes) { - this.bytes.put(name, bytes); - } + public void setBytes(final String name, final byte[] bytes) { + this.bytes.put(name, bytes); + } - public byte[] getBytes(final String name) { - return this.bytes.get(name); - } + public byte[] getBytes(final String name) { + return this.bytes.get(name); + } - @Override - public Class loadClass(final String name) throws ClassNotFoundException { - final byte[] bytes = this.bytes.get(name); - if (bytes != null) { - return defineClass(name, bytes, 0, bytes.length); - } - return super.loadClass(name); + @Override + public Class loadClass(final String name) throws ClassNotFoundException { + final byte[] bytes = this.bytes.get(name); + if (bytes != null) { + return defineClass(name, bytes, 0, bytes.length); } + return super.loadClass(name); + } - @Override - protected Class findClass(final String name) throws ClassNotFoundException { - final byte[] bytes = this.bytes.get(name); - if (bytes != null) { - return defineClass(name, bytes, 0, bytes.length); - } - return super.findClass(name); + @Override + protected Class findClass(final String name) throws ClassNotFoundException { + final byte[] bytes = this.bytes.get(name); + if (bytes != null) { + return defineClass(name, bytes, 0, bytes.length); } + return super.findClass(name); + } } diff --git a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java index 9beed43..266d801 100644 --- a/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java +++ b/test-utils/src/main/java/com/intergral/deep/tests/snapshot/SnapshotUtils.java @@ -31,7 +31,7 @@ private SnapshotUtils() { } /** - * Scan a snapshot for a variable with the given name + * Scan a snapshot for a variable with the given name. * * @param name the variable name * @param snapshot the snapshot From 78291f231eb037ddd996fa97ad11a0ee4bc9e85a Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 1 Sep 2023 14:53:11 +0100 Subject: [PATCH 14/15] chore(lint): correct lint errors --- .../main/java/com/intergral/deep/Deep.java | 27 ++++++++++--------- .../deep/{DEEPAPI.java => DeepAPI.java} | 6 ++--- .../com/intergral/deep/DeepConfigBuilder.java | 10 ++++--- .../java/com/intergral/deep/DeepLoader.java | 9 ++++--- .../deep/ShippedToolsJarProvider.java | 2 +- .../com/intergral/deep/examples/Main.java | 14 +++++++--- .../com/intergral/deep/examples/Main.java | 19 ++++++++----- 7 files changed, 54 insertions(+), 33 deletions(-) rename deep/src/main/java/com/intergral/deep/{DEEPAPI.java => DeepAPI.java} (94%) diff --git a/deep/src/main/java/com/intergral/deep/Deep.java b/deep/src/main/java/com/intergral/deep/Deep.java index c07361f..5fb8a14 100644 --- a/deep/src/main/java/com/intergral/deep/Deep.java +++ b/deep/src/main/java/com/intergral/deep/Deep.java @@ -45,12 +45,21 @@ public class Deep { /** - * This is a shortcut for {@code Deep.config().start()} + * This is a shortcut for {@code Deep.config().start()}. */ public static void start() { Deep.config().start(); } + /** + * This allows deep to be started with the parsed config builder. + * + * @param builder the config to use + */ + public void start(final DeepConfigBuilder builder) { + builder.start(); + } + /** * This will create an instance of DEEP allowing access to the APIs from inside deep agent. * @@ -76,7 +85,7 @@ public static DeepConfigBuilder config() { } /** - * This allows deep to be started with the parsed config + * This allows deep to be started with the parsed config. * * @param config as a string */ @@ -84,14 +93,6 @@ void startWithConfig(final String config, final String jarPath) { getInstance().startDeep(config, jarPath); } - /** - * This allows deep to be started with the parsed config builder - * - * @param builder the config to use - */ - public void start(final DeepConfigBuilder builder) { - builder.start(); - } private void startDeep(final String config, final String jarPath) { try { @@ -112,7 +113,7 @@ private void loadAgent(final String config, final String jarPath) throws Throwab /** - * Get the process id for the current process + * Get the process id for the current process. * * @return the process id */ @@ -123,7 +124,7 @@ String getPid() { /** - * Load the loader for NerdVision + * Load the loader for NerdVision. * * @return the loader to use * @throws Throwable if we cannot load the loader @@ -218,8 +219,8 @@ public T api() { *

    * This uses T as the type {@link com.intergral.deep.agent.api.reflection.IReflection} is not loaded so this class cannot use it. * - * @return the {@link com.intergral.deep.agent.api.reflection.IReflection} service * @param this should be {@link com.intergral.deep.agent.api.reflection.IReflection} + * @return the {@link com.intergral.deep.agent.api.reflection.IReflection} service */ public T reflection() { loadAPI(); diff --git a/deep/src/main/java/com/intergral/deep/DEEPAPI.java b/deep/src/main/java/com/intergral/deep/DeepAPI.java similarity index 94% rename from deep/src/main/java/com/intergral/deep/DEEPAPI.java rename to deep/src/main/java/com/intergral/deep/DeepAPI.java index f827771..f79b730 100644 --- a/deep/src/main/java/com/intergral/deep/DEEPAPI.java +++ b/deep/src/main/java/com/intergral/deep/DeepAPI.java @@ -24,10 +24,10 @@ * This type provides helper methods to get the api and other exposed APIs from deep. This type MUST not be used until after the agent is * loaded or there will be Exceptions thrown. */ -public class DEEPAPI { +public class DeepAPI { /** - * Get the reflection API + * Get the reflection API. * * @return a {@link IReflection} instance for the java version we are running */ @@ -36,7 +36,7 @@ public static IReflection reflection() { } /** - * Get the Deep api + * Get the Deep api. * * @return a {@link IDeep} instance */ diff --git a/deep/src/main/java/com/intergral/deep/DeepConfigBuilder.java b/deep/src/main/java/com/intergral/deep/DeepConfigBuilder.java index 1abe153..4dd7ac6 100644 --- a/deep/src/main/java/com/intergral/deep/DeepConfigBuilder.java +++ b/deep/src/main/java/com/intergral/deep/DeepConfigBuilder.java @@ -20,13 +20,16 @@ import java.util.HashMap; import java.util.Map; +/** + * Builder to create deep config. + */ public class DeepConfigBuilder { private final Map config = new HashMap<>(); private String jarPath; /** - * Start Deep using this config + * Start Deep using this config. */ public void start() { Deep.getInstance().startWithConfig(this.build(), this.jarPath); @@ -96,7 +99,8 @@ public DeepConfigBuilder setValue(final String key, final double value) { } /** - * Converts this object into a string that can be used by the attachment process + * Converts this object into a string that can be used by the attachment process. + * * @return a string for this config */ private String build() { @@ -104,7 +108,7 @@ private String build() { } /** - * Convert the config to a string + * Convert the config to a string. * * @param config the config to parse * @return the config as a string diff --git a/deep/src/main/java/com/intergral/deep/DeepLoader.java b/deep/src/main/java/com/intergral/deep/DeepLoader.java index 9b07e4c..730bc86 100644 --- a/deep/src/main/java/com/intergral/deep/DeepLoader.java +++ b/deep/src/main/java/com/intergral/deep/DeepLoader.java @@ -24,6 +24,9 @@ import java.io.InputStream; import net.bytebuddy.agent.ByteBuddyAgent; +/** + * Custom loader to attach deep to the running process. + */ public class DeepLoader implements IDeepLoader { @Override @@ -54,7 +57,7 @@ private File getToolsJar() { /** - * Load the agent jar to a file + * Load the agent jar to a file. * * @return the {@link File} object for the agent */ @@ -75,7 +78,7 @@ private File getAgentJar(final String jarPath) { /** - * Log the agent file as a stream + * Log the agent file as a stream. * * @return the stream to use, or {@code null} */ @@ -85,7 +88,7 @@ private InputStream getAgentJarStream() { /** - * Extract a stream to a temp file and return the absolute file path + * Extract a stream to a temp file and return the absolute file path. * * @param inputStream the stream to extract * @return the absolute file path to the extracted library diff --git a/deep/src/main/java/com/intergral/deep/ShippedToolsJarProvider.java b/deep/src/main/java/com/intergral/deep/ShippedToolsJarProvider.java index 8fa8554..be4fcd9 100644 --- a/deep/src/main/java/com/intergral/deep/ShippedToolsJarProvider.java +++ b/deep/src/main/java/com/intergral/deep/ShippedToolsJarProvider.java @@ -33,7 +33,7 @@ public class ShippedToolsJarProvider implements ByteBuddyAgent.AttachmentProvide /** - * Create a new provider + * Create a new provider. * * @param tools the file object for the tools jar to use */ diff --git a/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java b/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java index 8c933bb..e024e86 100644 --- a/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java +++ b/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java @@ -18,7 +18,7 @@ package com.intergral.deep.examples; -import com.intergral.deep.DEEPAPI; +import com.intergral.deep.DeepAPI; import com.intergral.deep.agent.api.resource.Resource; import java.util.Collections; @@ -33,23 +33,29 @@ */ public class Main { + /** + * Main entry for example. + * + * @param args the startup arguments + * @throws Throwable if we error + */ public static void main(String[] args) throws Throwable { // Use the API to show the version of deep that is running // If the Deep agent is not loaded then you will get an exception on this line // java.lang.IllegalStateException: Must start Deep first! - System.out.println(DEEPAPI.api().getVersion()); + System.out.println(DeepAPI.api().getVersion()); // Use the API to register a plugin // This plugin will attach the attribute 'example' to the created snapshot // you should also see the log line 'custom plugin' when you run this example - DEEPAPI.api().registerPlugin((settings, snapshot) -> { + DeepAPI.api().registerPlugin((settings, snapshot) -> { System.out.println("custom plugin"); return Resource.create(Collections.singletonMap("example", "agent_load")); }); // USe the API to create a tracepoint that will fire forever - DEEPAPI.api() + DeepAPI.api() .registerTracepoint("com/intergral/deep/examples/SimpleTest", 46, Collections.singletonMap("fire_count", "-1"), Collections.emptyList()); diff --git a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java index bc313b1..41ed7bd 100644 --- a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java +++ b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java @@ -18,8 +18,8 @@ package com.intergral.deep.examples; -import com.intergral.deep.DEEPAPI; import com.intergral.deep.Deep; +import com.intergral.deep.DeepAPI; import com.intergral.deep.agent.api.IDeep; import com.intergral.deep.agent.api.reflection.IReflection; import com.intergral.deep.agent.api.resource.Resource; @@ -39,6 +39,12 @@ */ public class Main { + /** + * Main entry for example. + * + * @param args the startup arguments + * @throws Throwable if we error + */ public static void main(String[] args) throws Throwable { // this is only needed in this example as we are using a local built module // if using the dependency from maven you do not need to set the path @@ -61,20 +67,21 @@ public static void main(String[] args) throws Throwable { System.out.println(instance.api().getVersion()); System.out.println(instance.reflection()); - System.out.println(DEEPAPI.api().getVersion()); - System.out.println(DEEPAPI.reflection()); + System.out.println(DeepAPI.api().getVersion()); + System.out.println(DeepAPI.reflection()); // Use the API to register a plugin // This plugin will attach the attribute 'example' to the created snapshot // you should also see the log line 'custom plugin' when you run this example - DEEPAPI.api().registerPlugin((settings, snapshot) -> { + DeepAPI.api().registerPlugin((settings, snapshot) -> { System.out.println("custom plugin"); return Resource.create(Collections.singletonMap("example", "dynamic_load")); }); // USe the API to create a tracepoint that will fire forever - DEEPAPI.api() - .registerTracepoint("com/intergral/deep/examples/SimpleTest", 46, Collections.singletonMap("fire_count", "-1"), Collections.emptyList()); + DeepAPI.api() + .registerTracepoint("com/intergral/deep/examples/SimpleTest", 46, + Collections.singletonMap("fire_count", "-1"), Collections.emptyList()); final SimpleTest ts = new SimpleTest("This is a test", 2); for (; ; ) { From bf7c940124e0d80de5a83b1a46048b858790389c Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Fri, 1 Sep 2023 15:05:00 +0100 Subject: [PATCH 15/15] chore(lint): correct test errors --- .../deep/agent/grpc/GrpcServiceTest.java | 17 +++++++++++++---- .../TracepointInstrumentationServiceTest.java | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java index 03b2949..43dc10f 100644 --- a/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/grpc/GrpcServiceTest.java @@ -36,6 +36,8 @@ import io.grpc.ServerBuilder; import io.grpc.ServerInterceptors; import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.net.ServerSocket; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -56,6 +58,7 @@ class GrpcServiceTest { private final AtomicReference snapshotRequest = new AtomicReference<>(); private final AtomicReference header = new AtomicReference<>(); + private int port; @BeforeAll static void beforeAll() { @@ -88,14 +91,20 @@ void setUp() throws Exception { responseObserver.onCompleted(); }); - server = ServerBuilder.forPort(9999).addService(ServerInterceptors.intercept(testPollService.bindService(), testInterceptor)) + // find a free port + try (ServerSocket socket = new ServerSocket(0)) { + port = socket.getLocalPort(); + } + + server = ServerBuilder.forPort(port) + .addService(ServerInterceptors.intercept(testPollService.bindService(), testInterceptor)) .addService(ServerInterceptors.intercept(testSnapshotService.bindService(), testInterceptor)).build(); server.start(); } @AfterEach - void tearDown() { + void tearDown() throws IOException { this.server.shutdownNow(); } @@ -103,7 +112,7 @@ void tearDown() { void serverCanConnect_poll() throws InterruptedException { final HashMap map = new HashMap<>(); - map.put(ISettings.KEY_SERVICE_URL, "localhost:9999"); + map.put(ISettings.KEY_SERVICE_URL, "localhost:" + port); map.put(ISettings.KEY_SERVICE_SECURE, "false"); map.put(ISettings.KEY_AUTH_PROVIDER, MockAuthProvider.class.getName()); @@ -125,7 +134,7 @@ void serverCanConnect_poll() throws InterruptedException { void serverCanConnect_snapshot() throws InterruptedException { final HashMap map = new HashMap<>(); - map.put(ISettings.KEY_SERVICE_URL, "localhost:9999"); + map.put(ISettings.KEY_SERVICE_URL, "localhost:" + port); map.put(ISettings.KEY_SERVICE_SECURE, "false"); map.put(ISettings.KEY_AUTH_PROVIDER, MockAuthProvider.class.getName()); diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java index 658b9ff..af42459 100644 --- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java +++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/inst/TracepointInstrumentationServiceTest.java @@ -104,7 +104,7 @@ void matchedTracepointShouldReTranform() throws UnmodifiableClassException { Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{Person.class}); Mockito.when(instrumentation.isModifiableClass(Person.class)).thenReturn(true); tracepointInstrumentationService.processBreakpoints( - Collections.singletonList(new MockTracepointConfig("/com/intergral/deep/Person.java"))); + Collections.singletonList(new MockTracepointConfig("/com/intergral/deep/test/target/Person.java"))); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Class.class); @@ -119,7 +119,7 @@ void removingTPShouldReTransform() throws UnmodifiableClassException { Mockito.when(instrumentation.getAllLoadedClasses()).thenReturn(new Class[]{Person.class}); Mockito.when(instrumentation.isModifiableClass(Person.class)).thenReturn(true); tracepointInstrumentationService.processBreakpoints( - Collections.singletonList(new MockTracepointConfig("/com/intergral/deep/Person.java"))); + Collections.singletonList(new MockTracepointConfig("/com/intergral/deep/test/target/Person.java"))); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Class.class);