Skip to content

Commit

Permalink
Inject coverage store field into Thread class
Browse files Browse the repository at this point in the history
  • Loading branch information
nikita-tkachenko-datadog committed Jul 5, 2024
1 parent 0584623 commit 50a5b2e
Show file tree
Hide file tree
Showing 28 changed files with 507 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import datadog.trace.civisibility.ipc.Serializer;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;

public class JvmInfo {
Expand All @@ -10,16 +12,40 @@ public class JvmInfo {
new JvmInfo(
System.getProperty("java.runtime.name"),
System.getProperty("java.version"),
System.getProperty("java.vendor"));
System.getProperty("java.class.version"),
System.getProperty("java.vendor"),
System.getProperty("java.home"));

private static final int JAVA_8_CLASS_VERSION = 52;

private final String name;
private final String version;
private final int majorClassVersion;
private final String vendor;
private final Path home;

public JvmInfo(String name, String version, String classVersion, String vendor, String home) {
this.name = name;
this.version = version;
this.vendor = vendor;
this.home = Paths.get(home);

int majorClassVersion;
try {
String[] classVersionTokens = classVersion.split("\\.");
majorClassVersion = Integer.parseInt(classVersionTokens[0]);
} catch (Exception e) {
majorClassVersion = -1;
}
this.majorClassVersion = majorClassVersion;
}

public JvmInfo(String name, String version, String vendor) {
private JvmInfo(String name, String version, int majorClassVersion, String vendor, Path home) {
this.name = name;
this.version = version;
this.vendor = vendor;
this.home = home;
this.majorClassVersion = majorClassVersion;
}

public String getName() {
Expand All @@ -30,10 +56,22 @@ public String getVersion() {
return version;
}

public int getMajorClassVersion() {
return majorClassVersion;
}

public String getVendor() {
return vendor;
}

public Path getHome() {
return home;
}

public boolean isModular() {
return majorClassVersion > JAVA_8_CLASS_VERSION;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand All @@ -45,12 +83,14 @@ public boolean equals(Object o) {
JvmInfo jvmInfo = (JvmInfo) o;
return Objects.equals(name, jvmInfo.name)
&& Objects.equals(version, jvmInfo.version)
&& Objects.equals(vendor, jvmInfo.vendor);
&& Objects.equals(majorClassVersion, jvmInfo.majorClassVersion)
&& Objects.equals(vendor, jvmInfo.vendor)
&& Objects.equals(home, jvmInfo.home);
}

@Override
public int hashCode() {
return Objects.hash(name, version, vendor);
return Objects.hash(name, version, majorClassVersion, vendor, home);
}

@Override
Expand All @@ -61,21 +101,33 @@ public String toString() {
+ '\''
+ ", version='"
+ version
+ ", majorClassVersion='"
+ majorClassVersion
+ '\''
+ ", vendor='"
+ vendor
+ '\''
+ ", home='"
+ home
+ '\''
+ '}';
}

public static void serialize(Serializer serializer, JvmInfo jvmInfo) {
serializer.write(jvmInfo.name);
serializer.write(jvmInfo.version);
serializer.write(jvmInfo.majorClassVersion);
serializer.write(jvmInfo.vendor);
serializer.write(String.valueOf(jvmInfo.home));
}

public static JvmInfo deserialize(ByteBuffer buf) {
String name = Serializer.readString(buf);
String version = Serializer.readString(buf);
int majorClassVersion = Serializer.readInt(buf);
String vendor = Serializer.readString(buf);
String home = Serializer.readString(buf);
return new JvmInfo(
Serializer.readString(buf), Serializer.readString(buf), Serializer.readString(buf));
name, version, majorClassVersion, vendor, home != null ? Paths.get(home) : null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ private static final class JvmVersionOutputParser
public JvmInfo parse(InputStream inputStream) throws IOException {
String name = null;
String version = null;
String classVersion = null;
String vendor = null;
String home = null;

BufferedReader bis =
new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
Expand All @@ -74,11 +76,15 @@ public JvmInfo parse(InputStream inputStream) throws IOException {
name = getPropertyValue(line);
} else if (line.contains("java.version ")) {
version = getPropertyValue(line);
} else if (line.contains("java.class.version ")) {
classVersion = getPropertyValue(line);
} else if (line.contains("java.vendor ")) {
vendor = getPropertyValue(line);
} else if (line.contains("java.home ")) {
home = getPropertyValue(line);
}
}
return new JvmInfo(name, version, vendor);
return new JvmInfo(name, version, classVersion, vendor, home);
}

private String getPropertyValue(String line) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
import datadog.trace.api.config.CiVisibilityConfig;
import datadog.trace.api.git.GitInfo;
import datadog.trace.api.git.GitInfoProvider;
import datadog.trace.civisibility.coverage.instrumentation.store.JvmPatcher;
import datadog.trace.civisibility.git.tree.GitDataUploader;
import datadog.trace.civisibility.source.index.RepoIndex;
import datadog.trace.civisibility.source.index.RepoIndexProvider;
import datadog.trace.civisibility.utils.FileUtils;
import datadog.trace.util.Strings;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -65,14 +68,6 @@ public ModuleExecutionSettings create(JvmInfo jvmInfo, @Nullable String moduleNa
boolean testSkippingEnabled = isTestSkippingEnabled(ciVisibilitySettings);
boolean flakyTestRetriesEnabled = isFlakyTestRetriesEnabled(ciVisibilitySettings);
boolean earlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled(ciVisibilitySettings);
Map<String, String> systemProperties =
getPropertiesPropagatedToChildProcess(
itrEnabled,
codeCoverageEnabled,
testSkippingEnabled,
flakyTestRetriesEnabled,
earlyFlakeDetectionEnabled);

LOGGER.info(
"CI Visibility settings ({}, {}):\n"
+ "Intelligent Test Runner - {},\n"
Expand Down Expand Up @@ -109,6 +104,19 @@ public ModuleExecutionSettings create(JvmInfo jvmInfo, @Nullable String moduleNa
Map<String, Collection<TestIdentifier>> knownTestsByModuleName =
earlyFlakeDetectionEnabled ? getKnownTests(tracerEnvironment) : null;

Map<String, String> systemProperties =
getPropertiesPropagatedToChildProcess(
itrEnabled,
codeCoverageEnabled,
testSkippingEnabled,
flakyTestRetriesEnabled,
earlyFlakeDetectionEnabled);

List<String> jvmOptions = new ArrayList<>();
if (codeCoverageEnabled && config.isCiVisibilityJvmPatchingEnabled()) {
jvmOptions.addAll(patchJvm(jvmInfo));
}

List<String> coverageEnabledPackages = getCoverageEnabledPackages(codeCoverageEnabled);
return new ModuleExecutionSettings(
itrEnabled,
Expand All @@ -123,13 +131,38 @@ public ModuleExecutionSettings create(JvmInfo jvmInfo, @Nullable String moduleNa
? ciVisibilitySettings.getEarlyFlakeDetectionSettings()
: EarlyFlakeDetectionSettings.DEFAULT,
systemProperties,
jvmOptions,
itrCorrelationId,
skippableTestsByModuleName,
flakyTests,
knownTestsByModuleName,
coverageEnabledPackages);
}

/**
* @return The list of JVM options that are needed to prepend the patch to the bootstrap classpath
*/
private List<String> patchJvm(JvmInfo jvmInfo) {
try {
JvmPatcher patcher = new JvmPatcher(jvmInfo);
Path patchPath = patcher.createPatch();

Thread cleanupPatch =
new Thread(() -> FileUtils.deleteSafely(patchPath), "dd-ci-vis-patched-classes-remove");
Runtime.getRuntime().addShutdownHook(cleanupPatch);

if (jvmInfo.isModular()) {
return Arrays.asList("--patch-module", String.format("java.base=%s", patchPath));
} else {
return Collections.singletonList(String.format("-Xbootclasspath/p:%s", patchPath));
}

} catch (Exception e) {
LOGGER.debug("Failed to patch {}", jvmInfo, e);
return Collections.emptyList();
}
}

private TracerEnvironment buildTracerEnvironment(
String repositoryRoot, JvmInfo jvmInfo, @Nullable String moduleName) {
GitInfo gitInfo = GitInfoProvider.INSTANCE.getGitInfo(repositoryRoot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static ByteBuffer serialize(ModuleExecutionSettings settings) {
EarlyFlakeDetectionSettingsSerializer.serialize(s, settings.getEarlyFlakeDetectionSettings());

s.write(settings.getSystemProperties());
s.write(settings.getJvmOptions());
s.write(settings.getItrCorrelationId());
s.write(
settings.getSkippableTestsByModule(),
Expand All @@ -56,6 +57,8 @@ public static ModuleExecutionSettings deserialize(ByteBuffer buffer) {
EarlyFlakeDetectionSettingsSerializer.deserialize(buffer);

Map<String, String> systemProperties = Serializer.readStringMap(buffer);
List<String> jvmOptions = Serializer.readStringList(buffer);

String itrCorrelationId = Serializer.readString(buffer);
Map<String, Collection<TestIdentifier>> skippableTestsByModule =
Serializer.readMap(
Expand All @@ -78,6 +81,7 @@ public static ModuleExecutionSettings deserialize(ByteBuffer buffer) {
flakyTestRetriesEnabled,
earlyFlakeDetectionSettings,
systemProperties,
jvmOptions,
itrCorrelationId,
skippableTestsByModule,
flakyTests,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package datadog.trace.civisibility.coverage.instrumentation.store;

import datadog.communication.util.IOThrowingFunction;
import datadog.trace.civisibility.config.JvmInfo;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class CoreJvmClassReader {

private static final String JAVA_BASE_MODULE_RELATIVE_PATH = "jmods/java.base.jmod";

// java.home contains the "/jre" segment for Java 8 distributions
private static final String RT_JAR_RELATIVE_PATH = "lib/rt.jar";

/**
* Performs an action on the bytecode of a core JVM class
*
* @param jvm The JVM distribution
* @param className The name of the class (as returned by {@link Class#getName}, e.g. {@code
* java.lang.Thread})
* @param action The action to be performed on the class bytecode stream
* @return The return value of the action
* @param <T> The type of the return value
* @throws IOException If the stream could not be retrieved
*/
public <T> T withClassStream(
JvmInfo jvm, String className, IOThrowingFunction<InputStream, T> action) throws IOException {
if (jvm.isModular()) {
return withClassStreamModularJdk(jvm.getHome(), className, action);
} else {
return withClassStreamPreModularJdk(jvm.getHome(), className, action);
}
}

private <T> T withClassStreamModularJdk(
Path jvmHome, String className, IOThrowingFunction<InputStream, T> action)
throws IOException {
Path javaBaseModule = jvmHome.resolve(JAVA_BASE_MODULE_RELATIVE_PATH);
return withZipEntry(
javaBaseModule, "classes/" + className.replace('.', '/') + ".class", action);
}

private <T> T withClassStreamPreModularJdk(
Path jvmHome, String className, IOThrowingFunction<InputStream, T> action)
throws IOException {
Path rtJar = jvmHome.resolve(RT_JAR_RELATIVE_PATH);
return withZipEntry(rtJar, className.replace('.', '/') + ".class", action);
}

private static <T> T withZipEntry(
Path zipFilePath, String entryName, IOThrowingFunction<InputStream, T> action)
throws IOException {
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) {
ZipEntry entry = zipFile.getEntry(entryName);
if (entry == null) {
throw new IOException("Entry " + entryName + " not found in zip file " + zipFilePath);
}
try (InputStream entryStream = zipFile.getInputStream(entry)) {
return action.apply(entryStream);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package datadog.trace.civisibility.coverage.instrumentation.store;

import static datadog.trace.api.civisibility.coverage.CoverageBridge.COVERAGE_STORE_FIELD_NAME;

import datadog.trace.api.civisibility.coverage.CoverageBridge;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;

/**
* A {@link ClassVisitor} that injects an instance field named {@link
* CoverageBridge#COVERAGE_STORE_FIELD_NAME} of type {@link Object} into the visited class
*/
public class CoverageStoreFieldInjector extends ClassVisitor {

public CoverageStoreFieldInjector(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}

@Override
public void visitEnd() {
cv.visitField(Opcodes.ACC_PUBLIC, COVERAGE_STORE_FIELD_NAME, "Ljava/lang/Object;", null, null)
.visitEnd();
super.visitEnd();
}
}
Loading

0 comments on commit 50a5b2e

Please sign in to comment.