Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Validator #8151

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import datadog.trace.api.ProductActivation;
import datadog.trace.bootstrap.ExceptionLogger;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.asm.Advice.ExceptionHandler;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.jar.asm.Label;
Expand All @@ -20,8 +20,8 @@ public class ExceptionHandlers {
// Bootstrap ExceptionHandler.class will always be resolvable, so we'll use it in the log name
private static final String HANDLER_NAME = ExceptionLogger.class.getName().replace('.', '/');

private static final ExceptionHandler EXCEPTION_STACK_HANDLER =
new ExceptionHandler.Simple(
private static final Advice.ExceptionHandler EXCEPTION_STACK_HANDLER =
new Advice.ExceptionHandler.Simple(
new StackManipulation() {
// Pops one Throwable off the stack. Maxes the stack to at least 3.
private final Size size = new StackManipulation.Size(-1, 3);
Expand Down Expand Up @@ -131,7 +131,7 @@ public Size apply(final MethodVisitor mv, final Implementation.Context context)
}
});

public static ExceptionHandler defaultExceptionHandler() {
public static Advice.ExceptionHandler defaultExceptionHandler() {
return EXCEPTION_STACK_HANDLER;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.aws.v1.lambda.LambdaHandlerDecorator.INVOCATION_SPAN_NAME;
import static net.bytebuddy.asm.Advice.Enter;
import static net.bytebuddy.asm.Advice.OnMethodEnter;
import static net.bytebuddy.asm.Advice.OnMethodExit;
import static net.bytebuddy.asm.Advice.Origin;
import static net.bytebuddy.asm.Advice.This;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
Expand Down Expand Up @@ -82,11 +77,11 @@ public void methodAdvice(MethodTransformer transformer) {
}

public static class ExtensionCommunicationAdvice {
@OnMethodEnter
@Advice.OnMethodEnter
static AgentScope enter(
@This final Object that,
@Advice.This final Object that,
@Advice.Argument(0) final Object event,
@Origin("#m") final String methodName) {
@Advice.Origin("#m") final String methodName) {

if (CallDepthThreadLocalMap.incrementCallDepth(RequestHandler.class) > 0) {
return null;
Expand All @@ -103,10 +98,10 @@ static AgentScope enter(
return scope;
}

@OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
static void exit(
@Origin String method,
@Enter final AgentScope scope,
@Advice.Origin String method,
@Advice.Enter final AgentScope scope,
@Advice.Return(typing = DYNAMIC) final Object result,
@Advice.Thrown final Throwable throwable) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package datadog.trace.instrumentation.codeorigin;

import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule.Tracing;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.agent.tooling.bytebuddy.matcher.HierarchyMatchers;
import datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers;
import datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.OneOf;
Expand All @@ -12,7 +12,7 @@
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

public abstract class CodeOriginInstrumentation extends Tracing
public abstract class CodeOriginInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice {

private final OneOf<NamedElement> matcher;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public List<Instrumenter> typeInstrumentations() {
}

// Not Using AutoService to hook up this instrumentation
public static class TracerClassInstrumentation implements ForTypeHierarchy, HasMethodAdvice {
public static class TracerClassInstrumentation implements Instrumenter.ForTypeHierarchy, Instrumenter.HasMethodAdvice {
private final String className;
private final Set<String> methodNames;

Expand Down
96 changes: 96 additions & 0 deletions validate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import javalang
import os
import re


# Helper function to validate kebab-case
def is_kebab_case(name):
return re.match(r'^[a-z0-9]+(-[a-z0-9.]+)*$', name) is not None

def find_java_files(directory):
java_files = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(".java"):
java_files.append(os.path.join(root, file))
return java_files

def parse_java_file(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
return javalang.parse.parse(file.read())

def validate_inheritance(tree, file_name):
issues = []
for path, node in tree:
if isinstance(node, javalang.tree.ClassDeclaration):
# Skip static inner classes
if any(modifier for modifier in node.modifiers if modifier == "static") and '.' in node.name:
continue

if node.name.endswith("Decorator"):
if not node.extends or "Decorator" not in node.extends.name:
issues.append(f"{file_name}: Class '{node.name}' should extend a class with 'Decorator' in its name.")
elif node.extends and "Decorator" in node.extends.name:
if not node.name.endswith("Decorator"):
issues.append(f"{file_name}: Class '{node.name}' extends a 'Decorator' class but does not end with 'Decorator'.")

if node.name.endswith("Instrumentation") or node.name.endswith("Module"):
# should extend something
if not node.extends and not node.implements:
issues.append(f"{file_name}: Class '{node.name}' should extend or implement a class with 'Instrument' in its name.")
# if it extends something, it should extend an instrument
elif not ((node.extends and "Instrument" in node.extends.name) or (node.implements and any("Instrument" in impl.name for impl in node.implements))):
issues.append(f"{file_name}: Class '{node.name}' should extend a class with 'Instrument' in its name.")
elif node.extends and "Instrument" in node.extends.name:
if not node.name.endswith("Instrumentation") or not node.name.endswith("Module"):
issues.append(f"{file_name}: Class '{node.name}' extends a 'Instrument' class but does not end with 'Instrumentation' or 'Module'.")
elif node.implements and any("Instrument" in impl for impl in node.implements):
if not node.name.endswith("Instrumentation") or not node.name.endswith("Module"):
issues.append(f"{file_name}: Class '{node.name}' implements a 'Instrument' class but does not end with 'Instrumentation' or 'Module'.")

if node.name.endswith("Advice"):
# Check for methods with an @Advice annotation
if not any(
isinstance(member, javalang.tree.MethodDeclaration) and
any(anno.name.startswith("Advice") for anno in member.annotations)
for member in node.body
):
issues.append(f"{file_name}: Class '{node.name}' does not have a method tagged with an @Advice annotation.")
return issues

def validate_project_structure(base_directory):
issues = []

# Check kebab-case for subdirectories
instrumentation_path = os.path.join(base_directory, "dd-java-agent", "instrumentation")
if not os.path.exists(instrumentation_path):
issues.append(f"Path '{instrumentation_path}' does not exist.")
return issues

for subdir in os.listdir(instrumentation_path):
if os.path.isdir(os.path.join(instrumentation_path, subdir)):
if not is_kebab_case(subdir):
issues.append(f"Subdirectory '{subdir}' is not in kebab-case.")

# Validate Java files in subdirectories
for subdir in os.listdir(instrumentation_path):
subdir_path = os.path.join(instrumentation_path, subdir)
if os.path.isdir(subdir_path):
java_files = find_java_files(os.path.join(subdir_path, "src", "main", "java"))
for java_file in java_files:
try:
tree = parse_java_file(java_file)
issues.extend(validate_inheritance(tree, java_file))
except (javalang.parser.JavaSyntaxError, UnicodeDecodeError):
issues.append(f"Could not parse Java file '{java_file}'.")

return issues

if __name__ == "__main__":
base_directory = "." # Change this to the root directory of your project
problems = validate_project_structure(base_directory)

if problems:
print("\n".join(problems))
else:
print("All checks passed!")
Loading