1 /*
  2  * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
  4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  5  *
  6  * This code is free software; you can redistribute it and/or modify it
  7  * under the terms of the GNU General Public License version 2 only, as
  8  * published by the Free Software Foundation.  Oracle designates this
  9  * particular file as subject to the "Classpath" exception as provided
 10  * by Oracle in the LICENSE file that accompanied this code.
 11  *
 12  * This code is distributed in the hope that it will be useful, but WITHOUT
 13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 15  * version 2 for more details (a copy is included in the LICENSE file that
 16  * accompanied this code).
 17  *
 18  * You should have received a copy of the GNU General Public License version
 19  * 2 along with this work; if not, write to the Free Software Foundation,
 20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 21  *
 22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 23  * or visit www.oracle.com if you need additional information or have any
 24  * questions.
 25  */
 26 package jdk.internal.classfile.impl;
 27 
 28 import java.lang.classfile.BufWriter;
 29 import java.lang.classfile.ClassModel;
 30 import java.lang.classfile.constantpool.ClassEntry;
 31 import java.lang.classfile.constantpool.ConstantPool;
 32 import java.lang.classfile.constantpool.ConstantPoolBuilder;
 33 import java.lang.classfile.constantpool.PoolEntry;
 34 import java.util.Arrays;
 35 import java.util.HashSet;
 36 
 37 import jdk.internal.access.JavaLangAccess;
 38 import jdk.internal.access.SharedSecrets;
 39 import jdk.internal.vm.annotation.ForceInline;
 40 
 41 import static java.lang.classfile.ClassFile.ACC_STATIC;
 42 import static java.lang.classfile.ClassFile.ACC_STRICT;
 43 import static java.lang.classfile.constantpool.PoolEntry.TAG_UTF8;
 44 import static jdk.internal.util.ModifiedUtf.putChar;
 45 import static jdk.internal.util.ModifiedUtf.utfLen;
 46 
 47 public final class BufWriterImpl implements BufWriter {
 48     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 49 
 50     private final ConstantPoolBuilder constantPool;
 51     private final ClassFileImpl context;
 52     private LabelContext labelContext;
 53     private WritableField.UnsetField[] strictInstanceFields; // do not modify array contents
 54     private ClassModel lastStrictCheckClass; // buf writer has short life, so do not need weak here
 55     private boolean lastStrictCheckResult;
 56     private final ClassEntry thisClass;
 57     private final int majorVersion;
 58     byte[] elems;
 59     int offset = 0;
 60 
 61     public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context) {
 62         this(constantPool, context, 64, null, 0);
 63     }
 64 
 65     public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context, int initialSize) {
 66         this(constantPool, context, initialSize, null, 0);
 67     }
 68 
 69     public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context, int initialSize, ClassEntry thisClass, int majorVersion) {
 70         this.constantPool = constantPool;
 71         this.context = context;
 72         elems = new byte[initialSize];
 73         this.thisClass = thisClass;
 74         this.majorVersion = majorVersion;
 75     }
 76 
 77     public boolean strictFieldsMatch(ClassModel cm) {
 78         // We have a cache because this check will be called multiple times
 79         // if a MethodModel is sent wholesale
 80         if (lastStrictCheckClass == cm) {
 81             return lastStrictCheckResult;
 82         }
 83 
 84         var result = doStrictFieldsMatchCheck(cm);
 85         lastStrictCheckClass = cm;
 86         lastStrictCheckResult = result;
 87         return result;
 88     }
 89 
 90     private boolean doStrictFieldsMatchCheck(ClassModel cm) {
 91         // TODO only check for preview class files?
 92         // UTF8 Entry can be used as equality objects
 93         var checks = new HashSet<>(Arrays.asList(getStrictInstanceFields()));
 94         for (var f : cm.fields()) {
 95             if ((f.flags().flagsMask() & (ACC_STATIC | ACC_STRICT)) == ACC_STRICT) {
 96                 if (!checks.remove(new WritableField.UnsetField(f.fieldName(), f.fieldType()))) {
 97                     return false; // Field mismatch!
 98                 }
 99             }
100         }
101         return checks.isEmpty();
102     }
103 
104     @Override
105     public ConstantPoolBuilder constantPool() {
106         return constantPool;
107     }
108 
109     public LabelContext labelContext() {
110         return labelContext;
111     }
112 
113     public void setLabelContext(LabelContext labelContext) {
114         this.labelContext = labelContext;
115     }
116 
117     public WritableField.UnsetField[] getStrictInstanceFields() {
118         assert strictInstanceFields != null : "should access only after setter call in DirectClassBuilder";
119         return strictInstanceFields;
120     }
121 
122     public void setStrictInstanceFields(WritableField.UnsetField[] strictInstanceFields) {
123         this.strictInstanceFields = strictInstanceFields;
124     }
125 
126     @Override
127     public boolean canWriteDirect(ConstantPool other) {
128         return constantPool.canWriteDirect(other);
129     }
130 
131     public ClassEntry thisClass() {
132         return thisClass;
133     }
134 
135     public int getMajorVersion() {
136         return majorVersion;
137     }
138 
139     public ClassFileImpl context() {
140         return context;
141     }
142 
143     @Override
144     public void writeU1(int x) {
145         reserveSpace(1);
146         elems[offset++] = (byte) x;
147     }
148 
149     @ForceInline
150     @Override
151     public void writeU2(int x) {
152         reserveSpace(2);
153         byte[] elems = this.elems;
154         int offset = this.offset;
155         elems[offset    ] = (byte) (x >> 8);
156         elems[offset + 1] = (byte) x;
157         this.offset = offset + 2;
158     }
159 
160     @ForceInline
161     public void writeU1U1(int x1, int x2) {
162         reserveSpace(2);
163         byte[] elems = this.elems;
164         int offset = this.offset;
165         elems[offset    ] = (byte) x1;
166         elems[offset + 1] = (byte) x2;
167         this.offset = offset + 2;
168     }
169 
170     public void writeU1U2(int u1, int u2) {
171         reserveSpace(3);
172         byte[] elems = this.elems;
173         int offset = this.offset;
174         elems[offset    ] = (byte) u1;
175         elems[offset + 1] = (byte) (u2 >> 8);
176         elems[offset + 2] = (byte) u2;
177         this.offset = offset + 3;
178     }
179 
180     public void writeU1U1U1(int x1, int x2, int x3) {
181         reserveSpace(3);
182         byte[] elems = this.elems;
183         int offset = this.offset;
184         elems[offset    ] = (byte) x1;
185         elems[offset + 1] = (byte) x2;
186         elems[offset + 2] = (byte) x3;
187         this.offset = offset + 3;
188     }
189 
190     public void writeU1U1U2(int x1, int x2, int x3) {
191         reserveSpace(4);
192         byte[] elems = this.elems;
193         int offset = this.offset;
194         elems[offset    ] = (byte) x1;
195         elems[offset + 1] = (byte) x2;
196         elems[offset + 2] = (byte) (x3 >> 8);
197         elems[offset + 3] = (byte) x3;
198         this.offset = offset + 4;
199     }
200 
201     public void writeU1U2U2(int x1, int x2, int x3) {
202         reserveSpace(5);
203         byte[] elems = this.elems;
204         int offset = this.offset;
205         elems[offset    ] = (byte) x1;
206         elems[offset + 1] = (byte) (x2 >> 8);
207         elems[offset + 2] = (byte) x2;
208         elems[offset + 3] = (byte) (x3 >> 8);
209         elems[offset + 4] = (byte) x3;
210         this.offset = offset + 5;
211     }
212 
213     public void writeU2U1(int x1, int x2) {
214         reserveSpace(3);
215         byte[] elems = this.elems;
216         int offset = this.offset;
217         elems[offset    ] = (byte) (x1 >> 8);
218         elems[offset + 1] = (byte) x1;
219         elems[offset + 2] = (byte) x2;
220         this.offset = offset + 3;
221     }
222 
223     public void writeU2U2(int x1, int x2) {
224         reserveSpace(4);
225         byte[] elems = this.elems;
226         int offset = this.offset;
227         elems[offset    ] = (byte) (x1 >> 8);
228         elems[offset + 1] = (byte) x1;
229         elems[offset + 2] = (byte) (x2 >> 8);
230         elems[offset + 3] = (byte) x2;
231         this.offset = offset + 4;
232     }
233 
234     public void writeU2U2U2(int x1, int x2, int x3) {
235         reserveSpace(6);
236         byte[] elems = this.elems;
237         int offset = this.offset;
238         elems[offset    ] = (byte) (x1 >> 8);
239         elems[offset + 1] = (byte) x1;
240         elems[offset + 2] = (byte) (x2 >> 8);
241         elems[offset + 3] = (byte) x2;
242         elems[offset + 4] = (byte) (x3 >> 8);
243         elems[offset + 5] = (byte) x3;
244         this.offset = offset + 6;
245     }
246 
247     @Override
248     public void writeInt(int x) {
249         reserveSpace(4);
250         byte[] elems = this.elems;
251         int offset = this.offset;
252         elems[offset    ] = (byte) (x >> 24);
253         elems[offset + 1] = (byte) (x >> 16);
254         elems[offset + 2] = (byte) (x >> 8);
255         elems[offset + 3] = (byte)  x;
256         this.offset = offset + 4;
257     }
258 
259     public void writeIntInt(int x1, int x2) {
260         reserveSpace(8);
261         byte[] elems = this.elems;
262         int offset = this.offset;
263         elems[offset    ] = (byte) (x1 >> 24);
264         elems[offset + 1] = (byte) (x1 >> 16);
265         elems[offset + 2] = (byte) (x1 >> 8);
266         elems[offset + 3] = (byte)  x1;
267         elems[offset + 4] = (byte) (x2 >> 24);
268         elems[offset + 5] = (byte) (x2 >> 16);
269         elems[offset + 6] = (byte) (x2 >> 8);
270         elems[offset + 7] = (byte)  x2;
271         this.offset = offset + 8;
272     }
273 
274     @Override
275     public void writeFloat(float x) {
276         writeInt(Float.floatToIntBits(x));
277     }
278 
279     @Override
280     public void writeLong(long x) {
281         reserveSpace(8);
282         byte[] elems = this.elems;
283         int offset = this.offset;
284         elems[offset    ] = (byte) (x >> 56);
285         elems[offset + 1] = (byte) (x >> 48);
286         elems[offset + 2] = (byte) (x >> 40);
287         elems[offset + 3] = (byte) (x >> 32);
288         elems[offset + 4] = (byte) (x >> 24);
289         elems[offset + 5] = (byte) (x >> 16);
290         elems[offset + 6] = (byte) (x >> 8);
291         elems[offset + 7] = (byte)  x;
292         this.offset = offset + 8;
293     }
294 
295     @Override
296     public void writeDouble(double x) {
297         writeLong(Double.doubleToLongBits(x));
298     }
299 
300     @Override
301     public void writeBytes(byte[] arr) {
302         writeBytes(arr, 0, arr.length);
303     }
304 
305     public void writeBytes(BufWriterImpl other) {
306         writeBytes(other.elems, 0, other.offset);
307     }
308 
309     @SuppressWarnings("deprecation")
310     void writeUtfEntry(String str) {
311         int strlen = str.length();
312         int countNonZeroAscii = JLA.countNonZeroAscii(str);
313         int utflen = utfLen(str, countNonZeroAscii);
314         if (utflen > 65535) {
315             throw new IllegalArgumentException("string too long");
316         }
317         reserveSpace(utflen + 3);
318 
319         int offset = this.offset;
320         byte[] elems = this.elems;
321 
322         elems[offset    ] = (byte) TAG_UTF8;
323         elems[offset + 1] = (byte) (utflen >> 8);
324         elems[offset + 2] = (byte)  utflen;
325         offset += 3;
326 
327         str.getBytes(0, countNonZeroAscii, elems, offset);
328         offset += countNonZeroAscii;
329 
330         for (int i = countNonZeroAscii; i < strlen; i++) {
331             offset = putChar(elems, offset, str.charAt(i));
332         }
333 
334         this.offset = offset;
335     }
336 
337     @Override
338     public void writeBytes(byte[] arr, int start, int length) {
339         reserveSpace(length);
340         System.arraycopy(arr, start, elems, offset, length);
341         offset += length;
342     }
343 
344     @Override
345     public void patchInt(int offset, int size, int value) {
346         int prevOffset = this.offset;
347         this.offset = offset;
348         writeIntBytes(size, value);
349         this.offset = prevOffset;
350     }
351 
352     public void patchU2(int offset, int x) {
353         byte[] elems = this.elems;
354         elems[offset    ] = (byte) (x >> 8);
355         elems[offset + 1] = (byte)  x;
356     }
357 
358     public void patchInt(int offset, int x) {
359         byte[] elems = this.elems;
360         elems[offset    ] = (byte) (x >> 24);
361         elems[offset + 1] = (byte) (x >> 16);
362         elems[offset + 2] = (byte) (x >> 8);
363         elems[offset + 3] = (byte)  x;
364     }
365 
366     @Override
367     public void writeIntBytes(int intSize, long intValue) {
368         reserveSpace(intSize);
369         for (int i = 0; i < intSize; i++) {
370             elems[offset++] = (byte) ((intValue >> 8 * (intSize - i - 1)) & 0xFF);
371         }
372     }
373 
374     /**
375      * Skip a few bytes in the output buffer. The skipped area has undefined value.
376      * @param bytes number of bytes to skip
377      * @return the index, for later patching
378      */
379     public int skip(int bytes) {
380         int now = offset;
381         reserveSpace(bytes);
382         offset += bytes;
383         return now;
384     }
385 
386     @Override
387     public void reserveSpace(int freeBytes) {
388         int minCapacity = offset + freeBytes;
389         if (minCapacity > elems.length) {
390             grow(minCapacity);
391         }
392     }
393 
394     private void grow(int minCapacity) {
395         int newsize = elems.length * 2;
396         while (minCapacity > newsize) {
397             newsize *= 2;
398         }
399         elems = Arrays.copyOf(elems, newsize);
400     }
401 
402     @Override
403     public int size() {
404         return offset;
405     }
406 
407     public RawBytecodeHelper.CodeRange bytecodeView() {
408         return RawBytecodeHelper.of(elems, offset);
409     }
410 
411     public void copyTo(byte[] array, int bufferOffset) {
412         System.arraycopy(elems, 0, array, bufferOffset, size());
413     }
414 
415     // writeIndex methods ensure that any CP info written
416     // is relative to the correct constant pool
417 
418     public int cpIndex(PoolEntry entry) {
419         int idx = AbstractPoolEntry.maybeClone(constantPool, entry).index();
420         if (idx < 1 || idx > Character.MAX_VALUE)
421             throw invalidIndex(idx, entry);
422         return idx;
423     }
424 
425     public int cpIndexOrZero(PoolEntry entry) {
426         if (entry == null || entry.index() == 0)
427             return 0;
428         return cpIndex(entry);
429     }
430 
431     @ForceInline
432     @Override
433     public void writeIndex(PoolEntry entry) {
434         writeU2(cpIndex(entry));
435     }
436 
437     public void writeIndex(int bytecode, PoolEntry entry) {
438         writeU1U2(bytecode, cpIndex(entry));
439     }
440 
441     static IllegalArgumentException invalidIndex(int idx, PoolEntry entry) {
442         return new IllegalArgumentException(idx + " is not a valid index. Entry: " + entry);
443     }
444 
445     @Override
446     public void writeIndexOrZero(PoolEntry entry) {
447         writeU2(cpIndexOrZero(entry));
448     }
449 
450     /**
451      * Join head and tail into an exact-size buffer
452      */
453     static byte[] join(BufWriterImpl head, BufWriterImpl tail) {
454         byte[] result = new byte[head.size() + tail.size()];
455         head.copyTo(result, 0);
456         tail.copyTo(result, head.size());
457         return result;
458     }
459 }