Skip to content

Commit e2d3b87

Browse files
committedJul 1, 2022
7903209: Handle lifecycle of libclang abstractions more explicitly
Reviewed-by: sundar
1 parent 9fa389f commit e2d3b87

16 files changed

+433
-378
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*
25+
*/
26+
27+
package org.openjdk.jextract.clang;
28+
29+
import java.lang.foreign.MemoryAddress;
30+
import java.lang.foreign.MemorySegment;
31+
import java.lang.foreign.MemorySession;
32+
import java.lang.foreign.SegmentAllocator;
33+
34+
/**
35+
* This class models a libclang entity that has an explicit lifecycle (e.g. TranslationUnit, Index).
36+
* This class starts a new confined session and an arena allocator; this arena allocator is used by all
37+
* the abstractions "owned" by this disposable. For instance, as a CXCursor's lifetime is the same as that of
38+
* the CXTranslationUnit's lifetime, cursors are allocated inside the translation unit's lifetime.
39+
*/
40+
public abstract class ClangDisposable implements SegmentAllocator, AutoCloseable {
41+
protected final MemorySegment ptr;
42+
protected final MemorySession session;
43+
protected final SegmentAllocator arena;
44+
45+
public ClangDisposable(MemoryAddress ptr, long size, Runnable cleanup) {
46+
this.session = MemorySession.openConfined();
47+
this.ptr = MemorySegment.ofAddress(ptr, size, session).asReadOnly();
48+
session.addCloseAction(cleanup);
49+
this.arena = SegmentAllocator.newNativeArena(session);
50+
}
51+
52+
public ClangDisposable(MemoryAddress ptr, Runnable cleanup) {
53+
this(ptr, 0, cleanup);
54+
}
55+
56+
@Override
57+
public void close() {
58+
session.close();
59+
}
60+
61+
@Override
62+
public MemorySegment allocate(long bytesSize, long bytesAlignment) {
63+
return arena.allocate(bytesSize, bytesAlignment);
64+
}
65+
66+
/**
67+
* A libclang entity owned by some libclang disposable entity. Entities modelled by this class
68+
* do not have their own session; instead, they piggyback on the session of their owner.
69+
*/
70+
static class Owned {
71+
final MemorySegment segment;
72+
final ClangDisposable owner;
73+
74+
protected Owned(MemorySegment segment, ClangDisposable owner) {
75+
this.segment = segment;
76+
this.owner = owner;
77+
}
78+
}
79+
}

‎src/main/java/org/openjdk/jextract/clang/Cursor.java

+101-66
Original file line numberDiff line numberDiff line change
@@ -29,23 +29,18 @@
2929
import java.lang.foreign.MemoryAddress;
3030
import java.lang.foreign.MemorySegment;
3131
import java.lang.foreign.MemorySession;
32-
import java.lang.foreign.SegmentAllocator;
3332
import org.openjdk.jextract.clang.libclang.CXCursorVisitor;
3433
import org.openjdk.jextract.clang.libclang.Index_h;
3534

36-
import java.util.ArrayList;
37-
import java.util.stream.Stream;
35+
import java.util.function.Consumer;
3836

39-
import static org.openjdk.jextract.clang.LibClang.IMPLICIT_ALLOCATOR;
37+
public final class Cursor extends ClangDisposable.Owned {
4038

41-
public final class Cursor {
42-
43-
private final MemorySegment cursor;
4439
private final int kind;
4540

46-
Cursor(MemorySegment cursor) {
47-
this.cursor = cursor;
48-
kind = Index_h.clang_getCursorKind(cursor);
41+
Cursor(MemorySegment segment, ClangDisposable owner) {
42+
super(segment, owner);
43+
kind = Index_h.clang_getCursorKind(segment);
4944
}
5045

5146
public boolean isDeclaration() {
@@ -61,32 +56,32 @@ public boolean isInvalid() {
6156
}
6257

6358
public boolean isDefinition() {
64-
return Index_h.clang_isCursorDefinition(cursor) != 0;
59+
return Index_h.clang_isCursorDefinition(segment) != 0;
6560
}
6661

6762
public boolean isAttribute() { return Index_h.clang_isAttribute(kind) != 0; }
6863

6964
public boolean isAnonymousStruct() {
70-
return Index_h.clang_Cursor_isAnonymousRecordDecl(cursor) != 0;
65+
return Index_h.clang_Cursor_isAnonymousRecordDecl(segment) != 0;
7166
}
7267

7368
public boolean isMacroFunctionLike() {
74-
return Index_h.clang_Cursor_isMacroFunctionLike(cursor) != 0;
69+
return Index_h.clang_Cursor_isMacroFunctionLike(segment) != 0;
7570
}
7671

7772
public String spelling() {
78-
return LibClang.CXStrToString(allocator ->
79-
Index_h.clang_getCursorSpelling(allocator, cursor));
73+
var spelling = Index_h.clang_getCursorSpelling(LibClang.STRING_ALLOCATOR, segment);
74+
return LibClang.CXStrToString(spelling);
8075
}
8176

8277
public String USR() {
83-
return LibClang.CXStrToString(allocator ->
84-
Index_h.clang_getCursorUSR(allocator, cursor));
78+
var USR = Index_h.clang_getCursorUSR(LibClang.STRING_ALLOCATOR, segment);
79+
return LibClang.CXStrToString(USR);
8580
}
8681

8782
public String prettyPrinted(PrintingPolicy policy) {
88-
return LibClang.CXStrToString(allocator ->
89-
Index_h.clang_getCursorPrettyPrinted(allocator, cursor, policy.ptr()));
83+
var prettyOutput = Index_h.clang_getCursorPrettyPrinted(LibClang.STRING_ALLOCATOR, segment, policy.ptr());
84+
return LibClang.CXStrToString(prettyOutput);
9085
}
9186

9287
public String prettyPrinted() {
@@ -96,129 +91,169 @@ public String prettyPrinted() {
9691
}
9792

9893
public String displayName() {
99-
return LibClang.CXStrToString(allocator ->
100-
Index_h.clang_getCursorDisplayName(allocator, cursor));
94+
var displayName = Index_h.clang_getCursorDisplayName(LibClang.STRING_ALLOCATOR, segment);
95+
return LibClang.CXStrToString(displayName);
10196
}
10297

10398
public boolean equalCursor(Cursor other) {
104-
return Index_h.clang_equalCursors(cursor, other.cursor) != 0;
99+
return Index_h.clang_equalCursors(segment, other.segment) != 0;
105100
}
106101

107102
public Type type() {
108-
return new Type(Index_h.clang_getCursorType(IMPLICIT_ALLOCATOR, cursor));
103+
var cursorType = Index_h.clang_getCursorType(owner, segment);
104+
return new Type(cursorType, owner);
109105
}
110106

111107
public Type getEnumDeclIntegerType() {
112-
return new Type(Index_h.clang_getEnumDeclIntegerType(IMPLICIT_ALLOCATOR, cursor));
108+
var enumType = Index_h.clang_getEnumDeclIntegerType(owner, segment);
109+
return new Type(enumType, owner);
113110
}
114111

115112
public Cursor getDefinition() {
116-
return new Cursor(Index_h.clang_getCursorDefinition(IMPLICIT_ALLOCATOR, cursor));
113+
var cursorDef = Index_h.clang_getCursorDefinition(owner, segment);
114+
return new Cursor(cursorDef, owner);
117115
}
118116

119117
public SourceLocation getSourceLocation() {
120-
MemorySegment loc = Index_h.clang_getCursorLocation(IMPLICIT_ALLOCATOR, cursor);
118+
MemorySegment loc = Index_h.clang_getCursorLocation(owner, segment);
121119
try (MemorySession session = MemorySession.openConfined()) {
122120
if (Index_h.clang_equalLocations(loc, Index_h.clang_getNullLocation(session)) != 0) {
123121
return null;
124122
}
125123
}
126-
return new SourceLocation(loc);
124+
return new SourceLocation(loc, owner);
127125
}
128126

129127
public SourceRange getExtent() {
130-
MemorySegment range = Index_h.clang_getCursorExtent(IMPLICIT_ALLOCATOR, cursor);
128+
MemorySegment range = Index_h.clang_getCursorExtent(owner, segment);
131129
if (Index_h.clang_Range_isNull(range) != 0) {
132130
return null;
133131
}
134-
return new SourceRange(range);
132+
return new SourceRange(range, owner);
135133
}
136134

137135
public int numberOfArgs() {
138-
return Index_h.clang_Cursor_getNumArguments(cursor);
136+
return Index_h.clang_Cursor_getNumArguments(segment);
139137
}
140138

141139
public Cursor getArgument(int idx) {
142-
return new Cursor(Index_h.clang_Cursor_getArgument(IMPLICIT_ALLOCATOR, cursor, idx));
140+
var cursorArg = Index_h.clang_Cursor_getArgument(owner, segment, idx);
141+
return new Cursor(cursorArg, owner);
143142
}
144143

145144
// C long long, 64-bit
146145
public long getEnumConstantValue() {
147-
return Index_h.clang_getEnumConstantDeclValue(cursor);
146+
return Index_h.clang_getEnumConstantDeclValue(segment);
148147
}
149148

150149
// C unsigned long long, 64-bit
151150
public long getEnumConstantUnsignedValue() {
152-
return Index_h.clang_getEnumConstantDeclUnsignedValue(cursor);
151+
return Index_h.clang_getEnumConstantDeclUnsignedValue(segment);
153152
}
154153

155154
public boolean isBitField() {
156-
return Index_h.clang_Cursor_isBitField(cursor) != 0;
155+
return Index_h.clang_Cursor_isBitField(segment) != 0;
157156
}
158157

159158
public int getBitFieldWidth() {
160-
return Index_h.clang_getFieldDeclBitWidth(cursor);
159+
return Index_h.clang_getFieldDeclBitWidth(segment);
161160
}
162161

163162
public CursorKind kind() {
164163
return CursorKind.valueOf(kind);
165164
}
166165

167166
public CursorLanguage language() {
168-
return CursorLanguage.valueOf(Index_h.clang_getCursorLanguage(cursor));
167+
return CursorLanguage.valueOf(Index_h.clang_getCursorLanguage(segment));
169168
}
170169

171170
public int kind0() {
172171
return kind;
173172
}
174173

175174
/**
176-
* For a cursor that is a reference, retrieve a cursor representing the entity that it references.
175+
* For a segment that is a reference, retrieve a segment representing the entity that it references.
177176
*/
178177
public Cursor getCursorReferenced() {
179-
return new Cursor(Index_h.clang_getCursorReferenced(
180-
IMPLICIT_ALLOCATOR, cursor));
178+
var referenced = Index_h.clang_getCursorReferenced(owner, segment);
179+
return new Cursor(referenced, owner);
180+
}
181+
182+
public void forEach(Consumer<Cursor> action) {
183+
CursorChildren.forEach(this, action);
181184
}
182185

186+
/**
187+
* We run the visitor action inside the upcall, so that we do not have to worry about
188+
* having to copy cursors into separate off-heap storage. To do this, we have to setup
189+
* some context for the upcall, so that the upcall code can call the "correct" user-defined visitor action.
190+
* Note: exceptions must be delayed until after the upcall has returned; this is necessary as upcalls
191+
* cannot throw (if they do, they cause a JVM crash).
192+
*/
183193
private static class CursorChildren {
184-
private static final ArrayList<Cursor> children = new ArrayList<>();
194+
195+
static class Context {
196+
private final Consumer<Cursor> action;
197+
private final ClangDisposable owner;
198+
private RuntimeException exception;
199+
200+
Context(Consumer<Cursor> action, ClangDisposable owner) {
201+
this.action = action;
202+
this.owner = owner;
203+
}
204+
205+
boolean visit(MemorySegment segment) {
206+
// Note: the session of this cursor is smaller than that of the translation unit
207+
// this is because the cursor will be destroyed when the upcall ends. This means
208+
// that the cursor passed by the visitor must NOT be leaked into a field and accessed
209+
// at a later time (or the liveness check will fail with IllegalStateException).
210+
try {
211+
// run the visitor action
212+
action.accept(new Cursor(segment, owner));
213+
return true;
214+
} catch (RuntimeException ex) {
215+
// if we fail, record the exception, and return false to stop the visit
216+
exception = ex;
217+
return false;
218+
}
219+
}
220+
221+
void handleExceptions() {
222+
if (exception != null) {
223+
throw exception;
224+
}
225+
}
226+
}
227+
228+
static Context pendingContext = null;
229+
185230
private static final MemorySegment callback = CXCursorVisitor.allocate((c, p, d) -> {
186-
MemorySegment copy = MemorySegment.allocateNative(c.byteSize(), MemorySession.openImplicit());
187-
copy.copyFrom(c);
188-
Cursor cursor = new Cursor(copy);
189-
children.add(cursor);
190-
return Index_h.CXChildVisit_Continue();
231+
if (pendingContext.visit(c)) {
232+
return Index_h.CXChildVisit_Continue();
233+
} else {
234+
return Index_h.CXChildVisit_Break();
235+
}
191236
}, MemorySession.openImplicit());
192237

193-
synchronized static Stream<Cursor> get(Cursor c) {
238+
synchronized static void forEach(Cursor c, Consumer<Cursor> op) {
239+
// everything is confined, no need to synchronize
240+
Context prevContext = pendingContext;
194241
try {
195-
Index_h.clang_visitChildren(c.cursor, callback, MemoryAddress.NULL);
196-
return new ArrayList<>(children).stream();
242+
pendingContext = new Context(op, c.owner);
243+
Index_h.clang_visitChildren(c.segment, callback, MemoryAddress.NULL);
244+
pendingContext.handleExceptions();
197245
} finally {
198-
children.clear();
246+
pendingContext = prevContext;
199247
}
200248
}
201249
}
202250

203-
public Stream<Cursor> children() {
204-
return CursorChildren.get(this);
205-
}
206-
207-
public Stream<Cursor> allChildren() {
208-
return children().flatMap(c -> Stream.concat(Stream.of(c), c.children()));
209-
}
210-
211-
public String getMangling() {
212-
return LibClang.CXStrToString(allocator ->
213-
Index_h.clang_Cursor_getMangling(allocator, cursor));
214-
}
215-
216251
public TranslationUnit getTranslationUnit() {
217-
return new TranslationUnit(Index_h.clang_Cursor_getTranslationUnit(cursor));
252+
return new TranslationUnit(Index_h.clang_Cursor_getTranslationUnit(segment));
218253
}
219254

220255
private MemoryAddress eval0() {
221-
return Index_h.clang_Cursor_Evaluate(cursor);
256+
return Index_h.clang_Cursor_Evaluate(segment);
222257
}
223258

224259
public EvalResult eval() {
@@ -227,7 +262,7 @@ public EvalResult eval() {
227262
}
228263

229264
public PrintingPolicy getPrintingPolicy() {
230-
return new PrintingPolicy(Index_h.clang_getCursorPrintingPolicy(cursor));
265+
return new PrintingPolicy(Index_h.clang_getCursorPrintingPolicy(segment));
231266
}
232267

233268
@Override
@@ -236,7 +271,7 @@ public boolean equals(Object other) {
236271
return true;
237272
}
238273
return other instanceof Cursor otherCursor &&
239-
(Index_h.clang_equalCursors(cursor, otherCursor.cursor) != 0);
274+
(Index_h.clang_equalCursors(segment, otherCursor.segment) != 0);
240275
}
241276

242277
@Override

0 commit comments

Comments
 (0)
Please sign in to comment.