Skip to content

Commit e5de02b

Browse files
Merge branch 'master' into alexeyk/test-base-windows
2 parents c373e7b + d149028 commit e5de02b

15 files changed

Lines changed: 203 additions & 94 deletions

File tree

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ config-inversion-linter:
427427
needs: []
428428
script:
429429
- ./gradlew --version
430-
- ./gradlew logEnvVarUsages checkEnvironmentVariablesUsage checkConfigStrings
430+
- ./gradlew checkConfigurations
431431

432432
test_published_artifacts:
433433
extends: .gradle_build
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package datadog.gradle.plugin.config
2+
3+
import com.github.javaparser.StaticJavaParser
4+
import com.github.javaparser.ast.Modifier
5+
import com.github.javaparser.ast.body.FieldDeclaration
6+
import com.github.javaparser.ast.body.VariableDeclarator
7+
import com.github.javaparser.ast.expr.Expression
8+
import com.github.javaparser.ast.expr.MethodCallExpr
9+
import com.github.javaparser.ast.expr.NameExpr
10+
import com.github.javaparser.ast.expr.StringLiteralExpr
11+
import org.gradle.api.Action
12+
import org.gradle.api.GradleException
13+
import org.gradle.api.Task
14+
import org.gradle.api.provider.Provider
15+
import org.gradle.api.tasks.SourceSetOutput
16+
import java.io.File
17+
18+
/** Validates that all config definitions in the config directory are documented in supported-configurations.json. */
19+
internal class RegularConfigCheckAction(
20+
private val mainSourceSetOutput: Provider<Provider<SourceSetOutput>>,
21+
private val generatedClassName: Provider<String>,
22+
private val extension: SupportedTracerConfigurations
23+
) : Action<Task> {
24+
override fun execute(task: Task) {
25+
val repoRoot = task.project.rootProject.projectDir.toPath()
26+
val configDir = repoRoot.resolve("dd-trace-api/src/main/java/datadog/trace/api/config").toFile()
27+
28+
if (!configDir.exists()) {
29+
throw GradleException("Config directory not found: ${configDir.absolutePath}")
30+
}
31+
32+
val configFields = loadConfigFields(mainSourceSetOutput.get().get(), generatedClassName.get())
33+
val supported = configFields.supported
34+
val aliasMapping = configFields.aliasMapping
35+
36+
val violations = buildList {
37+
configDir.listFiles()?.forEach { file ->
38+
val fileName = file.name
39+
extractStringConstants(file).forEach eachConstant@{ (fieldName, entry) ->
40+
if (fieldName.endsWith("_DEFAULT")) return@eachConstant
41+
val normalized = normalize(entry.value)
42+
if (normalized !in supported && normalized !in aliasMapping) {
43+
add("$fileName:${entry.line} -> Config '${entry.value}' normalizes to '$normalized' " +
44+
"which is missing from '${extension.jsonFile.get()}'")
45+
}
46+
}
47+
}
48+
}
49+
50+
if (violations.isNotEmpty()) {
51+
task.logger.error("\nFound config definitions not in '${extension.jsonFile.get()}':")
52+
violations.forEach { task.logger.lifecycle(it) }
53+
throw GradleException("Undocumented Environment Variables found. Please add the above Environment Variables to '${extension.jsonFile.get()}'.")
54+
} else {
55+
task.logger.info("All config strings are present in '${extension.jsonFile.get()}'.")
56+
}
57+
}
58+
}
59+
60+
/**
61+
* Validates that every `.ddprof.` config key used as a primary key in `DatadogProfilerConfig`'s
62+
* static helpers also has its async-translated form (`profiling.ddprof.*` → `profiling.async.*`)
63+
* documented in `supported-configurations.json`.
64+
*/
65+
internal class ProfilingConfigCheckAction(
66+
private val mainSourceSetOutput: Provider<Provider<SourceSetOutput>>,
67+
private val generatedClassName: Provider<String>,
68+
private val extension: SupportedTracerConfigurations
69+
) : Action<Task> {
70+
override fun execute(task: Task) {
71+
val repoRoot = task.project.rootProject.projectDir.toPath()
72+
73+
val constantMap = extractStringConstants(
74+
repoRoot.resolve("dd-trace-api/src/main/java/datadog/trace/api/config/ProfilingConfig.java").toFile()
75+
)
76+
77+
val configFields = loadConfigFields(mainSourceSetOutput.get().get(), generatedClassName.get())
78+
val supported = configFields.supported
79+
val aliasMapping = configFields.aliasMapping
80+
81+
val ddprofConfigFile = repoRoot.resolve(
82+
"dd-java-agent/agent-profiling/profiling-ddprof/src/main/java/com/datadog/profiling/ddprof/DatadogProfilerConfig.java"
83+
).toFile()
84+
val cu = StaticJavaParser.parse(ddprofConfigFile)
85+
86+
val helperMethodNames = setOf("getBoolean", "getInteger", "getLong", "getString")
87+
val violations = mutableListOf<String>()
88+
89+
cu.findAll(MethodCallExpr::class.java).forEach { call ->
90+
if (call.scope.isPresent) return@forEach
91+
if (call.nameAsString !in helperMethodNames) return@forEach
92+
val args = call.arguments
93+
if (args.size < 2 || args[0] !is NameExpr || (args[0] as NameExpr).nameAsString != "configProvider") return@forEach
94+
95+
val primaryKeyEntry = resolveConstant(args[1], constantMap) ?: return@forEach
96+
checkDocumented(primaryKeyEntry, supported, aliasMapping, call, violations, extension)
97+
}
98+
99+
if (violations.isNotEmpty()) {
100+
violations.forEach { task.logger.error(it) }
101+
throw GradleException("Undocumented configs found in DatadogProfilerConfig. Please add the above to '${extension.jsonFile.get()}'.")
102+
} else {
103+
task.logger.info("All DatadogProfilerConfig configs are documented.")
104+
}
105+
}
106+
}
107+
108+
internal data class ConstantEntry(val value: String, val line: Int)
109+
110+
internal fun extractStringConstants(file: File): Map<String, ConstantEntry> {
111+
val map = mutableMapOf<String, ConstantEntry>()
112+
StaticJavaParser.parse(file).findAll(VariableDeclarator::class.java).forEach { varDecl ->
113+
val field = varDecl.parentNode.map { it as? FieldDeclaration }.orElse(null) ?: return@forEach
114+
if (field.hasModifiers(Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC, Modifier.Keyword.FINAL)
115+
&& varDecl.typeAsString == "String") {
116+
val init = varDecl.initializer.orElse(null) as? StringLiteralExpr ?: return@forEach
117+
val line = varDecl.range.map { it.begin.line }.orElse(-1)
118+
map[varDecl.nameAsString] = ConstantEntry(init.value, line)
119+
}
120+
}
121+
return map
122+
}
123+
124+
internal fun resolveConstant(expr: Expression?, constantMap: Map<String, ConstantEntry>): ConstantEntry? = when (expr) {
125+
is StringLiteralExpr -> ConstantEntry(expr.value, -1)
126+
is NameExpr -> constantMap[expr.nameAsString]
127+
else -> null
128+
}
129+
130+
// Only check the async-translated form produced by DatadogProfilerConfig.normalizeKey.
131+
internal fun checkDocumented(
132+
entry: ConstantEntry,
133+
supported: Set<String>,
134+
aliasMapping: Map<String, String>,
135+
call: MethodCallExpr,
136+
violations: MutableList<String>,
137+
extension: SupportedTracerConfigurations
138+
) {
139+
if (!entry.value.contains(".ddprof.")) return
140+
val asyncNormalized = normalize(entry.value.replace(".ddprof.", ".async."))
141+
if (asyncNormalized !in supported && asyncNormalized !in aliasMapping) {
142+
val callLine = call.range.map { it.begin.line }.orElse(-1)
143+
violations.add("ProfilingConfig.java:${entry.line} (DatadogProfilerConfig.java:$callLine) -> '${entry.value}' (async form) → '$asyncNormalized' is missing from '${extension.jsonFile.get()}'")
144+
}
145+
}

buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt

Lines changed: 25 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
package datadog.gradle.plugin.config
22

3-
import com.github.javaparser.ParserConfiguration
4-
import com.github.javaparser.StaticJavaParser
5-
import com.github.javaparser.ast.CompilationUnit
63
import com.github.javaparser.ast.Modifier
7-
import com.github.javaparser.ast.body.FieldDeclaration
8-
import com.github.javaparser.ast.body.VariableDeclarator
9-
import com.github.javaparser.ast.expr.StringLiteralExpr
104
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
115
import org.gradle.api.GradleException
126
import org.gradle.api.Plugin
137
import org.gradle.api.Project
8+
import org.gradle.api.Task
9+
import org.gradle.api.tasks.TaskProvider
1410
import org.gradle.api.tasks.SourceSet
1511
import org.gradle.api.tasks.SourceSetContainer
1612
import org.gradle.kotlin.dsl.getByType
@@ -20,23 +16,29 @@ import java.nio.file.Path
2016
class ConfigInversionLinter : Plugin<Project> {
2117
override fun apply(target: Project) {
2218
val extension = target.extensions.create("supportedTracerConfigurations", SupportedTracerConfigurations::class.java)
23-
registerLogEnvVarUsages(target, extension)
24-
registerCheckEnvironmentVariablesUsage(target)
25-
registerCheckConfigStringsTask(target, extension)
19+
val logEnvVarUsages = registerLogEnvVarUsages(target, extension)
20+
val checkEnvVarUsage = registerCheckEnvironmentVariablesUsage(target)
21+
val checkConfigStrings = registerCheckConfigStringsTask(target, extension)
22+
23+
target.tasks.register("checkConfigurations") {
24+
group = "verification"
25+
description = "Runs all config inversion validation checks"
26+
dependsOn(logEnvVarUsages, checkEnvVarUsage, checkConfigStrings)
27+
}
2628
}
2729
}
2830

2931
// Data class for fields from generated class
30-
private data class LoadedConfigFields(
32+
internal data class LoadedConfigFields(
3133
val supported: Set<String>,
3234
val aliasMapping: Map<String, String> = emptyMap()
3335
)
3436

3537
// Cache for fields from generated class
36-
private var cachedConfigFields: LoadedConfigFields? = null
38+
internal var cachedConfigFields: LoadedConfigFields? = null
3739

3840
// Helper function to load fields from the generated class
39-
private fun loadConfigFields(
41+
internal fun loadConfigFields(
4042
mainSourceSetOutput: org.gradle.api.file.FileCollection,
4143
generatedClassName: String
4244
): LoadedConfigFields {
@@ -61,12 +63,12 @@ private fun loadConfigFields(
6163
}
6264

6365
/** Registers `logEnvVarUsages` (scan for DD_/OTEL_ tokens and fail if unsupported). */
64-
private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerConfigurations) {
66+
private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerConfigurations): TaskProvider<Task> {
6567
val ownerPath = extension.configOwnerPath
6668
val generatedFile = extension.className
6769

6870
// token check that uses the generated class instead of JSON
69-
target.tasks.register("logEnvVarUsages") {
71+
return target.tasks.register("logEnvVarUsages") {
7072
group = "verification"
7173
description = "Scan Java files for DD_/OTEL_ tokens and fail if unsupported (using generated constants)"
7274

@@ -127,8 +129,8 @@ private fun registerLogEnvVarUsages(target: Project, extension: SupportedTracerC
127129
}
128130

129131
/** Registers `checkEnvironmentVariablesUsage` (forbid EnvironmentVariables.get(...)). */
130-
private fun registerCheckEnvironmentVariablesUsage(project: Project) {
131-
project.tasks.register("checkEnvironmentVariablesUsage") {
132+
private fun registerCheckEnvironmentVariablesUsage(project: Project): TaskProvider<Task> {
133+
return project.tasks.register("checkEnvironmentVariablesUsage") {
132134
group = "verification"
133135
description = "Scans src/main/java for direct usages of EnvironmentVariables.get(...)"
134136

@@ -166,19 +168,19 @@ private fun registerCheckEnvironmentVariablesUsage(project: Project) {
166168
}
167169

168170
// Helper functions for checking Config Strings
169-
private fun normalize(configValue: String) =
171+
internal fun normalize(configValue: String) =
170172
"DD_" + configValue.uppercase().replace("-", "_").replace(".", "_")
171173

172174
// Checking "public" "static" "final"
173-
private fun NodeWithModifiers<*>.hasModifiers(vararg mods: Modifier.Keyword) =
175+
internal fun NodeWithModifiers<*>.hasModifiers(vararg mods: Modifier.Keyword) =
174176
mods.all { hasModifier(it) }
175177

176178
/** Registers `checkConfigStrings` to validate config definitions against documented supported configurations. */
177-
private fun registerCheckConfigStringsTask(project: Project, extension: SupportedTracerConfigurations) {
179+
private fun registerCheckConfigStringsTask(project: Project, extension: SupportedTracerConfigurations): TaskProvider<Task> {
178180
val ownerPath = extension.configOwnerPath
179181
val generatedFile = extension.className
180182

181-
project.tasks.register("checkConfigStrings") {
183+
return project.tasks.register("checkConfigStrings") {
182184
group = "verification"
183185
description = "Validates that all config definitions in `dd-trace-api/src/main/java/datadog/trace/api/config` exist in `metadata/supported-configurations.json`"
184186

@@ -190,61 +192,8 @@ private fun registerCheckConfigStringsTask(project: Project, extension: Supporte
190192
}
191193
inputs.files(mainSourceSetOutput)
192194

193-
doLast {
194-
val repoRoot: Path = project.rootProject.projectDir.toPath()
195-
val configDir = repoRoot.resolve("dd-trace-api/src/main/java/datadog/trace/api/config").toFile()
196-
197-
if (!configDir.exists()) {
198-
throw GradleException("Config directory not found: ${configDir.absolutePath}")
199-
}
200-
201-
val configFields = loadConfigFields(mainSourceSetOutput.get().get(), generatedFile.get())
202-
val supported = configFields.supported
203-
val aliasMapping = configFields.aliasMapping
204-
205-
var parserConfig = ParserConfiguration()
206-
parserConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8)
207-
208-
StaticJavaParser.setConfiguration(parserConfig)
209-
210-
val violations = buildList {
211-
configDir.listFiles()?.forEach { file ->
212-
val fileName = file.name
213-
val cu: CompilationUnit = StaticJavaParser.parse(file)
214-
215-
cu.findAll(VariableDeclarator::class.java).forEach { varDecl ->
216-
varDecl.parentNode
217-
.map { it as? FieldDeclaration }
218-
.ifPresent { field ->
219-
if (field.hasModifiers(Modifier.Keyword.PUBLIC, Modifier.Keyword.STATIC, Modifier.Keyword.FINAL) &&
220-
varDecl.typeAsString == "String") {
221-
222-
val fieldName = varDecl.nameAsString
223-
if (fieldName.endsWith("_DEFAULT")) return@ifPresent
224-
val init = varDecl.initializer.orElse(null) ?: return@ifPresent
225-
226-
if (init !is StringLiteralExpr) return@ifPresent
227-
val rawValue = init.value
228-
229-
val normalized = normalize(rawValue)
230-
if (normalized !in supported && normalized !in aliasMapping) {
231-
val line = varDecl.range.map { it.begin.line }.orElse(1)
232-
add("$fileName:$line -> Config '$rawValue' normalizes to '$normalized' " +
233-
"which is missing from '${extension.jsonFile.get()}'")
234-
}
235-
}
236-
}
237-
}
238-
}
239-
}
240-
241-
if (violations.isNotEmpty()) {
242-
logger.error("\nFound config definitions not in '${extension.jsonFile.get()}':")
243-
violations.forEach { logger.lifecycle(it) }
244-
throw GradleException("Undocumented Environment Variables found. Please add the above Environment Variables to '${extension.jsonFile.get()}'.")
245-
} else {
246-
logger.info("All config strings are present in '${extension.jsonFile.get()}'.")
247-
}
248-
}
195+
doLast("regular-config-check", RegularConfigCheckAction(mainSourceSetOutput, generatedFile, extension))
196+
doLast("profiling-config-check", ProfilingConfigCheckAction(mainSourceSetOutput, generatedFile, extension))
249197
}
250198
}
199+

dd-java-agent/agent-crashtracking/src/main/java/datadog/crashtracking/buildid/ElfBuildIdExtractor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.RandomAccessFile;
55
import java.nio.ByteBuffer;
66
import java.nio.ByteOrder;
7+
import java.nio.charset.StandardCharsets;
78
import java.nio.file.Path;
89
import java.util.Arrays;
910
import org.slf4j.Logger;
@@ -31,7 +32,7 @@ public class ElfBuildIdExtractor implements BuildIdExtractor {
3132

3233
// Note header constants
3334
private static final int NT_GNU_BUILD_ID = 3;
34-
private static final byte[] GNU_NOTE_NAME = "GNU\0".getBytes();
35+
private static final byte[] GNU_NOTE_NAME = "GNU\0".getBytes(StandardCharsets.UTF_8);
3536

3637
@Override
3738
public String extractBuildId(Path file) {

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/exception/Fingerprinter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.datadog.debugger.util.ExceptionHelper.getInnerMostThrowable;
44

55
import datadog.trace.bootstrap.debugger.DebuggerContext.ClassNameFilter;
6+
import java.nio.charset.StandardCharsets;
67
import java.security.MessageDigest;
78
import java.security.NoSuchAlgorithmException;
89
import org.slf4j.Logger;
@@ -30,15 +31,15 @@ public static String fingerprint(Throwable t, ClassNameFilter classNameFiltering
3031
return null;
3132
}
3233
String typeName = clazz.getTypeName();
33-
digest.update(typeName.getBytes());
34+
digest.update(typeName.getBytes(StandardCharsets.UTF_8));
3435
StackTraceElement[] stackTrace = t.getStackTrace();
3536
if (stackTrace != null) {
3637
for (StackTraceElement stackTraceElement : stackTrace) {
3738
String className = stackTraceElement.getClassName();
3839
if (classNameFiltering.isExcluded(className)) {
3940
continue;
4041
}
41-
digest.update(stackTraceElement.toString().getBytes());
42+
digest.update(stackTraceElement.toString().getBytes(StandardCharsets.UTF_8));
4243
}
4344
}
4445
return bytesToHex(digest.digest());
@@ -47,7 +48,7 @@ public static String fingerprint(Throwable t, ClassNameFilter classNameFiltering
4748
public static String fingerprint(StackTraceElement element) {
4849
try {
4950
MessageDigest digest = MessageDigest.getInstance("SHA-256");
50-
digest.update(element.toString().getBytes());
51+
digest.update(element.toString().getBytes(StandardCharsets.UTF_8));
5152
return bytesToHex(digest.digest());
5253
} catch (NoSuchAlgorithmException e) {
5354
LOGGER.debug("Unable to find digest algorithm SHA-256", e);

dd-java-agent/agent-iast/src/main/java/com/datadog/iast/model/json/VulnerabilityEncoding.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.squareup.moshi.Moshi;
66
import datadog.trace.api.iast.telemetry.IastMetric;
77
import datadog.trace.api.iast.telemetry.IastMetricCollector;
8+
import java.nio.charset.StandardCharsets;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
1011

@@ -25,7 +26,7 @@ public class VulnerabilityEncoding {
2526
public static String toJson(final VulnerabilityBatch value) {
2627
try {
2728
String json = BATCH_ADAPTER.toJson(value);
28-
return json.getBytes().length > MAX_SPAN_TAG_SIZE
29+
return json.getBytes(StandardCharsets.UTF_8).length > MAX_SPAN_TAG_SIZE
2930
? getExceededTagSizeJson(new TruncatedVulnerabilities(value.getVulnerabilities()))
3031
: json;
3132
} catch (Exception ex) {

0 commit comments

Comments
 (0)