1 /*
  2  *  Copyright (c) 2020, 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 jdk.internal.clang;
 28 
 29 import jdk.incubator.foreign.MemoryAddress;
 30 import jdk.incubator.foreign.MemoryLayout;
 31 import jdk.incubator.foreign.MemorySegment;
 32 import jdk.incubator.foreign.ResourceScope;
 33 import jdk.incubator.foreign.SegmentAllocator;
 34 import jdk.internal.clang.libclang.CXToken;
 35 import jdk.internal.clang.libclang.Index_h;
 36 import jdk.internal.clang.libclang.CXUnsavedFile;
 37 
 38 import java.io.IOException;
 39 import java.nio.file.Path;
 40 import java.util.Objects;
 41 import java.util.function.Consumer;
 42 
 43 import static jdk.internal.clang.libclang.Index_h.C_INT;
 44 import static jdk.internal.clang.libclang.Index_h.C_POINTER;
 45 
 46 import static jdk.internal.clang.LibClang.IMPLICIT_ALLOCATOR;
 47 
 48 public class TranslationUnit implements AutoCloseable {
 49 
 50     private MemoryAddress tu;
 51 
 52     TranslationUnit(MemoryAddress tu) {
 53         this.tu = tu;
 54     }
 55 
 56     public Cursor getCursor() {
 57         return new Cursor(Index_h.clang_getTranslationUnitCursor(IMPLICIT_ALLOCATOR, tu));
 58     }
 59 
 60     public Diagnostic[] getDiagnostics() {
 61         int cntDiags = Index_h.clang_getNumDiagnostics(tu);
 62         Diagnostic[] rv = new Diagnostic[cntDiags];
 63         for (int i = 0; i < cntDiags; i++) {
 64             MemoryAddress diag = Index_h.clang_getDiagnostic(tu, i);
 65             rv[i] = new Diagnostic(diag);
 66         }
 67 
 68         return rv;
 69     }
 70 
 71     public final void save(Path path) throws TranslationUnitSaveException {
 72         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
 73             var allocator = SegmentAllocator.nativeAllocator(scope);
 74             MemorySegment pathStr = allocator.allocateUtf8String(path.toAbsolutePath().toString());
 75             SaveError res = SaveError.valueOf(Index_h.clang_saveTranslationUnit(tu, pathStr, 0));
 76             if (res != SaveError.None) {
 77                 throw new TranslationUnitSaveException(path, res);
 78             }
 79         }
 80     }
 81 
 82     void processDiagnostics(Consumer<Diagnostic> dh) {
 83         Objects.requireNonNull(dh);
 84         for (Diagnostic diag : getDiagnostics()) {
 85             dh.accept(diag);
 86         }
 87     }
 88 
 89     static long FILENAME_OFFSET = CXUnsavedFile.$LAYOUT().byteOffset(MemoryLayout.PathElement.groupElement("Filename"));
 90     static long CONTENTS_OFFSET = CXUnsavedFile.$LAYOUT().byteOffset(MemoryLayout.PathElement.groupElement("Contents"));
 91     static long LENGTH_OFFSET = CXUnsavedFile.$LAYOUT().byteOffset(MemoryLayout.PathElement.groupElement("Length"));
 92 
 93     public void reparse(Index.UnsavedFile... inMemoryFiles) {
 94         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
 95             var allocator = SegmentAllocator.newNativeArena(scope);
 96             MemorySegment files = inMemoryFiles.length == 0 ?
 97                     null :
 98                     allocator.allocateArray(CXUnsavedFile.$LAYOUT(), inMemoryFiles.length);
 99             for (int i = 0; i < inMemoryFiles.length; i++) {
100                 MemorySegment start = files.asSlice(i * CXUnsavedFile.$LAYOUT().byteSize());
101                 start.set(C_POINTER, FILENAME_OFFSET, allocator.allocateUtf8String(inMemoryFiles[i].file));
102                 start.set(C_POINTER, CONTENTS_OFFSET, allocator.allocateUtf8String(inMemoryFiles[i].contents));
103                 start.set(C_INT, LENGTH_OFFSET, inMemoryFiles[i].contents.length());
104             }
105             ErrorCode code = ErrorCode.valueOf(Index_h.clang_reparseTranslationUnit(
106                         tu,
107                         inMemoryFiles.length,
108                         files == null ? MemoryAddress.NULL : files,
109                         Index_h.clang_defaultReparseOptions(tu)));
110 
111             if (code != ErrorCode.Success) {
112                 throw new IllegalStateException("Re-parsing failed: " + code);
113             }
114         }
115     }
116 
117     public void reparse(Consumer<Diagnostic> dh, Index.UnsavedFile... inMemoryFiles) {
118         reparse(inMemoryFiles);
119         processDiagnostics(dh);
120     }
121 
122     public String[] tokens(SourceRange range) {
123         Tokens tokens = tokenize(range);
124         String rv[] = new String[tokens.size()];
125         for (int i = 0; i < rv.length; i++) {
126             rv[i] = tokens.getToken(i).spelling();
127         }
128         return rv;
129     }
130 
131     public Tokens tokenize(SourceRange range) {
132         try (ResourceScope scope = ResourceScope.newConfinedScope()) {
133             MemorySegment p = MemorySegment.allocateNative(C_POINTER, scope);
134             MemorySegment pCnt = MemorySegment.allocateNative(C_INT, scope);
135             Index_h.clang_tokenize(tu, range.range, p, pCnt);
136             Tokens rv = new Tokens(p.get(C_POINTER, 0), pCnt.get(C_INT, 0));
137             return rv;
138         }
139     }
140 
141     @Override
142     public void close() {
143         dispose();
144     }
145 
146     public void dispose() {
147         if (tu != MemoryAddress.NULL) {
148             Index_h.clang_disposeTranslationUnit(tu);
149             tu = MemoryAddress.NULL;
150         }
151     }
152 
153     public class Tokens {
154         private final MemoryAddress ar;
155         private final int size;
156 
157         Tokens(MemoryAddress ar, int size) {
158             this.ar = ar;
159             this.size = size;
160         }
161 
162         public void dispose() {
163             Index_h.clang_disposeTokens(tu, ar, size);
164         }
165 
166         public int size() {
167             return size;
168         }
169 
170         public MemorySegment getTokenSegment(int idx) {
171             MemoryAddress p = ar.addOffset(idx * CXToken.$LAYOUT().byteSize());
172             return MemorySegment.ofAddressNative(p, CXToken.$LAYOUT().byteSize(), ResourceScope.newConfinedScope());
173         }
174 
175         public Token getToken(int index) {
176             return new Token(getTokenSegment(index));
177         }
178 
179         @Override
180         public String toString() {
181             StringBuilder sb = new StringBuilder();
182             for (int i = 0; i < size; i++) {
183                 sb.append("Token[");
184                 sb.append(i);
185                 sb.append("]=");
186                 int pos = i;
187                 sb.append(LibClang.CXStrToString(allocator ->
188                         Index_h.clang_getTokenSpelling(allocator, tu, getTokenSegment(pos))));
189                 sb.append("\n");
190             }
191             return sb.toString();
192         }
193     }
194 
195     public class Token {
196         final MemorySegment token;
197 
198         Token(MemorySegment token) {
199             this.token = token;
200         }
201 
202         public int kind() {
203             return Index_h.clang_getTokenKind(token);
204         }
205 
206         public String spelling() {
207             return LibClang.CXStrToString(allocator ->
208                     Index_h.clang_getTokenSpelling(allocator, tu, token));
209         }
210 
211         public SourceLocation getLocation() {
212             return new SourceLocation(Index_h.clang_getTokenLocation(
213                 IMPLICIT_ALLOCATOR, tu, token));
214         }
215 
216         public SourceRange getExtent() {
217             return new SourceRange(Index_h.clang_getTokenExtent(IMPLICIT_ALLOCATOR,
218                     tu, token));
219         }
220     }
221 
222     public static class TranslationUnitSaveException extends IOException {
223 
224         static final long serialVersionUID = 1L;
225 
226         private final SaveError error;
227 
228         TranslationUnitSaveException(Path path, SaveError error) {
229             super("Cannot save translation unit to: " + path.toAbsolutePath() + ". Error: " + error);
230             this.error = error;
231         }
232     }
233 }