Skip to content

Commit 1481179

Browse files
authored
Merge branch 'master' into dougqh/interceptor-bypass
2 parents 98f71f6 + b86f4f7 commit 1481179

File tree

24 files changed

+480
-117
lines changed

24 files changed

+480
-117
lines changed

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies {
4646
implementation("org.ow2.asm", "asm", "9.8")
4747
implementation("org.ow2.asm", "asm-tree", "9.8")
4848

49-
testImplementation("org.spockframework", "spock-core", "2.3-groovy-3.0")
49+
testImplementation(libs.spock.core)
5050
testImplementation("org.codehaus.groovy", "groovy-all", "3.0.17")
5151
}
5252

buildSrc/call-site-instrumentation-plugin/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ dependencies {
3737
implementation("com.github.javaparser", "javaparser-symbol-solver-core", "3.24.4")
3838

3939
testImplementation("net.bytebuddy", "byte-buddy", "1.17.5")
40-
testImplementation("org.spockframework", "spock-core", "2.3-groovy-3.0")
40+
testImplementation(libs.spock.core)
4141
testImplementation("org.objenesis", "objenesis", "3.0.1")
4242
testImplementation("org.codehaus.groovy", "groovy-all", "3.0.17")
4343
testImplementation("javax.servlet", "javax.servlet-api", "3.0.1")

buildSrc/settings.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
include(":call-site-instrumentation-plugin")
2+
3+
dependencyResolutionManagement {
4+
versionCatalogs {
5+
create("libs") {
6+
from(files("../gradle/libs.versions.toml"))
7+
}
8+
}
9+
}

communication/src/main/java/datadog/communication/ddagent/SharedCommunicationObjects.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public DDAgentFeaturesDiscovery featuresDiscovery(Config config) {
159159
ret.discover(); // safe to run on same thread
160160
} else {
161161
// avoid performing blocking I/O operation on application thread
162-
AgentTaskScheduler.INSTANCE.execute(ret::discover);
162+
AgentTaskScheduler.INSTANCE.execute(ret::discoverIfOutdated);
163163
}
164164
}
165165
featuresDiscovery = ret;

dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStream.java

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package datadog.trace.bootstrap.instrumentation.buffer;
22

3-
import java.io.FilterOutputStream;
43
import java.io.IOException;
54
import java.io.OutputStream;
65

76
/**
8-
* A circular buffer with a lookbehind buffer of n bytes. The first time that the latest n bytes
9-
* matches the marker, a content is injected before.
7+
* An OutputStream containing a circular buffer with a lookbehind buffer of n bytes. The first time
8+
* that the latest n bytes matches the marker, a content is injected before.
109
*/
11-
public class InjectingPipeOutputStream extends FilterOutputStream {
10+
public class InjectingPipeOutputStream extends OutputStream {
1211
private final byte[] lookbehind;
1312
private int pos;
1413
private boolean bufferFilled;
@@ -18,10 +17,11 @@ public class InjectingPipeOutputStream extends FilterOutputStream {
1817
private int matchingPos = 0;
1918
private final Runnable onContentInjected;
2019
private final int bulkWriteThreshold;
20+
private final OutputStream downstream;
2121

2222
/**
2323
* @param downstream the delegate output stream
24-
* @param marker the marker to find in the stream
24+
* @param marker the marker to find in the stream. Must at least be one byte.
2525
* @param contentToInject the content to inject once before the marker if found.
2626
* @param onContentInjected callback called when and if the content is injected.
2727
*/
@@ -30,7 +30,7 @@ public InjectingPipeOutputStream(
3030
final byte[] marker,
3131
final byte[] contentToInject,
3232
final Runnable onContentInjected) {
33-
super(downstream);
33+
this.downstream = downstream;
3434
this.marker = marker;
3535
this.lookbehind = new byte[marker.length];
3636
this.pos = 0;
@@ -42,12 +42,12 @@ public InjectingPipeOutputStream(
4242
@Override
4343
public void write(int b) throws IOException {
4444
if (found) {
45-
out.write(b);
45+
downstream.write(b);
4646
return;
4747
}
4848

4949
if (bufferFilled) {
50-
out.write(lookbehind[pos]);
50+
downstream.write(lookbehind[pos]);
5151
}
5252

5353
lookbehind[pos] = (byte) b;
@@ -60,7 +60,7 @@ public void write(int b) throws IOException {
6060
if (marker[matchingPos++] == b) {
6161
if (matchingPos == marker.length) {
6262
found = true;
63-
out.write(contentToInject);
63+
downstream.write(contentToInject);
6464
if (onContentInjected != null) {
6565
onContentInjected.run();
6666
}
@@ -72,46 +72,48 @@ public void write(int b) throws IOException {
7272
}
7373

7474
@Override
75-
public void write(byte[] b, int off, int len) throws IOException {
75+
public void write(byte[] array, int off, int len) throws IOException {
7676
if (found) {
77-
out.write(b, off, len);
77+
downstream.write(array, off, len);
7878
return;
7979
}
8080
if (len > bulkWriteThreshold) {
8181
// if the content is large enough, we can bulk write everything but the N trail and tail.
8282
// This because the buffer can already contain some byte from a previous single write.
8383
// Also we need to fill the buffer with the tail since we don't know about the next write.
84-
int idx = arrayContains(b, marker);
84+
int idx = arrayContains(array, off, len, marker);
8585
if (idx >= 0) {
8686
// we have a full match. just write everything
8787
found = true;
8888
drain();
89-
out.write(b, off, idx);
90-
out.write(contentToInject);
89+
downstream.write(array, off, idx);
90+
downstream.write(contentToInject);
9191
if (onContentInjected != null) {
9292
onContentInjected.run();
9393
}
94-
out.write(b, off + idx, len - idx);
94+
downstream.write(array, off + idx, len - idx);
9595
} else {
9696
// we don't have a full match. write everything in a bulk except the lookbehind buffer
9797
// sequentially
9898
for (int i = off; i < off + marker.length - 1; i++) {
99-
write(b[i]);
99+
write(array[i]);
100100
}
101101
drain();
102-
out.write(b, off + marker.length - 1, len - bulkWriteThreshold);
102+
downstream.write(array, off + marker.length - 1, len - bulkWriteThreshold);
103103
for (int i = len - marker.length + 1; i < len; i++) {
104-
write(b[i]);
104+
write(array[i]);
105105
}
106106
}
107107
} else {
108108
// use slow path because the length to write is small and within the lookbehind buffer size
109-
super.write(b, off, len);
109+
for (int i = off; i < off + len; i++) {
110+
write(array[i]);
111+
}
110112
}
111113
}
112114

113-
private int arrayContains(byte[] array, byte[] search) {
114-
for (int i = 0; i < array.length - search.length; i++) {
115+
private int arrayContains(byte[] array, int off, int len, byte[] search) {
116+
for (int i = off; i < len - search.length; i++) {
115117
if (array[i] == search[0]) {
116118
boolean found = true;
117119
int k = i;
@@ -133,21 +135,26 @@ private int arrayContains(byte[] array, byte[] search) {
133135
private void drain() throws IOException {
134136
if (bufferFilled) {
135137
for (int i = 0; i < lookbehind.length; i++) {
136-
out.write(lookbehind[(pos + i) % lookbehind.length]);
138+
downstream.write(lookbehind[(pos + i) % lookbehind.length]);
137139
}
138140
} else {
139-
out.write(this.lookbehind, 0, pos);
141+
downstream.write(this.lookbehind, 0, pos);
140142
}
141143
pos = 0;
142144
matchingPos = 0;
143145
bufferFilled = false;
144146
}
145147

148+
@Override
149+
public void flush() throws IOException {
150+
downstream.flush();
151+
}
152+
146153
@Override
147154
public void close() throws IOException {
148155
if (!found) {
149156
drain();
150157
}
151-
super.close();
158+
downstream.close();
152159
}
153160
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package datadog.trace.bootstrap.instrumentation.buffer;
2+
3+
import java.io.IOException;
4+
import java.io.Writer;
5+
6+
/**
7+
* A Writer containing a circular buffer with a lookbehind buffer of n bytes. The first time that
8+
* the latest n bytes matches the marker, a content is injected before.
9+
*/
10+
public class InjectingPipeWriter extends Writer {
11+
private final char[] lookbehind;
12+
private int pos;
13+
private boolean bufferFilled;
14+
private final char[] marker;
15+
private final char[] contentToInject;
16+
private boolean found = false;
17+
private int matchingPos = 0;
18+
private final Runnable onContentInjected;
19+
private final int bulkWriteThreshold;
20+
private final Writer downstream;
21+
22+
/**
23+
* @param downstream the delegate writer
24+
* @param marker the marker to find in the stream. Must at least be one char.
25+
* @param contentToInject the content to inject once before the marker if found.
26+
* @param onContentInjected callback called when and if the content is injected.
27+
*/
28+
public InjectingPipeWriter(
29+
final Writer downstream,
30+
final char[] marker,
31+
final char[] contentToInject,
32+
final Runnable onContentInjected) {
33+
this.downstream = downstream;
34+
this.marker = marker;
35+
this.lookbehind = new char[marker.length];
36+
this.pos = 0;
37+
this.contentToInject = contentToInject;
38+
this.onContentInjected = onContentInjected;
39+
this.bulkWriteThreshold = marker.length * 2 - 2;
40+
}
41+
42+
@Override
43+
public void write(int c) throws IOException {
44+
if (found) {
45+
downstream.write(c);
46+
return;
47+
}
48+
49+
if (bufferFilled) {
50+
downstream.write(lookbehind[pos]);
51+
}
52+
53+
lookbehind[pos] = (char) c;
54+
pos = (pos + 1) % lookbehind.length;
55+
56+
if (!bufferFilled) {
57+
bufferFilled = pos == 0;
58+
}
59+
60+
if (marker[matchingPos++] == c) {
61+
if (matchingPos == marker.length) {
62+
found = true;
63+
downstream.write(contentToInject);
64+
if (onContentInjected != null) {
65+
onContentInjected.run();
66+
}
67+
drain();
68+
}
69+
} else {
70+
matchingPos = 0;
71+
}
72+
}
73+
74+
@Override
75+
public void flush() throws IOException {
76+
downstream.flush();
77+
}
78+
79+
@Override
80+
public void write(char[] array, int off, int len) throws IOException {
81+
if (found) {
82+
downstream.write(array, off, len);
83+
return;
84+
}
85+
if (len > bulkWriteThreshold) {
86+
// if the content is large enough, we can bulk write everything but the N trail and tail.
87+
// This because the buffer can already contain some byte from a previous single write.
88+
// Also we need to fill the buffer with the tail since we don't know about the next write.
89+
int idx = arrayContains(array, off, len, marker);
90+
if (idx >= 0) {
91+
// we have a full match. just write everything
92+
found = true;
93+
drain();
94+
downstream.write(array, off, idx);
95+
downstream.write(contentToInject);
96+
if (onContentInjected != null) {
97+
onContentInjected.run();
98+
}
99+
downstream.write(array, off + idx, len - idx);
100+
} else {
101+
// we don't have a full match. write everything in a bulk except the lookbehind buffer
102+
// sequentially
103+
for (int i = off; i < off + marker.length - 1; i++) {
104+
write(array[i]);
105+
}
106+
drain();
107+
downstream.write(array, off + marker.length - 1, len - bulkWriteThreshold);
108+
for (int i = len - marker.length + 1; i < len; i++) {
109+
write(array[i]);
110+
}
111+
}
112+
} else {
113+
// use slow path because the length to write is small and within the lookbehind buffer size
114+
for (int i = off; i < off + len; i++) {
115+
write(array[i]);
116+
}
117+
}
118+
}
119+
120+
private int arrayContains(char[] array, int off, int len, char[] search) {
121+
for (int i = off; i < len - search.length; i++) {
122+
if (array[i] == search[0]) {
123+
boolean found = true;
124+
int k = i;
125+
for (int j = 1; j < search.length; j++) {
126+
k++;
127+
if (array[k] != search[j]) {
128+
found = false;
129+
break;
130+
}
131+
}
132+
if (found) {
133+
return i;
134+
}
135+
}
136+
}
137+
return -1;
138+
}
139+
140+
private void drain() throws IOException {
141+
if (bufferFilled) {
142+
for (int i = 0; i < lookbehind.length; i++) {
143+
downstream.write(lookbehind[(pos + i) % lookbehind.length]);
144+
}
145+
} else {
146+
downstream.write(this.lookbehind, 0, pos);
147+
}
148+
pos = 0;
149+
matchingPos = 0;
150+
bufferFilled = false;
151+
}
152+
153+
@Override
154+
public void close() throws IOException {
155+
if (!found) {
156+
drain();
157+
}
158+
downstream.close();
159+
}
160+
}

dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/buffer/InjectingPipeOutputStreamTest.groovy

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,6 @@ package datadog.trace.bootstrap.instrumentation.buffer
33
import datadog.trace.test.util.DDSpecification
44

55
class InjectingPipeOutputStreamTest extends DDSpecification {
6-
7-
static class ExceptionControlledOutputStream extends FilterOutputStream {
8-
9-
boolean failWrite = false
10-
11-
ExceptionControlledOutputStream(OutputStream out) {
12-
super(out)
13-
}
14-
15-
@Override
16-
void write(int b) throws IOException {
17-
if (failWrite) {
18-
throw new IOException("Failed")
19-
}
20-
super.write(b)
21-
}
22-
}
23-
246
def 'should filter a buffer and inject if found #found'() {
257
setup:
268
def downstream = new ByteArrayOutputStream()

0 commit comments

Comments
 (0)