Skip to content

Commit 19c024a

Browse files
committed
[GR-46471] Mitigating the consequences of a potential stack overflow in adoptChildren().
PullRequest: graal/14729
2 parents 7d7a573 + 4455c66 commit 19c024a

File tree

2 files changed

+136
-5
lines changed
  • truffle/src

2 files changed

+136
-5
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.api.test.nodes;
42+
43+
import static org.junit.Assert.assertSame;
44+
45+
import org.junit.Test;
46+
47+
import com.oracle.truffle.api.nodes.Node;
48+
49+
public class DeepAdoptNodeTest {
50+
51+
static class DeepNode extends Node {
52+
@Child DeepNode child;
53+
54+
DeepNode(int depth) {
55+
if (depth > 0) {
56+
child = new DeepNode(depth - 1);
57+
}
58+
}
59+
60+
}
61+
62+
@Test
63+
public void testAdoptionNearStackLimit() {
64+
// "warm up": we are going to invoke adoption near stack limit.
65+
// So, we want to ensure that all the needed classes are loaded.
66+
// We do not want the test to fail due to ExceptionInInitializerError
67+
// from the initialization of the truffle runtime.
68+
adoptAndCheck(new DeepNode(10));
69+
70+
// perform the actual test
71+
exhaustStackAndAdopt(new DeepNode(100));
72+
}
73+
74+
private static void exhaustStackAndAdopt(DeepNode node) {
75+
try {
76+
exhaustStackAndAdopt(node);
77+
} catch (StackOverflowError error) {
78+
adoptAndCheck(node);
79+
}
80+
}
81+
82+
private static void adoptAndCheck(DeepNode node) {
83+
try {
84+
node.adoptChildren();
85+
86+
// All nodes should be adopted on success
87+
DeepNode parent = node;
88+
while (parent.child != null) {
89+
DeepNode child = parent.child;
90+
assertSame(parent, child.getParent());
91+
parent = child;
92+
}
93+
} catch (StackOverflowError err) {
94+
// Nodes not adopted due to StackOverflowError should
95+
// not be hidden under nodes adopted correctly
96+
// i.e. nothing should be adopted in our case
97+
DeepNode child = node.child;
98+
while (child != null) {
99+
// Not using assertNull(child.getParent()) intentionally.
100+
// We are close to stack limit and assertNull() is too fancy to fit
101+
assert child.getParent() == null;
102+
child = child.child;
103+
}
104+
// Rethrow the error to ensure that adoptAndCheck()
105+
// is invoked again (with more stack space) until
106+
// it succeeds finally.
107+
throw err;
108+
}
109+
}
110+
111+
}

truffle/src/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/Node.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ public final <T extends Node> T[] insert(final T[] newChildren) {
205205
if (newChildren != null) {
206206
for (Node newChild : newChildren) {
207207
adoptHelper(newChild);
208+
assert checkSameLanguages(newChild) && newChild.checkSameLanguageOfChildren();
208209
}
209210
}
210211
return newChildren;
@@ -223,6 +224,7 @@ public final <T extends Node> T insert(final T newChild) {
223224
CompilerDirectives.transferToInterpreterAndInvalidate();
224225
if (newChild != null) {
225226
adoptHelper(newChild);
227+
assert checkSameLanguages(newChild) && newChild.checkSameLanguageOfChildren();
226228
}
227229
return newChild;
228230
}
@@ -257,6 +259,7 @@ protected final void notifyInserted(Node node) {
257259
public final void adoptChildren() {
258260
CompilerDirectives.transferToInterpreterAndInvalidate();
259261
NodeUtil.adoptChildrenHelper(this);
262+
assert checkSameLanguageOfChildren();
260263
}
261264

262265
final void adoptHelper(final Node newChild) {
@@ -265,15 +268,16 @@ final void adoptHelper(final Node newChild) {
265268
throw new IllegalStateException("The parent of a node can never be the node itself.");
266269
}
267270
if (newChild.isAdoptable()) {
268-
assert checkSameLanguages(newChild);
269-
newChild.parent = this;
270271
NodeUtil.adoptChildrenHelper(newChild);
272+
newChild.parent = this;
271273
}
272274
}
273275

274276
int adoptChildrenAndCount() {
275277
CompilerAsserts.neverPartOfCompilation();
276-
return 1 + NodeUtil.adoptChildrenAndCountHelper(this);
278+
int count = 1 + NodeUtil.adoptChildrenAndCountHelper(this);
279+
assert checkSameLanguageOfChildren();
280+
return count;
277281
}
278282

279283
int adoptAndCountHelper(Node newChild) {
@@ -283,13 +287,29 @@ int adoptAndCountHelper(Node newChild) {
283287
}
284288
int count = 1;
285289
if (newChild.isAdoptable()) {
286-
assert checkSameLanguages(newChild);
287-
newChild.parent = this;
288290
count += NodeUtil.adoptChildrenAndCountHelper(newChild);
291+
newChild.parent = this;
289292
}
290293
return count;
291294
}
292295

296+
private static final NodeVisitor SAME_LANGUAGE_CHECK_VISITOR = new NodeVisitor() {
297+
@Override
298+
public boolean visit(Node node) {
299+
if (node.isAdoptable()) {
300+
assert node.parent.checkSameLanguages(node);
301+
return true;
302+
} else {
303+
return false;
304+
}
305+
};
306+
};
307+
308+
boolean checkSameLanguageOfChildren() {
309+
NodeUtil.forEachChild(this, SAME_LANGUAGE_CHECK_VISITOR);
310+
return true;
311+
}
312+
293313
private boolean checkSameLanguages(final Node newChild) {
294314
if (newChild instanceof ExecutableNode && !(newChild instanceof RootNode)) {
295315
RootNode root = getRootNode();

0 commit comments

Comments
 (0)