11/*
2- * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
2424 */
2525package jdk .internal .classfile .impl ;
2626
27- import java .nio .ByteBuffer ;
28- import static java .lang .classfile .ClassFile .ASTORE_3 ;
29- import static java .lang .classfile .ClassFile .ISTORE ;
30- import static java .lang .classfile .ClassFile .LOOKUPSWITCH ;
31- import static java .lang .classfile .ClassFile .TABLESWITCH ;
32- import static java .lang .classfile .ClassFile .WIDE ;
27+ import java .util .List ;
28+ import java .util .function .BiFunction ;
29+ import java .util .function .Function ;
30+
31+ import jdk .internal .misc .Unsafe ;
32+ import jdk .internal .util .Preconditions ;
33+ import jdk .internal .vm .annotation .Stable ;
34+
35+ import static java .lang .classfile .ClassFile .*;
3336
3437public final class RawBytecodeHelper {
3538
39+ public static final BiFunction <String , List <Number >, IllegalArgumentException >
40+ IAE_FORMATTER = Preconditions .outOfBoundsExceptionFormatter (new Function <>() {
41+ @ Override
42+ public IllegalArgumentException apply (String s ) {
43+ return new IllegalArgumentException (s );
44+ }
45+ });
46+
47+ public record CodeRange (byte [] array , int length ) {
48+ public RawBytecodeHelper start () {
49+ return new RawBytecodeHelper (this );
50+ }
51+ }
52+
3653 public static final int ILLEGAL = -1 ;
3754
38- private static final byte [] LENGTHS = new byte [] {
39- 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 2 , 3 , 2 , 3 , 3 , 2 | (4 << 4 ), 2 | (4 << 4 ), 2 | (4 << 4 ), 2 | (4 << 4 ), 2 | (4 << 4 ), 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
40- 2 | (4 << 4 ), 2 | (4 << 4 ), 2 | (4 << 4 ), 2 | (4 << 4 ), 2 | (4 << 4 ), 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
41- 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 3 | (6 << 4 ), 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 2 | (4 << 4 ), 0 , 0 , 1 , 1 , 1 ,
42- 1 , 1 , 1 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 5 , 5 , 3 , 2 , 3 , 1 , 1 , 3 , 3 , 1 , 1 , 0 , 4 , 3 , 3 , 5 , 5 , 0 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 1 , 4 , 4 , 4 , 2 , 4 , 3 , 3 , 0 , 0 , 1 , 3 , 2 , 3 , 3 , 3 , 1 , 2 , 1 ,
43- -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1
55+ /**
56+ * The length of opcodes, or -1 for no fixed length.
57+ * This is generated as if:
58+ * {@snippet lang=java :
59+ * var lengths = new byte[0x100];
60+ * Arrays.fill(lengths, (byte) -1);
61+ * for (var op : Opcode.values()) {
62+ * if (!op.isWide()) {
63+ * lengths[op.bytecode()] = (byte) op.sizeIfFixed();
64+ * }
65+ * }
66+ * }
67+ * Tested in UtilTest::testOpcodeLengthTable.
68+ */
69+ // Note: Consider distinguishing non-opcode and non-fixed-length opcode
70+ public static final @ Stable byte [] LENGTHS = new byte [] {
71+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
72+ 2 , 3 , 2 , 3 , 3 , 2 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 , 1 ,
73+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
74+ 1 , 1 , 1 , 1 , 1 , 1 , 2 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 ,
75+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
76+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
77+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
78+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
79+ 1 , 1 , 1 , 1 , 3 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
80+ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 3 , 3 , 3 , 3 , 3 , 3 , 3 ,
81+ 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 2 , -1 , -1 , 1 , 1 , 1 , 1 ,
82+ 1 , 1 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 5 , 5 , 3 , 2 , 3 , 1 , 1 ,
83+ 3 , 3 , 1 , 1 , -1 , 4 , 3 , 3 , 5 , 5 , -1 , -1 , -1 , -1 , -1 , -1 ,
84+ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ,
85+ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ,
86+ -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 , -1 ,
4487 };
4588
4689 public static boolean isStoreIntoLocal (int code ) {
@@ -51,121 +94,200 @@ public static int align(int n) {
5194 return (n + 3 ) & ~3 ;
5295 }
5396
54- private final ByteBuffer bytecode ;
55- public int bci , nextBci , endBci ;
56- public int rawCode ;
57- public boolean isWide ;
97+ private static final Unsafe UNSAFE = Unsafe .getUnsafe ();
98+ public final CodeRange code ;
99+ private int nextBci ;
100+ private int bci ;
101+ private int opcode ;
102+ private boolean isWide ;
58103
59- public RawBytecodeHelper (ByteBuffer bytecode ) {
60- this .bytecode = bytecode ;
61- this .bci = 0 ;
62- this .nextBci = 0 ;
63- this .endBci = bytecode .capacity ();
104+ public static CodeRange of (byte [] array ) {
105+ return new CodeRange (array , array .length );
64106 }
65107
66- public boolean isLastBytecode ( ) {
67- return nextBci >= endBci ;
108+ public static CodeRange of ( byte [] array , int limit ) {
109+ return new CodeRange ( array , limit ) ;
68110 }
69111
70- public int getShort ( int bci ) {
71- return bytecode . getShort ( bci ) ;
112+ private RawBytecodeHelper ( CodeRange range ) {
113+ this . code = range ;
72114 }
73115
74- public int dest () {
75- return bci + getShort (bci + 1 );
116+ // immutable states
117+
118+ /** {@return the end of the code array} */
119+ public int endBci () {
120+ return code .length ;
76121 }
77122
78- public int getInt (int bci ) {
79- return bytecode .getInt (bci );
123+ // setup
124+
125+ /**
126+ * Sets the starting bci for bytecode reading. Can be set to
127+ * {@link #endBci} to end scanning. Must be followed by a
128+ * {@link #next} before getter access.
129+ */
130+ public void reset (int nextBci ) {
131+ Preconditions .checkIndex (nextBci , endBci () + 1 , IAE_FORMATTER );
132+ this .nextBci = nextBci ;
80133 }
81134
82- public int destW () {
83- return bci + getInt (bci + 1 );
135+ // getters after transition
136+
137+ /**
138+ * Returns the current functional opcode, or {@link #ILLEGAL} if
139+ * the next instruction is invalid in format.
140+ * If this returns a valid opcode, that instruction's format must
141+ * be valid and can be accessed unchecked.
142+ */
143+ public int opcode () {
144+ return opcode ;
84145 }
85146
86- public int getIndexU1 () {
87- return bytecode .get (bci + 1 ) & 0xff ;
147+ /**
148+ * Returns whether the current functional opcode is in wide.
149+ */
150+ public boolean isWide () {
151+ return isWide ;
88152 }
89153
154+ /**
155+ * Returns the last validated instruction's index.
156+ */
157+ public int bci () {
158+ return bci ;
159+ }
160+
161+ // general utilities
162+
90163 public int getU1 (int bci ) {
91- return bytecode .get (bci ) & 0xff ;
164+ Preconditions .checkIndex (bci , endBci (), IAE_FORMATTER );
165+ return getU1Unchecked (bci );
92166 }
93167
94- public int rawNext (int jumpTo ) {
95- this . nextBci = jumpTo ;
96- return rawNext ( );
168+ public int getU2 (int bci ) {
169+ Preconditions . checkFromIndexSize ( bci , 2 , endBci (), IAE_FORMATTER ) ;
170+ return getU2Unchecked ( bci );
97171 }
98172
99- public int rawNext () {
100- bci = nextBci ;
101- int code = bytecode .get (bci ) & 0xff ;
102- int len = LENGTHS [code ] & 0xf ;
103- if (len > 0 && (bci <= endBci - len )) {
104- isWide = false ;
105- nextBci += len ;
106- if (nextBci <= bci ) {
107- code = ILLEGAL ;
108- }
109- rawCode = code ;
110- return code ;
111- } else {
112- len = switch (bytecode .get (bci ) & 0xff ) {
113- case WIDE -> {
114- if (bci + 1 >= endBci ) {
115- yield -1 ;
116- }
117- yield LENGTHS [bytecode .get (bci + 1 ) & 0xff ] >> 4 ;
118- }
119- case TABLESWITCH -> {
120- int aligned_bci = align (bci + 1 );
121- if (aligned_bci + 3 * 4 >= endBci ) {
122- yield -1 ;
123- }
124- int lo = bytecode .getInt (aligned_bci + 1 * 4 );
125- int hi = bytecode .getInt (aligned_bci + 2 * 4 );
126- int l = aligned_bci - bci + (3 + hi - lo + 1 ) * 4 ;
127- if (l > 0 ) yield l ; else yield -1 ;
128- }
129- case LOOKUPSWITCH -> {
130- int aligned_bci = align (bci + 1 );
131- if (aligned_bci + 2 * 4 >= endBci ) {
132- yield -1 ;
133- }
134- int npairs = bytecode .getInt (aligned_bci + 4 );
135- int l = aligned_bci - bci + (2 + 2 * npairs ) * 4 ;
136- if (l > 0 ) yield l ; else yield -1 ;
137- }
138- default ->
139- 0 ;
140- };
141- if (len <= 0 || (bci > endBci - len ) || (bci - len >= nextBci )) {
142- code = ILLEGAL ;
143- } else {
144- nextBci += len ;
145- isWide = false ;
146- if (code == WIDE ) {
147- if (bci + 1 >= endBci ) {
148- code = ILLEGAL ;
149- } else {
150- code = bytecode .get (bci + 1 ) & 0xff ;
151- isWide = true ;
152- }
153- }
154- }
155- rawCode = code ;
156- return code ;
157- }
173+ public int getShort (int bci ) {
174+ Preconditions .checkFromIndexSize (bci , 2 , endBci (), IAE_FORMATTER );
175+ return getShortUnchecked (bci );
176+ }
177+
178+ public int getInt (int bci ) {
179+ Preconditions .checkFromIndexSize (bci , 4 , endBci (), IAE_FORMATTER );
180+ return getIntUnchecked (bci );
181+ }
182+
183+ // Unchecked accessors: only if opcode() is validated
184+
185+ public int getU1Unchecked (int bci ) {
186+ return Byte .toUnsignedInt (code .array [bci ]);
187+ }
188+
189+ public int getU2Unchecked (int bci ) {
190+ return UNSAFE .getCharUnaligned (code .array , (long ) Unsafe .ARRAY_BYTE_BASE_OFFSET + bci , true );
158191 }
159192
193+ public int getShortUnchecked (int bci ) {
194+ return UNSAFE .getShortUnaligned (code .array , (long ) Unsafe .ARRAY_BYTE_BASE_OFFSET + bci , true );
195+ }
196+
197+ // used after switch validation
198+ public int getIntUnchecked (int bci ) {
199+ return UNSAFE .getIntUnaligned (code .array , (long ) Unsafe .ARRAY_BYTE_BASE_OFFSET + bci , true );
200+ }
201+
202+ // non-wide branches
203+ public int dest () {
204+ return bci + getShortUnchecked (bci + 1 );
205+ }
206+
207+ // goto_w and jsr_w
208+ public int destW () {
209+ return bci + getIntUnchecked (bci + 1 );
210+ }
211+
212+ // *load, *store, iinc
160213 public int getIndex () {
161- return ( isWide ) ? getIndexU2Raw (bci + 2 ) : getIndexU1 ();
214+ return isWide ? getU2Unchecked (bci + 2 ) : getIndexU1 ();
162215 }
163216
217+ // ldc
218+ public int getIndexU1 () {
219+ return getU1Unchecked (bci + 1 );
220+ }
221+
222+ // usually cp entry index
164223 public int getIndexU2 () {
165- return getIndexU2Raw (bci + 1 );
224+ return getU2Unchecked (bci + 1 );
166225 }
167226
168- public int getIndexU2Raw (int bci ) {
169- return bytecode .getShort (bci ) & 0xffff ;
227+ // Transition methods
228+
229+ /**
230+ * Transitions to the next instruction and returns whether scanning should
231+ * continue. If the next instruction is malformed, {@link #opcode()} returns
232+ * {@link #ILLEGAL}, so we can perform value access without bound checks if
233+ * we have a valid opcode.
234+ */
235+ public boolean next () {
236+ var bci = nextBci ;
237+ var end = endBci ();
238+ if (bci >= end ) {
239+ return false ;
240+ }
241+
242+ int code = getU1Unchecked (bci );
243+ int len = LENGTHS [code & 0xFF ]; // & 0xFF eliminates bound check
244+ this .bci = bci ;
245+ opcode = code ;
246+ isWide = false ;
247+ if (len <= 0 ) {
248+ len = checkSpecialInstruction (bci , end , code ); // sets opcode
249+ }
250+
251+ if (len <= 0 || (nextBci += len ) > end ) {
252+ opcode = ILLEGAL ;
253+ }
254+
255+ return true ;
256+ }
257+
258+ // Put rarely used code in another method to reduce code size
259+ private int checkSpecialInstruction (int bci , int end , int code ) {
260+ if (code == WIDE ) {
261+ if (bci + 1 >= end ) {
262+ return -1 ;
263+ }
264+ opcode = code = getIndexU1 ();
265+ isWide = true ;
266+ // Validated in UtilTest.testOpcodeLengthTable
267+ return LENGTHS [code ] * 2 ;
268+ }
269+ if (code == TABLESWITCH ) {
270+ int alignedBci = align (bci + 1 );
271+ if (alignedBci + 3 * 4 >= end ) {
272+ return -1 ;
273+ }
274+ int lo = getIntUnchecked (alignedBci + 1 * 4 );
275+ int hi = getIntUnchecked (alignedBci + 2 * 4 );
276+ long l = alignedBci - bci + (3L + (long ) hi - lo + 1L ) * 4L ;
277+ return l > 0 && ((int ) l == l ) ? (int ) l : -1 ;
278+ }
279+ if (code == LOOKUPSWITCH ) {
280+ int alignedBci = align (bci + 1 );
281+ if (alignedBci + 2 * 4 >= end ) {
282+ return -1 ;
283+ }
284+ int npairs = getIntUnchecked (alignedBci + 4 );
285+ if (npairs < 0 ) {
286+ return -1 ;
287+ }
288+ long l = alignedBci - bci + (2L + 2L * npairs ) * 4L ;
289+ return l > 0 && ((int ) l == l ) ? (int ) l : -1 ;
290+ }
291+ return -1 ;
170292 }
171293}
0 commit comments