diff --git a/tests/test_data/std/jdk/internal/ValueBased.class b/tests/test_data/std/jdk/internal/ValueBased.class new file mode 100644 index 00000000..3c220a66 Binary files /dev/null and b/tests/test_data/std/jdk/internal/ValueBased.class differ diff --git a/tests/test_data/std/jdk/internal/ValueBased.java b/tests/test_data/std/jdk/internal/ValueBased.java new file mode 100644 index 00000000..d27ab73c --- /dev/null +++ b/tests/test_data/std/jdk/internal/ValueBased.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; + +/** + * Indicates the API declaration in question is associated with a Value Based class. + * References to value-based classes + * should produce warnings about behavior that is inconsistent with value based semantics. + * + * Note this internal annotation is handled specially by the javac compiler. + * To work properly with {@code --release older-release}, it requires special + * handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java} + * and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}. + * + * @since 16 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value={TYPE}) +public @interface ValueBased { +} + diff --git a/tests/test_data/std/jdk/internal/access/JavaAWTAccess.class b/tests/test_data/std/jdk/internal/access/JavaAWTAccess.class new file mode 100644 index 00000000..bf95dad1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaAWTAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaAWTAccess.java b/tests/test_data/std/jdk/internal/access/JavaAWTAccess.java new file mode 100644 index 00000000..a4c170ca --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaAWTAccess.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +public interface JavaAWTAccess { + + // Returns the AppContext used for applet logging isolation, or null if + // no isolation is required. + // If there's no applet, or if the caller is a stand alone application, + // or running in the main app context, returns null. + // Otherwise, returns the AppContext of the calling applet. + public Object getAppletContext(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaAWTFontAccess.class b/tests/test_data/std/jdk/internal/access/JavaAWTFontAccess.class new file mode 100644 index 00000000..c94de1d7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaAWTFontAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaAWTFontAccess.java b/tests/test_data/std/jdk/internal/access/JavaAWTFontAccess.java new file mode 100644 index 00000000..139f0245 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaAWTFontAccess.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +/** + * SharedSecrets interface used for the access from java.text.Bidi + */ + +public interface JavaAWTFontAccess { + + // java.awt.font.TextAttribute constants + public Object getTextAttributeConstant(String name); + + // java.awt.font.NumericShaper + public void shape(Object shaper, char[] text, int start, int count); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaBeansAccess.class b/tests/test_data/std/jdk/internal/access/JavaBeansAccess.class new file mode 100644 index 00000000..8ede588e Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaBeansAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaBeansAccess.java b/tests/test_data/std/jdk/internal/access/JavaBeansAccess.java new file mode 100644 index 00000000..bcde7323 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaBeansAccess.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public interface JavaBeansAccess { + /** + * Returns the getter method for a property of the given name + * @param clazz The JavaBeans class + * @param property The property name + * @return The resolved property getter method + * @throws Exception + */ + Method getReadMethod(Class clazz, String property) throws Exception; + + /** + * Return the value attribute of the associated + * @ConstructorProperties annotation if that is present. + * @param ctr The constructor to extract the annotation value from + * @return The {@code value} attribute of the @ConstructorProperties + * annotation or {@code null} if the constructor is not annotated by + * this annotation or the annotation is not accessible. + */ + String[] getConstructorPropertiesValue(Constructor ctr); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaIOAccess.class b/tests/test_data/std/jdk/internal/access/JavaIOAccess.class new file mode 100644 index 00000000..3647ab4f Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaIOAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaIOAccess.java b/tests/test_data/std/jdk/internal/access/JavaIOAccess.java new file mode 100644 index 00000000..532c1f25 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaIOAccess.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.Console; + +public interface JavaIOAccess { + Console console(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaIOFileDescriptorAccess.class b/tests/test_data/std/jdk/internal/access/JavaIOFileDescriptorAccess.class new file mode 100644 index 00000000..4ae3aded Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaIOFileDescriptorAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaIOFileDescriptorAccess.java b/tests/test_data/std/jdk/internal/access/JavaIOFileDescriptorAccess.java new file mode 100644 index 00000000..5e627abc --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaIOFileDescriptorAccess.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import java.io.FileDescriptor; +import java.io.IOException; + +import jdk.internal.ref.PhantomCleanable; + +/* + * @author Chris Hegarty + */ + +public interface JavaIOFileDescriptorAccess { + public void set(FileDescriptor fdo, int fd); + public int get(FileDescriptor fdo); + public void setAppend(FileDescriptor fdo, boolean append); + public boolean getAppend(FileDescriptor fdo); + public void close(FileDescriptor fdo) throws IOException; + public void registerCleanup(FileDescriptor fdo); + public void registerCleanup(FileDescriptor fdo, PhantomCleanable cleanable); + public void unregisterCleanup(FileDescriptor fdo); + + // Only valid on Windows + public void setHandle(FileDescriptor fdo, long handle); + public long getHandle(FileDescriptor fdo); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaIOFilePermissionAccess.class b/tests/test_data/std/jdk/internal/access/JavaIOFilePermissionAccess.class new file mode 100644 index 00000000..3311e266 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaIOFilePermissionAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaIOFilePermissionAccess.java b/tests/test_data/std/jdk/internal/access/JavaIOFilePermissionAccess.java new file mode 100644 index 00000000..48a80ec7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaIOFilePermissionAccess.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import java.io.FilePermission; + +public interface JavaIOFilePermissionAccess { + + /** + * Returns a new FilePermission plus an alternative path. + * + * @param input the input + * @return the new FilePermission plus the alt path (as npath2) + * or the input itself if no alt path is available. + */ + FilePermission newPermPlusAltPath(FilePermission input); + + /** + * Returns a new FilePermission using an alternative path. + * + * @param input the input + * @return the new FilePermission using the alt path (as npath) + * or null if no alt path is available + */ + FilePermission newPermUsingAltPath(FilePermission input); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaIOPrintStreamAccess.class b/tests/test_data/std/jdk/internal/access/JavaIOPrintStreamAccess.class new file mode 100644 index 00000000..20538b61 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaIOPrintStreamAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaIOPrintStreamAccess.java b/tests/test_data/std/jdk/internal/access/JavaIOPrintStreamAccess.java new file mode 100644 index 00000000..ec205e27 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaIOPrintStreamAccess.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import java.io.PrintStream; + +public interface JavaIOPrintStreamAccess { + Object lock(PrintStream ps); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaIOPrintWriterAccess.class b/tests/test_data/std/jdk/internal/access/JavaIOPrintWriterAccess.class new file mode 100644 index 00000000..c7f9370d Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaIOPrintWriterAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaIOPrintWriterAccess.java b/tests/test_data/std/jdk/internal/access/JavaIOPrintWriterAccess.java new file mode 100644 index 00000000..8be54a76 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaIOPrintWriterAccess.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import java.io.PrintWriter; + +public interface JavaIOPrintWriterAccess { + Object lock(PrintWriter pw); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaIORandomAccessFileAccess.class b/tests/test_data/std/jdk/internal/access/JavaIORandomAccessFileAccess.class new file mode 100644 index 00000000..a78ccedf Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaIORandomAccessFileAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaIORandomAccessFileAccess.java b/tests/test_data/std/jdk/internal/access/JavaIORandomAccessFileAccess.java new file mode 100644 index 00000000..f6c59908 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaIORandomAccessFileAccess.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +public interface JavaIORandomAccessFileAccess { + public RandomAccessFile openAndDelete(File file, String mode) + throws IOException; +} diff --git a/tests/test_data/std/jdk/internal/access/JavaLangAccess.class b/tests/test_data/std/jdk/internal/access/JavaLangAccess.class new file mode 100644 index 00000000..0bc0dcae Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaLangAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaLangAccess.java b/tests/test_data/std/jdk/internal/access/JavaLangAccess.java new file mode 100644 index 00000000..c31e745c --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaLangAccess.java @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.annotation.Annotation; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.lang.module.ModuleDescriptor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.net.URI; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.security.AccessControlContext; +import java.security.ProtectionDomain; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.RejectedExecutionException; +import java.util.stream.Stream; + +import jdk.internal.misc.CarrierThreadLocal; +import jdk.internal.module.ServicesCatalog; +import jdk.internal.reflect.ConstantPool; +import jdk.internal.vm.Continuation; +import jdk.internal.vm.ContinuationScope; +import jdk.internal.vm.StackableScope; +import jdk.internal.vm.ThreadContainer; +import sun.reflect.annotation.AnnotationType; +import sun.nio.ch.Interruptible; + +public interface JavaLangAccess { + + /** + * Returns the list of {@code Method} objects for the declared public + * methods of this class or interface that have the specified method name + * and parameter types. + */ + List getDeclaredPublicMethods(Class klass, String name, Class... parameterTypes); + + /** + * Return most specific method that matches name and parameterTypes. + */ + Method findMethod(Class klass, boolean publicOnly, String name, Class... parameterTypes); + + /** + * Return the constant pool for a class. + */ + ConstantPool getConstantPool(Class klass); + + /** + * Compare-And-Set the AnnotationType instance corresponding to this class. + * (This method only applies to annotation types.) + */ + boolean casAnnotationType(Class klass, AnnotationType oldType, AnnotationType newType); + + /** + * Get the AnnotationType instance corresponding to this class. + * (This method only applies to annotation types.) + */ + AnnotationType getAnnotationType(Class klass); + + /** + * Get the declared annotations for a given class, indexed by their types. + */ + Map, Annotation> getDeclaredAnnotationMap(Class klass); + + /** + * Get the array of bytes that is the class-file representation + * of this Class' annotations. + */ + byte[] getRawClassAnnotations(Class klass); + + /** + * Get the array of bytes that is the class-file representation + * of this Class' type annotations. + */ + byte[] getRawClassTypeAnnotations(Class klass); + + /** + * Get the array of bytes that is the class-file representation + * of this Executable's type annotations. + */ + byte[] getRawExecutableTypeAnnotations(Executable executable); + + /** + * Returns the elements of an enum class or null if the + * Class object does not represent an enum type; + * the result is uncloned, cached, and shared by all callers. + */ + > E[] getEnumConstantsShared(Class klass); + + /** + * Set current thread's blocker field. + */ + void blockedOn(Interruptible b); + + /** + * Registers a shutdown hook. + * + * It is expected that this method with registerShutdownInProgress=true + * is only used to register DeleteOnExitHook since the first file + * may be added to the delete on exit list by the application shutdown + * hooks. + * + * @param slot the slot in the shutdown hook array, whose element + * will be invoked in order during shutdown + * @param registerShutdownInProgress true to allow the hook + * to be registered even if the shutdown is in progress. + * @param hook the hook to be registered + * + * @throws IllegalStateException if shutdown is in progress and + * the slot is not valid to register. + */ + void registerShutdownHook(int slot, boolean registerShutdownInProgress, Runnable hook); + + /** + * Returns a new Thread with the given Runnable and an + * inherited AccessControlContext. + */ + Thread newThreadWithAcc(Runnable target, @SuppressWarnings("removal") AccessControlContext acc); + + /** + * Invokes the finalize method of the given object. + */ + void invokeFinalize(Object o) throws Throwable; + + /** + * Returns the ConcurrentHashMap used as a storage for ClassLoaderValue(s) + * associated with the given class loader, creating it if it doesn't already exist. + */ + ConcurrentHashMap createOrGetClassLoaderValueMap(ClassLoader cl); + + /** + * Defines a class with the given name to a class loader. + */ + Class defineClass(ClassLoader cl, String name, byte[] b, ProtectionDomain pd, String source); + + /** + * Defines a class with the given name to a class loader with + * the given flags and class data. + * + * @see java.lang.invoke.MethodHandles.Lookup#defineClass + */ + Class defineClass(ClassLoader cl, Class lookup, String name, byte[] b, ProtectionDomain pd, boolean initialize, int flags, Object classData); + + /** + * Returns a class loaded by the bootstrap class loader. + */ + Class findBootstrapClassOrNull(String name); + + /** + * Define a Package of the given name and module by the given class loader. + */ + Package definePackage(ClassLoader cl, String name, Module module); + + /** + * Record the non-exported packages of the modules in the given layer + */ + void addNonExportedPackages(ModuleLayer layer); + + /** + * Invalidate package access cache + */ + void invalidatePackageAccessCache(); + + /** + * Defines a new module to the Java virtual machine. The module + * is defined to the given class loader. + * + * The URI is for information purposes only, it can be {@code null}. + */ + Module defineModule(ClassLoader loader, ModuleDescriptor descriptor, URI uri); + + /** + * Defines the unnamed module for the given class loader. + */ + Module defineUnnamedModule(ClassLoader loader); + + /** + * Updates the readability so that module m1 reads m2. The new read edge + * does not result in a strong reference to m2 (m2 can be GC'ed). + * + * This method is the same as m1.addReads(m2) but without a permission check. + */ + void addReads(Module m1, Module m2); + + /** + * Updates module m to read all unnamed modules. + */ + void addReadsAllUnnamed(Module m); + + /** + * Updates module m1 to export a package unconditionally. + */ + void addExports(Module m1, String pkg); + + /** + * Updates module m1 to export a package to module m2. The export does + * not result in a strong reference to m2 (m2 can be GC'ed). + */ + void addExports(Module m1, String pkg, Module m2); + + /** + * Updates a module m to export a package to all unnamed modules. + */ + void addExportsToAllUnnamed(Module m, String pkg); + + /** + * Updates module m1 to open a package to module m2. Opening the + * package does not result in a strong reference to m2 (m2 can be GC'ed). + */ + void addOpens(Module m1, String pkg, Module m2); + + /** + * Updates module m to open a package to all unnamed modules. + */ + void addOpensToAllUnnamed(Module m, String pkg); + + /** + * Updates module m to open all packages in the given sets. + */ + void addOpensToAllUnnamed(Module m, Set concealedPkgs, Set exportedPkgs); + + /** + * Updates module m to use a service. + */ + void addUses(Module m, Class service); + + /** + * Returns true if module m reflectively exports a package to other + */ + boolean isReflectivelyExported(Module module, String pn, Module other); + + /** + * Returns true if module m reflectively opens a package to other + */ + boolean isReflectivelyOpened(Module module, String pn, Module other); + + /** + * Updates module m to allow access to restricted methods. + */ + Module addEnableNativeAccess(Module m); + + /** + * Updates module named {@code name} in layer {@code layer} to allow access to restricted methods. + * Returns true iff the given module exists in the given layer. + */ + boolean addEnableNativeAccess(ModuleLayer layer, String name); + + /** + * Updates all unnamed modules to allow access to restricted methods. + */ + void addEnableNativeAccessToAllUnnamed(); + + /** + * Ensure that the given module has native access. If not, warn or + * throw exception depending on the configuration. + */ + void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass); + + /** + * Returns the ServicesCatalog for the given Layer. + */ + ServicesCatalog getServicesCatalog(ModuleLayer layer); + + /** + * Record that this layer has at least one module defined to the given + * class loader. + */ + void bindToLoader(ModuleLayer layer, ClassLoader loader); + + /** + * Returns an ordered stream of layers. The first element is the + * given layer, the remaining elements are its parents, in DFS order. + */ + Stream layers(ModuleLayer layer); + + /** + * Returns a stream of the layers that have modules defined to the + * given class loader. + */ + Stream layers(ClassLoader loader); + + /** + * Count the number of leading positive bytes in the range. + */ + int countPositives(byte[] ba, int off, int len); + + /** + * Constructs a new {@code String} by decoding the specified subarray of + * bytes using the specified {@linkplain java.nio.charset.Charset charset}. + * + * The caller of this method shall relinquish and transfer the ownership of + * the byte array to the callee since the later will not make a copy. + * + * @param bytes the byte array source + * @param cs the Charset + * @return the newly created string + * @throws CharacterCodingException for malformed or unmappable bytes + */ + String newStringNoRepl(byte[] bytes, Charset cs) throws CharacterCodingException; + + /** + * Encode the given string into a sequence of bytes using the specified Charset. + * + * This method avoids copying the String's internal representation if the input + * is ASCII. + * + * This method throws CharacterCodingException instead of replacing when + * malformed input or unmappable characters are encountered. + * + * @param s the string to encode + * @param cs the charset + * @return the encoded bytes + * @throws CharacterCodingException for malformed input or unmappable characters + */ + byte[] getBytesNoRepl(String s, Charset cs) throws CharacterCodingException; + + /** + * Returns a new string by decoding from the given utf8 bytes array. + * + * @param off the index of the first byte to decode + * @param len the number of bytes to decode + * @return the newly created string + * @throws IllegalArgumentException for malformed or unmappable bytes. + */ + String newStringUTF8NoRepl(byte[] bytes, int off, int len); + + /** + * Get the char at index in a byte[] in internal UTF-16 representation, + * with no bounds checks. + * + * @param bytes the UTF-16 encoded bytes + * @param index of the char to retrieve, 0 <= index < (bytes.length >> 1) + * @return the char value + */ + char getUTF16Char(byte[] bytes, int index); + + /** + * Put the char at index in a byte[] in internal UTF-16 representation, + * with no bounds checks. + * + * @param bytes the UTF-16 encoded bytes + * @param index of the char to retrieve, 0 <= index < (bytes.length >> 1) + */ + void putCharUTF16(byte[] bytes, int index, int ch); + + /** + * Encode the given string into a sequence of bytes using utf8. + * + * @param s the string to encode + * @return the encoded bytes in utf8 + * @throws IllegalArgumentException for malformed surrogates + */ + byte[] getBytesUTF8NoRepl(String s); + + /** + * Inflated copy from byte[] to char[], as defined by StringLatin1.inflate + */ + void inflateBytesToChars(byte[] src, int srcOff, char[] dst, int dstOff, int len); + + /** + * Decodes ASCII from the source byte array into the destination + * char array. + * + * @return the number of bytes successfully decoded, at most len + */ + int decodeASCII(byte[] src, int srcOff, char[] dst, int dstOff, int len); + + /** + * Returns the initial `System.in` to determine if it is replaced + * with `System.setIn(newIn)` method + */ + InputStream initialSystemIn(); + + /** + * Returns the initial value of System.err. + */ + PrintStream initialSystemErr(); + + /** + * Encodes ASCII codepoints as possible from the source array into + * the destination byte array, assuming that the encoding is ASCII + * compatible + * + * @return the number of bytes successfully encoded, or 0 if none + */ + int encodeASCII(char[] src, int srcOff, byte[] dst, int dstOff, int len); + + /** + * Set the cause of Throwable + * @param cause set t's cause to new value + */ + void setCause(Throwable t, Throwable cause); + + /** + * Get protection domain of the given Class + */ + ProtectionDomain protectionDomain(Class c); + + /** + * Get a method handle of string concat helper method + */ + MethodHandle stringConcatHelper(String name, MethodType methodType); + + /** + * Prepends constant and the stringly representation of value into buffer, + * given the coder and final index. Index is measured in chars, not in bytes! + */ + long stringConcatHelperPrepend(long indexCoder, byte[] buf, String value); + + /** + * Get the string concat initial coder + */ + long stringConcatInitialCoder(); + + /** + * Update lengthCoder for constant + */ + long stringConcatMix(long lengthCoder, String constant); + + /** + * Mix value length and coder into current length and coder. + */ + long stringConcatMix(long lengthCoder, char value); + + /** + * Join strings + */ + String join(String prefix, String suffix, String delimiter, String[] elements, int size); + + /* + * Get the class data associated with the given class. + * @param c the class + * @see java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) + */ + Object classData(Class c); + + int stringSize(long i); + + int getCharsLatin1(long i, int index, byte[] buf); + + int getCharsUTF16(long i, int index, byte[] buf); + + long findNative(ClassLoader loader, String entry); + + /** + * Direct access to Shutdown.exit to avoid security manager checks + * @param statusCode the status code + */ + void exit(int statusCode); + + /** + * Returns an array of all platform threads. + */ + Thread[] getAllThreads(); + + /** + * Returns the ThreadContainer for a thread, may be null. + */ + ThreadContainer threadContainer(Thread thread); + + /** + * Starts a thread in the given ThreadContainer. + */ + void start(Thread thread, ThreadContainer container); + + /** + * Returns the top of the given thread's stackable scope stack. + */ + StackableScope headStackableScope(Thread thread); + + /** + * Sets the top of the current thread's stackable scope stack. + */ + void setHeadStackableScope(StackableScope scope); + + /** + * Returns the Thread object for the current platform thread. If the + * current thread is a virtual thread then this method returns the carrier. + */ + Thread currentCarrierThread(); + + /** + * Executes the given value returning task on the current carrier thread. + */ + V executeOnCarrierThread(Callable task) throws Exception; + + /** + * Returns the value of the current carrier thread's copy of a thread-local. + */ + T getCarrierThreadLocal(CarrierThreadLocal local); + + /** + * Sets the value of the current carrier thread's copy of a thread-local. + */ + void setCarrierThreadLocal(CarrierThreadLocal local, T value); + + /** + * Removes the value of the current carrier thread's copy of a thread-local. + */ + void removeCarrierThreadLocal(CarrierThreadLocal local); + + /** + * Returns {@code true} if there is a value in the current carrier thread's copy of + * thread-local, even if that values is {@code null}. + */ + boolean isCarrierThreadLocalPresent(CarrierThreadLocal local); + + /** + * Returns the current thread's scoped values cache + */ + Object[] scopedValueCache(); + + /** + * Sets the current thread's scoped values cache + */ + void setScopedValueCache(Object[] cache); + + /** + * Return the current thread's scoped value bindings. + */ + Object scopedValueBindings(); + + /** + * Returns the innermost mounted continuation + */ + Continuation getContinuation(Thread thread); + + /** + * Sets the innermost mounted continuation + */ + void setContinuation(Thread thread, Continuation continuation); + + /** + * The ContinuationScope of virtual thread continuations + */ + ContinuationScope virtualThreadContinuationScope(); + + /** + * Parks the current virtual thread. + * @throws WrongThreadException if the current thread is not a virtual thread + */ + void parkVirtualThread(); + + /** + * Parks the current virtual thread for up to the given waiting time. + * @param nanos the maximum number of nanoseconds to wait + * @throws WrongThreadException if the current thread is not a virtual thread + */ + void parkVirtualThread(long nanos); + + /** + * Re-enables a virtual thread for scheduling. If the thread was parked then + * it will be unblocked, otherwise its next attempt to park will not block + * @param thread the virtual thread to unpark + * @throws IllegalArgumentException if the thread is not a virtual thread + * @throws RejectedExecutionException if the scheduler cannot accept a task + */ + void unparkVirtualThread(Thread thread); + + /** + * Creates a new StackWalker + */ + StackWalker newStackWalkerInstance(Set options, + ContinuationScope contScope, + Continuation continuation); + /** + * Returns '' @ if classloader has a name + * explicitly set otherwise @ + */ + String getLoaderNameID(ClassLoader loader); + + /** + * Copy the string bytes to an existing segment, avoiding intermediate copies. + */ + void copyToSegmentRaw(String string, MemorySegment segment, long offset); + + /** + * Are the string bytes compatible with the given charset? + */ + boolean bytesCompatible(String string, Charset charset); + + /** + * Is a security manager already set or allowed to be set + * (using -Djava.security.manager=allow)? + */ + boolean allowSecurityManager(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaLangInvokeAccess.class b/tests/test_data/std/jdk/internal/access/JavaLangInvokeAccess.class new file mode 100644 index 00000000..d50bc259 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaLangInvokeAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaLangInvokeAccess.java b/tests/test_data/std/jdk/internal/access/JavaLangInvokeAccess.java new file mode 100644 index 00000000..56387038 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaLangInvokeAccess.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import jdk.internal.foreign.abi.NativeEntryPoint; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.nio.ByteOrder; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +public interface JavaLangInvokeAccess { + /** + * Returns the declaring class for the given ResolvedMethodName. + * Used by {@code StackFrameInfo}. + */ + Class getDeclaringClass(Object rmname); + + /** + * Returns the {@code MethodType} for the given method descriptor + * and class loader. + * Used by {@code StackFrameInfo}. + */ + MethodType getMethodType(String descriptor, ClassLoader loader); + + /** + * Returns true if the given flags has MN_CALLER_SENSITIVE flag set. + */ + boolean isCallerSensitive(int flags); + + /** + * Returns true if the given flags has MN_HIDDEN_MEMBER flag set. + */ + boolean isHiddenMember(int flags); + + /** + * Returns a map of class name in internal forms to its corresponding + * class bytes per the given stream of LF_RESOLVE and SPECIES_RESOLVE + * trace logs. Used by GenerateJLIClassesPlugin to enable generation + * of such classes during the jlink phase. + */ + Map generateHolderClasses(Stream traces); + + /** + * Returns a var handle view of a given memory segment. + * Used by {@code jdk.internal.foreign.LayoutPath} and + * {@code java.lang.invoke.MethodHandles}. + */ + VarHandle memorySegmentViewHandle(Class carrier, long alignmentMask, ByteOrder order); + + /** + * Var handle carrier combinator. + * Used by {@code java.lang.invoke.MethodHandles}. + */ + VarHandle filterValue(VarHandle target, MethodHandle filterToTarget, MethodHandle filterFromTarget); + + /** + * Var handle filter coordinates combinator. + * Used by {@code java.lang.invoke.MethodHandles}. + */ + VarHandle filterCoordinates(VarHandle target, int pos, MethodHandle... filters); + + /** + * Var handle drop coordinates combinator. + * Used by {@code java.lang.invoke.MethodHandles}. + */ + VarHandle dropCoordinates(VarHandle target, int pos, Class... valueTypes); + + /** + * Var handle permute coordinates combinator. + * Used by {@code java.lang.invoke.MethodHandles}. + */ + VarHandle permuteCoordinates(VarHandle target, List> newCoordinates, int... reorder); + + /** + * Var handle collect coordinates combinator. + * Used by {@code java.lang.invoke.MethodHandles}. + */ + VarHandle collectCoordinates(VarHandle target, int pos, MethodHandle filter); + + /** + * Var handle insert coordinates combinator. + * Used by {@code java.lang.invoke.MethodHandles}. + */ + VarHandle insertCoordinates(VarHandle target, int pos, Object... values); + + /** + * Returns a native method handle with given arguments as fallback and steering info. + * + * Will allow JIT to intrinsify. + * + * @param nep the native entry point + * @return the native method handle + */ + MethodHandle nativeMethodHandle(NativeEntryPoint nep); + + /** + * Produces a method handle unreflecting from a {@code Constructor} with + * the trusted lookup + */ + MethodHandle unreflectConstructor(Constructor ctor) throws IllegalAccessException; + + /** + * Produces a method handle unreflecting from a {@code Field} with + * the trusted lookup + */ + MethodHandle unreflectField(Field field, boolean isSetter) throws IllegalAccessException; + + /** + * Produces a method handle of a virtual method with the trusted lookup. + */ + MethodHandle findVirtual(Class defc, String name, MethodType type) throws IllegalAccessException; + + /** + * Produces a method handle of a static method with the trusted lookup. + */ + MethodHandle findStatic(Class defc, String name, MethodType type) throws IllegalAccessException; + + /** + * Returns a method handle of an invoker class injected for core reflection + * implementation with the following signature: + * reflect_invoke_V(MethodHandle mh, Object target, Object[] args) + * + * The invoker class is a hidden class which has the same + * defining class loader, runtime package, and protection domain + * as the given caller class. + */ + MethodHandle reflectiveInvoker(Class caller); + + /** + * A best-effort method that tries to find any exceptions thrown by the given method handle. + * @param handle the handle to check + * @return an array of exceptions, or {@code null}. + */ + Class[] exceptionTypes(MethodHandle handle); + + /** + * Returns a method handle that allocates an instance of the given class + * and then invoke the given constructor of one of its superclasses. + * + * This method should only be used by ReflectionFactory::newConstructorForSerialization. + */ + MethodHandle serializableConstructor(Class decl, Constructor ctorToCall) throws IllegalAccessException; +} diff --git a/tests/test_data/std/jdk/internal/access/JavaLangModuleAccess.class b/tests/test_data/std/jdk/internal/access/JavaLangModuleAccess.class new file mode 100644 index 00000000..a7068213 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaLangModuleAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaLangModuleAccess.java b/tests/test_data/std/jdk/internal/access/JavaLangModuleAccess.java new file mode 100644 index 00000000..608dfb84 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaLangModuleAccess.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.PrintStream; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleDescriptor.Exports; +import java.lang.module.ModuleDescriptor.Opens; +import java.lang.module.ModuleDescriptor.Requires; +import java.lang.module.ModuleDescriptor.Provides; +import java.lang.module.ModuleDescriptor.Version; +import java.lang.module.ModuleFinder; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Provides access to non-public methods in java.lang.module. + */ + +public interface JavaLangModuleAccess { + + /** + * Creates a builder for building a module with the given module name. + * + * @param strict + * Indicates whether module names are checked or not + */ + ModuleDescriptor.Builder newModuleBuilder(String mn, + boolean strict, + Set ms); + + /** + * Returns a snapshot of the packages in the module. + */ + Set packages(ModuleDescriptor.Builder builder); + + /** + * Adds a dependence on a module with the given (possibly un-parsable) + * version string. + */ + void requires(ModuleDescriptor.Builder builder, + Set ms, + String mn, + String rawCompiledVersion); + + /** + * Returns a {@code ModuleDescriptor.Requires} of the given modifiers + * and module name. + */ + Requires newRequires(Set ms, String mn, Version v); + + /** + * Returns an unqualified {@code ModuleDescriptor.Exports} + * of the given modifiers and package name source. + */ + Exports newExports(Set ms, + String source); + + /** + * Returns a qualified {@code ModuleDescriptor.Exports} + * of the given modifiers, package name source and targets. + */ + Exports newExports(Set ms, + String source, + Set targets); + + /** + * Returns an unqualified {@code ModuleDescriptor.Opens} + * of the given modifiers and package name source. + */ + Opens newOpens(Set ms, String source); + + /** + * Returns a qualified {@code ModuleDescriptor.Opens} + * of the given modifiers, package name source and targets. + */ + Opens newOpens(Set ms, String source, Set targets); + + /** + * Returns a {@code ModuleDescriptor.Provides} + * of the given service name and providers. + */ + Provides newProvides(String service, List providers); + + /** + * Returns a new {@code ModuleDescriptor} instance. + */ + ModuleDescriptor newModuleDescriptor(String name, + Version version, + Set ms, + Set requires, + Set exports, + Set opens, + Set uses, + Set provides, + Set packages, + String mainClass, + int hashCode); + + /** + * Resolves a collection of root modules, with service binding + * and the empty configuration as the parent. + */ + Configuration resolveAndBind(ModuleFinder finder, + Collection roots, + PrintStream traceOutput); + + /** + * Creates a configuration from a pre-generated readability graph. + */ + Configuration newConfiguration(ModuleFinder finder, + Map> graph); + +} diff --git a/tests/test_data/std/jdk/internal/access/JavaLangRefAccess.class b/tests/test_data/std/jdk/internal/access/JavaLangRefAccess.class new file mode 100644 index 00000000..209408f7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaLangRefAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaLangRefAccess.java b/tests/test_data/std/jdk/internal/access/JavaLangRefAccess.java new file mode 100644 index 00000000..ed9967ec --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaLangRefAccess.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.lang.ref.ReferenceQueue; + +public interface JavaLangRefAccess { + + /** + * Starts the Finalizer and Reference Handler threads. + */ + void startThreads(); + + /** + * Wait for progress in {@link java.lang.ref.Reference} + * processing. If there aren't any pending {@link + * java.lang.ref.Reference}s, return immediately. + * + * @return {@code true} if there were any pending + * {@link java.lang.ref.Reference}s, {@code false} otherwise. + */ + boolean waitForReferenceProcessing() throws InterruptedException; + + /** + * Runs the finalization methods of any objects pending finalization. + * + * Invoked by Runtime.runFinalization() + */ + void runFinalization(); + + /** + * Constructs a new NativeReferenceQueue. + * + * Invoked by jdk.internal.util.ReferencedKeyMap + */ + ReferenceQueue newNativeReferenceQueue(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaLangReflectAccess.class b/tests/test_data/std/jdk/internal/access/JavaLangReflectAccess.class new file mode 100644 index 00000000..2c558e5b Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaLangReflectAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaLangReflectAccess.java b/tests/test_data/std/jdk/internal/access/JavaLangReflectAccess.java new file mode 100644 index 00000000..f49221e4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaLangReflectAccess.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.lang.reflect.*; +import jdk.internal.reflect.*; + +/** An interface which gives privileged packages Java-level access to + internals of java.lang.reflect. */ + +public interface JavaLangReflectAccess { + /** Creates a new java.lang.reflect.Constructor. Access checks as + per java.lang.reflect.AccessibleObject are not overridden. */ + public Constructor newConstructor(Class declaringClass, + Class[] parameterTypes, + Class[] checkedExceptions, + int modifiers, + int slot, + String signature, + byte[] annotations, + byte[] parameterAnnotations); + + /** Gets the MethodAccessor object for a java.lang.reflect.Method */ + public MethodAccessor getMethodAccessor(Method m); + + /** Sets the MethodAccessor object for a java.lang.reflect.Method */ + public void setMethodAccessor(Method m, MethodAccessor accessor); + + /** Gets the ConstructorAccessor object for a + java.lang.reflect.Constructor */ + public ConstructorAccessor getConstructorAccessor(Constructor c); + + /** Sets the ConstructorAccessor object for a + java.lang.reflect.Constructor */ + public void setConstructorAccessor(Constructor c, + ConstructorAccessor accessor); + + /** Gets the byte[] that encodes TypeAnnotations on an Executable. */ + public byte[] getExecutableTypeAnnotationBytes(Executable ex); + + /** Gets the "slot" field from a Constructor (used for serialization) */ + public int getConstructorSlot(Constructor c); + + /** Gets the "signature" field from a Constructor (used for serialization) */ + public String getConstructorSignature(Constructor c); + + /** Gets the "annotations" field from a Constructor (used for serialization) */ + public byte[] getConstructorAnnotations(Constructor c); + + /** Gets the "parameterAnnotations" field from a Constructor (used for serialization) */ + public byte[] getConstructorParameterAnnotations(Constructor c); + + /** Gets the shared array of parameter types of an Executable. */ + public Class[] getExecutableSharedParameterTypes(Executable ex); + + /** Gets the shared array of exception types of an Executable. */ + public Class[] getExecutableSharedExceptionTypes(Executable ex); + + // + // Copying routines, needed to quickly fabricate new Field, + // Method, and Constructor objects from templates + // + + /** Makes a "child" copy of a Method */ + public Method copyMethod(Method arg); + + /** Makes a copy of this non-root a Method */ + public Method leafCopyMethod(Method arg); + + /** Makes a "child" copy of a Field */ + public Field copyField(Field arg); + + /** Makes a "child" copy of a Constructor */ + public Constructor copyConstructor(Constructor arg); + + /** Gets the root of the given AccessibleObject object; null if arg is the root */ + public T getRoot(T obj); + + /** Tests if this is a trusted final field */ + public boolean isTrustedFinalField(Field f); + + /** Returns a new instance created by the given constructor with access check */ + public T newInstance(Constructor ctor, Object[] args, Class caller) + throws IllegalAccessException, InstantiationException, InvocationTargetException; +} diff --git a/tests/test_data/std/jdk/internal/access/JavaNetHttpCookieAccess.class b/tests/test_data/std/jdk/internal/access/JavaNetHttpCookieAccess.class new file mode 100644 index 00000000..7ae0b1bd Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaNetHttpCookieAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaNetHttpCookieAccess.java b/tests/test_data/std/jdk/internal/access/JavaNetHttpCookieAccess.java new file mode 100644 index 00000000..b76dbf33 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaNetHttpCookieAccess.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.net.HttpCookie; +import java.util.List; + +public interface JavaNetHttpCookieAccess { + /* + * Constructs cookies from Set-Cookie or Set-Cookie2 header string, + * retaining the original header String in the cookie itself. + */ + public List parse(String header); + + /* + * Returns the original header this cookie was constructed from, if it was + * constructed by parsing a header, otherwise null. + */ + public String header(HttpCookie cookie); +} + diff --git a/tests/test_data/std/jdk/internal/access/JavaNetInetAddressAccess.class b/tests/test_data/std/jdk/internal/access/JavaNetInetAddressAccess.class new file mode 100644 index 00000000..d9e1f147 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaNetInetAddressAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaNetInetAddressAccess.java b/tests/test_data/std/jdk/internal/access/JavaNetInetAddressAccess.java new file mode 100644 index 00000000..d3e0e658 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaNetInetAddressAccess.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +public interface JavaNetInetAddressAccess { + /** + * Return the original application specified hostname of + * the given InetAddress object. + */ + String getOriginalHostName(InetAddress ia); + + /** + * Returns the 32-bit IPv4 address. + */ + int addressValue(Inet4Address inet4Address); + + /** + * Returns a reference to the byte[] with the IPv6 address. + */ + byte[] addressBytes(Inet6Address inet6Address); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaNetURLAccess.class b/tests/test_data/std/jdk/internal/access/JavaNetURLAccess.class new file mode 100644 index 00000000..7b1faf8e Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaNetURLAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaNetURLAccess.java b/tests/test_data/std/jdk/internal/access/JavaNetURLAccess.java new file mode 100644 index 00000000..0ad25d80 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaNetURLAccess.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import java.net.URL; +import java.net.URLStreamHandler; + +public interface JavaNetURLAccess { + URLStreamHandler getHandler(URL u); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaNetUriAccess.class b/tests/test_data/std/jdk/internal/access/JavaNetUriAccess.class new file mode 100644 index 00000000..730b18d7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaNetUriAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaNetUriAccess.java b/tests/test_data/std/jdk/internal/access/JavaNetUriAccess.java new file mode 100644 index 00000000..8eb95a21 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaNetUriAccess.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.net.URI; + +public interface JavaNetUriAccess { + /** + * Create a URI of pre-validated scheme and path. + */ + URI create(String scheme, String path); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaNioAccess.class b/tests/test_data/std/jdk/internal/access/JavaNioAccess.class new file mode 100644 index 00000000..63ac2054 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaNioAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaNioAccess.java b/tests/test_data/std/jdk/internal/access/JavaNioAccess.java new file mode 100644 index 00000000..b34a0d42 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaNioAccess.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import jdk.internal.access.foreign.UnmapperProxy; +import jdk.internal.misc.VM.BufferPool; + +import java.lang.foreign.MemorySegment; +import java.io.FileDescriptor; +import java.nio.Buffer; +import java.nio.ByteBuffer; + +public interface JavaNioAccess { + + /** + * Used by {@code jdk.internal.misc.VM}. + */ + BufferPool getDirectBufferPool(); + + /** + * Constructs a direct ByteBuffer referring to the block of memory starting + * at the given memory address and extending {@code cap} bytes. + * The {@code ob} parameter is an arbitrary object that is attached + * to the resulting buffer. + * Used by {@code jdk.internal.foreignMemorySegmentImpl}. + */ + ByteBuffer newDirectByteBuffer(long addr, int cap, Object obj, MemorySegment segment); + + /** + * Constructs a mapped ByteBuffer referring to the block of memory starting + * at the given memory address and extending {@code cap} bytes. + * The {@code ob} parameter is an arbitrary object that is attached + * to the resulting buffer. The {@code sync} and {@code fd} parameters of the mapped + * buffer are derived from the {@code UnmapperProxy}. + * Used by {@code jdk.internal.foreignMemorySegmentImpl}. + */ + ByteBuffer newMappedByteBuffer(UnmapperProxy unmapperProxy, long addr, int cap, Object obj, MemorySegment segment); + + /** + * Constructs an heap ByteBuffer with given backing array, offset, capacity and segment. + * Used by {@code jdk.internal.foreignMemorySegmentImpl}. + */ + ByteBuffer newHeapByteBuffer(byte[] hb, int offset, int capacity, MemorySegment segment); + + /** + * Used by {@code jdk.internal.foreign.Utils}. + */ + Object getBufferBase(Buffer bb); + + /** + * Used by {@code jdk.internal.foreign.Utils}. + */ + long getBufferAddress(Buffer buffer); + + /** + * Used by {@code jdk.internal.foreign.Utils}. + */ + UnmapperProxy unmapper(Buffer buffer); + + /** + * Used by {@code jdk.internal.foreign.AbstractMemorySegmentImpl} and byte buffer var handle views. + */ + MemorySegment bufferSegment(Buffer buffer); + + /** + * Used by operations to make a buffer's session non-closeable + * (for the duration of the operation) by acquiring the session. + * {@snippet lang = java: + * acquireSession(buffer); + * try { + * performOperation(buffer); + * } finally { + * releaseSession(buffer); + * } + *} + * + * @see #releaseSession(Buffer) + */ + void acquireSession(Buffer buffer); + + void releaseSession(Buffer buffer); + + boolean isThreadConfined(Buffer buffer); + + boolean hasSession(Buffer buffer); + + /** + * Used by {@code jdk.internal.foreign.MappedMemorySegmentImpl} and byte buffer var handle views. + */ + void force(FileDescriptor fd, long address, boolean isSync, long offset, long size); + + /** + * Used by {@code jdk.internal.foreign.MappedMemorySegmentImpl} and byte buffer var handle views. + */ + void load(long address, boolean isSync, long size); + + /** + * Used by {@code jdk.internal.foreign.MappedMemorySegmentImpl}. + */ + void unload(long address, boolean isSync, long size); + + /** + * Used by {@code jdk.internal.foreign.MappedMemorySegmentImpl} and byte buffer var handle views. + */ + boolean isLoaded(long address, boolean isSync, long size); + + /** + * Used by {@code jdk.internal.foreign.NativeMemorySegmentImpl}. + */ + void reserveMemory(long size, long cap); + + /** + * Used by {@code jdk.internal.foreign.NativeMemorySegmentImpl}. + */ + void unreserveMemory(long size, long cap); + + /** + * Used by {@code jdk.internal.foreign.NativeMemorySegmentImpl}. + */ + int pageSize(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaObjectInputFilterAccess.class b/tests/test_data/std/jdk/internal/access/JavaObjectInputFilterAccess.class new file mode 100644 index 00000000..8a5242e4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaObjectInputFilterAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaObjectInputFilterAccess.java b/tests/test_data/std/jdk/internal/access/JavaObjectInputFilterAccess.java new file mode 100644 index 00000000..bc60cbbb --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaObjectInputFilterAccess.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.ObjectInputFilter; + +/** + * Access to the alternative ObjectInputFilter.Config.createFilter2 for RMI. + */ +public interface JavaObjectInputFilterAccess { + /** + * Creates a filter from the pattern. + */ + ObjectInputFilter createFilter2(String pattern); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamAccess.class b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamAccess.class new file mode 100644 index 00000000..85420383 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamAccess.java b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamAccess.java new file mode 100644 index 00000000..a47add47 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamAccess.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.ObjectStreamException; +import java.io.ObjectInputStream; + +/** + * Interface to specify methods for accessing {@code ObjectInputStream}. + */ +@FunctionalInterface +public interface JavaObjectInputStreamAccess { + void checkArray(ObjectInputStream ois, Class arrayType, int arrayLength) + throws ObjectStreamException; +} diff --git a/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamReadString.class b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamReadString.class new file mode 100644 index 00000000..9366ed49 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamReadString.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamReadString.java b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamReadString.java new file mode 100644 index 00000000..1ae489b7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaObjectInputStreamReadString.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.IOException; +import java.io.ObjectInputStream; + +/** + * Interface to specify methods for accessing {@code ObjectInputStream}. + */ +@FunctionalInterface +public interface JavaObjectInputStreamReadString { + String readString(ObjectInputStream ois) throws IOException; +} + diff --git a/tests/test_data/std/jdk/internal/access/JavaSecurityAccess$ProtectionDomainCache.class b/tests/test_data/std/jdk/internal/access/JavaSecurityAccess$ProtectionDomainCache.class new file mode 100644 index 00000000..66dcadec Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaSecurityAccess$ProtectionDomainCache.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaSecurityAccess.class b/tests/test_data/std/jdk/internal/access/JavaSecurityAccess.class new file mode 100644 index 00000000..2dce637e Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaSecurityAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaSecurityAccess.java b/tests/test_data/std/jdk/internal/access/JavaSecurityAccess.java new file mode 100644 index 00000000..cb1fd49c --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaSecurityAccess.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.security.AccessControlContext; +import java.security.PermissionCollection; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; + +public interface JavaSecurityAccess { + + T doIntersectionPrivilege(PrivilegedAction action, + @SuppressWarnings("removal") AccessControlContext stack, + @SuppressWarnings("removal") AccessControlContext context); + + T doIntersectionPrivilege(PrivilegedAction action, + @SuppressWarnings("removal") AccessControlContext context); + + ProtectionDomain[] getProtectDomains(@SuppressWarnings("removal") AccessControlContext context); + + interface ProtectionDomainCache { + void put(ProtectionDomain pd, PermissionCollection pc); + PermissionCollection get(ProtectionDomain pd); + } + + /** + * Returns the ProtectionDomainCache. + */ + ProtectionDomainCache getProtectionDomainCache(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaSecurityPropertiesAccess.class b/tests/test_data/std/jdk/internal/access/JavaSecurityPropertiesAccess.class new file mode 100644 index 00000000..2a8d1771 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaSecurityPropertiesAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaSecurityPropertiesAccess.java b/tests/test_data/std/jdk/internal/access/JavaSecurityPropertiesAccess.java new file mode 100644 index 00000000..a4875f35 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaSecurityPropertiesAccess.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.util.Properties; + +public interface JavaSecurityPropertiesAccess { + Properties getInitialProperties(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaSecuritySignatureAccess.class b/tests/test_data/std/jdk/internal/access/JavaSecuritySignatureAccess.class new file mode 100644 index 00000000..be5097ce Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaSecuritySignatureAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaSecuritySignatureAccess.java b/tests/test_data/std/jdk/internal/access/JavaSecuritySignatureAccess.java new file mode 100644 index 00000000..28adeaff --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaSecuritySignatureAccess.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +public interface JavaSecuritySignatureAccess { + + void initVerify(Signature s, PublicKey publicKey, AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException; + + void initVerify(Signature s, java.security.cert.Certificate certificate, + AlgorithmParameterSpec params) + throws InvalidKeyException, InvalidAlgorithmParameterException; + + void initSign(Signature s, PrivateKey privateKey, + AlgorithmParameterSpec params, SecureRandom random) + throws InvalidKeyException, InvalidAlgorithmParameterException; +} diff --git a/tests/test_data/std/jdk/internal/access/JavaSecuritySpecAccess.class b/tests/test_data/std/jdk/internal/access/JavaSecuritySpecAccess.class new file mode 100644 index 00000000..07a3a204 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaSecuritySpecAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaSecuritySpecAccess.java b/tests/test_data/std/jdk/internal/access/JavaSecuritySpecAccess.java new file mode 100644 index 00000000..c370074a --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaSecuritySpecAccess.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.security.spec.EncodedKeySpec; + +public interface JavaSecuritySpecAccess { + void clearEncodedKeySpec(EncodedKeySpec keySpec); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilCollectionAccess.class b/tests/test_data/std/jdk/internal/access/JavaUtilCollectionAccess.class new file mode 100644 index 00000000..7dff52aa Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaUtilCollectionAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilCollectionAccess.java b/tests/test_data/std/jdk/internal/access/JavaUtilCollectionAccess.java new file mode 100644 index 00000000..f88d5752 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaUtilCollectionAccess.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.util.List; + +public interface JavaUtilCollectionAccess { + List listFromTrustedArray(Object[] array); + List listFromTrustedArrayNullsAllowed(Object[] array); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentFJPAccess.class b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentFJPAccess.class new file mode 100644 index 00000000..ece63068 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentFJPAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentFJPAccess.java b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentFJPAccess.java new file mode 100644 index 00000000..28c1b7fd --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentFJPAccess.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import java.util.concurrent.ForkJoinPool; + +public interface JavaUtilConcurrentFJPAccess { + long beginCompensatedBlock(ForkJoinPool pool); + void endCompensatedBlock(ForkJoinPool pool, long post); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentTLRAccess.class b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentTLRAccess.class new file mode 100644 index 00000000..68c4213e Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentTLRAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentTLRAccess.java b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentTLRAccess.java new file mode 100644 index 00000000..5683146e --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaUtilConcurrentTLRAccess.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +public interface JavaUtilConcurrentTLRAccess { + int nextSecondaryThreadLocalRandomSeed(); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilJarAccess.class b/tests/test_data/std/jdk/internal/access/JavaUtilJarAccess.class new file mode 100644 index 00000000..dcfaab98 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaUtilJarAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilJarAccess.java b/tests/test_data/std/jdk/internal/access/JavaUtilJarAccess.java new file mode 100644 index 00000000..fb266330 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaUtilJarAccess.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public interface JavaUtilJarAccess { + public boolean jarFileHasClassPathAttribute(JarFile jar) throws IOException; + public Attributes getTrustedAttributes(Manifest man, String name); + public void ensureInitialization(JarFile jar); + public boolean isInitializing(); + public JarEntry entryFor(JarFile file, String name); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilResourceBundleAccess.class b/tests/test_data/std/jdk/internal/access/JavaUtilResourceBundleAccess.class new file mode 100644 index 00000000..ffd55232 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaUtilResourceBundleAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilResourceBundleAccess.java b/tests/test_data/std/jdk/internal/access/JavaUtilResourceBundleAccess.java new file mode 100644 index 00000000..fb8fda8f --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaUtilResourceBundleAccess.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * Provides access to non-public methods in java.util.ResourceBundle. + */ +public interface JavaUtilResourceBundleAccess { + /** + * Sets the bundle's parent to the given parent. + */ + void setParent(ResourceBundle bundle, ResourceBundle parent); + + /** + * Returns the parent of the given bundle or null if the bundle has no parent. + */ + ResourceBundle getParent(ResourceBundle bundle); + + /** + * Sets the bundle's locale to the given locale. + */ + void setLocale(ResourceBundle bundle, Locale locale); + + /** + * Sets the bundle's base name to the given name. + */ + void setName(ResourceBundle bundle, String name); + + /** + * Returns a {@code ResourceBundle} of the given baseName and locale + * loaded on behalf of the given module with no caller module + * access check. + */ + ResourceBundle getBundle(String baseName, Locale locale, Module module); + + /** + * Instantiates a {@code ResourceBundle} of the given bundle class. + */ + ResourceBundle newResourceBundle(Class bundleClass); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilZipFileAccess.class b/tests/test_data/std/jdk/internal/access/JavaUtilZipFileAccess.class new file mode 100644 index 00000000..b645b19c Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaUtilZipFileAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaUtilZipFileAccess.java b/tests/test_data/std/jdk/internal/access/JavaUtilZipFileAccess.java new file mode 100644 index 00000000..3728a6c7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaUtilZipFileAccess.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public interface JavaUtilZipFileAccess { + public boolean startsWithLocHeader(ZipFile zip); + public List getManifestAndSignatureRelatedFiles(JarFile zip); + public String getManifestName(JarFile zip, boolean onlyIfSignatureRelatedFiles); + public int getManifestNum(JarFile zip); + public int[] getMetaInfVersions(JarFile zip); + public Enumeration entries(ZipFile zip); + public Stream stream(ZipFile zip); + public Stream entryNameStream(ZipFile zip); + public void setExtraAttributes(ZipEntry ze, int extraAttrs); + public int getExtraAttributes(ZipEntry ze); +} + diff --git a/tests/test_data/std/jdk/internal/access/JavaxCryptoSealedObjectAccess.class b/tests/test_data/std/jdk/internal/access/JavaxCryptoSealedObjectAccess.class new file mode 100644 index 00000000..ae2865cf Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaxCryptoSealedObjectAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaxCryptoSealedObjectAccess.java b/tests/test_data/std/jdk/internal/access/JavaxCryptoSealedObjectAccess.java new file mode 100644 index 00000000..0ab3c139 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaxCryptoSealedObjectAccess.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.access; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.SealedObject; +import java.io.IOException; +import java.io.ObjectInputStream; + +public interface JavaxCryptoSealedObjectAccess { + ObjectInputStream getExtObjectInputStream( + SealedObject sealed, Cipher cipher) + throws BadPaddingException, IllegalBlockSizeException, IOException; +} diff --git a/tests/test_data/std/jdk/internal/access/JavaxCryptoSpecAccess.class b/tests/test_data/std/jdk/internal/access/JavaxCryptoSpecAccess.class new file mode 100644 index 00000000..51853c3b Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaxCryptoSpecAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaxCryptoSpecAccess.java b/tests/test_data/std/jdk/internal/access/JavaxCryptoSpecAccess.java new file mode 100644 index 00000000..f8904cae --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaxCryptoSpecAccess.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import javax.crypto.spec.SecretKeySpec; + +public interface JavaxCryptoSpecAccess { + void clearSecretKeySpec(SecretKeySpec keySpec); +} diff --git a/tests/test_data/std/jdk/internal/access/JavaxSecurityAccess.class b/tests/test_data/std/jdk/internal/access/JavaxSecurityAccess.class new file mode 100644 index 00000000..387f09ee Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/JavaxSecurityAccess.class differ diff --git a/tests/test_data/std/jdk/internal/access/JavaxSecurityAccess.java b/tests/test_data/std/jdk/internal/access/JavaxSecurityAccess.java new file mode 100644 index 00000000..004b6db3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/JavaxSecurityAccess.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import javax.security.auth.x500.X500Principal; +import sun.security.x509.X500Name; + +public interface JavaxSecurityAccess { + X500Name asX500Name(X500Principal p); + X500Principal asX500Principal(X500Name n); +} diff --git a/tests/test_data/std/jdk/internal/access/SharedSecrets.class b/tests/test_data/std/jdk/internal/access/SharedSecrets.class new file mode 100644 index 00000000..04123e79 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/SharedSecrets.class differ diff --git a/tests/test_data/std/jdk/internal/access/SharedSecrets.java b/tests/test_data/std/jdk/internal/access/SharedSecrets.java new file mode 100644 index 00000000..0dacbef9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/SharedSecrets.java @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import javax.crypto.SealedObject; +import javax.crypto.spec.SecretKeySpec; +import java.io.ObjectInputFilter; +import java.lang.invoke.MethodHandles; +import java.lang.module.ModuleDescriptor; +import java.security.Security; +import java.security.spec.EncodedKeySpec; +import java.util.ResourceBundle; +import java.util.concurrent.ForkJoinPool; +import java.util.jar.JarFile; +import java.io.Console; +import java.io.FileDescriptor; +import java.io.FilePermission; +import java.io.ObjectInputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.security.ProtectionDomain; +import java.security.Signature; +import javax.security.auth.x500.X500Principal; + +/** A repository of "shared secrets", which are a mechanism for + calling implementation-private methods in another package without + using reflection. A package-private class implements a public + interface and provides the ability to call package-private methods + within that package; the object implementing that interface is + provided through a third package to which access is restricted. + This framework avoids the primary disadvantage of using reflection + for this purpose, namely the loss of compile-time checking. */ + +public class SharedSecrets { + private static JavaAWTAccess javaAWTAccess; + private static JavaAWTFontAccess javaAWTFontAccess; + private static JavaBeansAccess javaBeansAccess; + private static JavaLangAccess javaLangAccess; + private static JavaLangInvokeAccess javaLangInvokeAccess; + private static JavaLangModuleAccess javaLangModuleAccess; + private static JavaLangRefAccess javaLangRefAccess; + private static JavaLangReflectAccess javaLangReflectAccess; + private static JavaIOAccess javaIOAccess; + private static JavaIOPrintStreamAccess javaIOPrintStreamAccess; + private static JavaIOPrintWriterAccess javaIOPrintWriterAccess; + private static JavaIOFileDescriptorAccess javaIOFileDescriptorAccess; + private static JavaIOFilePermissionAccess javaIOFilePermissionAccess; + private static JavaIORandomAccessFileAccess javaIORandomAccessFileAccess; + private static JavaObjectInputStreamReadString javaObjectInputStreamReadString; + private static JavaObjectInputStreamAccess javaObjectInputStreamAccess; + private static JavaObjectInputFilterAccess javaObjectInputFilterAccess; + private static JavaNetInetAddressAccess javaNetInetAddressAccess; + private static JavaNetHttpCookieAccess javaNetHttpCookieAccess; + private static JavaNetUriAccess javaNetUriAccess; + private static JavaNetURLAccess javaNetURLAccess; + private static JavaNioAccess javaNioAccess; + private static JavaUtilCollectionAccess javaUtilCollectionAccess; + private static JavaUtilConcurrentTLRAccess javaUtilConcurrentTLRAccess; + private static JavaUtilConcurrentFJPAccess javaUtilConcurrentFJPAccess; + private static JavaUtilJarAccess javaUtilJarAccess; + private static JavaUtilZipFileAccess javaUtilZipFileAccess; + private static JavaUtilResourceBundleAccess javaUtilResourceBundleAccess; + private static JavaSecurityAccess javaSecurityAccess; + private static JavaSecurityPropertiesAccess javaSecurityPropertiesAccess; + private static JavaSecuritySignatureAccess javaSecuritySignatureAccess; + private static JavaSecuritySpecAccess javaSecuritySpecAccess; + private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess; + private static JavaxCryptoSpecAccess javaxCryptoSpecAccess; + private static JavaxSecurityAccess javaxSecurityAccess; + + public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) { + javaUtilCollectionAccess = juca; + } + + public static JavaUtilCollectionAccess getJavaUtilCollectionAccess() { + var access = javaUtilCollectionAccess; + if (access == null) { + try { + Class.forName("java.util.ImmutableCollections$Access", true, null); + access = javaUtilCollectionAccess; + } catch (ClassNotFoundException e) {} + } + return access; + } + + public static void setJavaUtilConcurrentTLRAccess(JavaUtilConcurrentTLRAccess access) { + javaUtilConcurrentTLRAccess = access; + } + + public static JavaUtilConcurrentTLRAccess getJavaUtilConcurrentTLRAccess() { + var access = javaUtilConcurrentTLRAccess; + if (access == null) { + try { + Class.forName("java.util.concurrent.ThreadLocalRandom$Access", true, null); + access = javaUtilConcurrentTLRAccess; + } catch (ClassNotFoundException e) {} + } + return access; + } + + public static void setJavaUtilConcurrentFJPAccess(JavaUtilConcurrentFJPAccess access) { + javaUtilConcurrentFJPAccess = access; + } + + public static JavaUtilConcurrentFJPAccess getJavaUtilConcurrentFJPAccess() { + var access = javaUtilConcurrentFJPAccess; + if (access == null) { + ensureClassInitialized(ForkJoinPool.class); + access = javaUtilConcurrentFJPAccess; + } + return access; + } + + public static JavaUtilJarAccess javaUtilJarAccess() { + var access = javaUtilJarAccess; + if (access == null) { + // Ensure JarFile is initialized; we know that this class + // provides the shared secret + ensureClassInitialized(JarFile.class); + access = javaUtilJarAccess; + } + return access; + } + + public static void setJavaUtilJarAccess(JavaUtilJarAccess access) { + javaUtilJarAccess = access; + } + + public static void setJavaLangAccess(JavaLangAccess jla) { + javaLangAccess = jla; + } + + public static JavaLangAccess getJavaLangAccess() { + return javaLangAccess; + } + + public static void setJavaLangInvokeAccess(JavaLangInvokeAccess jlia) { + javaLangInvokeAccess = jlia; + } + + public static JavaLangInvokeAccess getJavaLangInvokeAccess() { + var access = javaLangInvokeAccess; + if (access == null) { + try { + Class.forName("java.lang.invoke.MethodHandleImpl", true, null); + access = javaLangInvokeAccess; + } catch (ClassNotFoundException e) {} + } + return access; + } + + public static void setJavaLangModuleAccess(JavaLangModuleAccess jlrma) { + javaLangModuleAccess = jlrma; + } + + public static JavaLangModuleAccess getJavaLangModuleAccess() { + var access = javaLangModuleAccess; + if (access == null) { + ensureClassInitialized(ModuleDescriptor.class); + access = javaLangModuleAccess; + } + return access; + } + + public static void setJavaLangRefAccess(JavaLangRefAccess jlra) { + javaLangRefAccess = jlra; + } + + public static JavaLangRefAccess getJavaLangRefAccess() { + return javaLangRefAccess; + } + + public static void setJavaLangReflectAccess(JavaLangReflectAccess jlra) { + javaLangReflectAccess = jlra; + } + + public static JavaLangReflectAccess getJavaLangReflectAccess() { + return javaLangReflectAccess; + } + + public static void setJavaNetUriAccess(JavaNetUriAccess jnua) { + javaNetUriAccess = jnua; + } + + public static JavaNetUriAccess getJavaNetUriAccess() { + var access = javaNetUriAccess; + if (access == null) { + ensureClassInitialized(java.net.URI.class); + access = javaNetUriAccess; + } + return access; + } + + public static void setJavaNetURLAccess(JavaNetURLAccess jnua) { + javaNetURLAccess = jnua; + } + + public static JavaNetURLAccess getJavaNetURLAccess() { + var access = javaNetURLAccess; + if (access == null) { + ensureClassInitialized(java.net.URL.class); + access = javaNetURLAccess; + } + return access; + } + + public static void setJavaNetInetAddressAccess(JavaNetInetAddressAccess jna) { + javaNetInetAddressAccess = jna; + } + + public static JavaNetInetAddressAccess getJavaNetInetAddressAccess() { + var access = javaNetInetAddressAccess; + if (access == null) { + ensureClassInitialized(java.net.InetAddress.class); + access = javaNetInetAddressAccess; + } + return access; + } + + public static void setJavaNetHttpCookieAccess(JavaNetHttpCookieAccess a) { + javaNetHttpCookieAccess = a; + } + + public static JavaNetHttpCookieAccess getJavaNetHttpCookieAccess() { + var access = javaNetHttpCookieAccess; + if (access == null) { + ensureClassInitialized(java.net.HttpCookie.class); + access = javaNetHttpCookieAccess; + } + return access; + } + + public static void setJavaNioAccess(JavaNioAccess jna) { + javaNioAccess = jna; + } + + public static JavaNioAccess getJavaNioAccess() { + var access = javaNioAccess; + if (access == null) { + // Ensure java.nio.Buffer is initialized, which provides the + // shared secret. + ensureClassInitialized(java.nio.Buffer.class); + access = javaNioAccess; + } + return access; + } + + public static void setJavaIOAccess(JavaIOAccess jia) { + javaIOAccess = jia; + } + + public static JavaIOAccess getJavaIOAccess() { + var access = javaIOAccess; + if (access == null) { + ensureClassInitialized(Console.class); + access = javaIOAccess; + } + return access; + } + + public static void setJavaIOCPrintWriterAccess(JavaIOPrintWriterAccess a) { + javaIOPrintWriterAccess = a; + } + + public static JavaIOPrintWriterAccess getJavaIOPrintWriterAccess() { + var access = javaIOPrintWriterAccess; + if (access == null) { + ensureClassInitialized(PrintWriter.class); + access = javaIOPrintWriterAccess; + } + return access; + } + + public static void setJavaIOCPrintStreamAccess(JavaIOPrintStreamAccess a) { + javaIOPrintStreamAccess = a; + } + + public static JavaIOPrintStreamAccess getJavaIOPrintStreamAccess() { + var access = javaIOPrintStreamAccess; + if (access == null) { + ensureClassInitialized(PrintStream.class); + access = javaIOPrintStreamAccess; + } + return access; + } + + public static void setJavaIOFileDescriptorAccess(JavaIOFileDescriptorAccess jiofda) { + javaIOFileDescriptorAccess = jiofda; + } + + public static JavaIOFilePermissionAccess getJavaIOFilePermissionAccess() { + var access = javaIOFilePermissionAccess; + if (access == null) { + ensureClassInitialized(FilePermission.class); + access = javaIOFilePermissionAccess; + } + return access; + } + + public static void setJavaIOFilePermissionAccess(JavaIOFilePermissionAccess jiofpa) { + javaIOFilePermissionAccess = jiofpa; + } + + public static JavaIOFileDescriptorAccess getJavaIOFileDescriptorAccess() { + var access = javaIOFileDescriptorAccess; + if (access == null) { + ensureClassInitialized(FileDescriptor.class); + access = javaIOFileDescriptorAccess; + } + return access; + } + + public static void setJavaSecurityAccess(JavaSecurityAccess jsa) { + javaSecurityAccess = jsa; + } + + public static JavaSecurityAccess getJavaSecurityAccess() { + var access = javaSecurityAccess; + if (access == null) { + ensureClassInitialized(ProtectionDomain.class); + access = javaSecurityAccess; + } + return access; + } + + public static void setJavaSecurityPropertiesAccess(JavaSecurityPropertiesAccess jspa) { + javaSecurityPropertiesAccess = jspa; + } + + public static JavaSecurityPropertiesAccess getJavaSecurityPropertiesAccess() { + var access = javaSecurityPropertiesAccess; + if (access == null) { + ensureClassInitialized(Security.class); + access = javaSecurityPropertiesAccess; + } + return access; + } + + public static JavaUtilZipFileAccess getJavaUtilZipFileAccess() { + var access = javaUtilZipFileAccess; + if (access == null) { + ensureClassInitialized(java.util.zip.ZipFile.class); + access = javaUtilZipFileAccess; + } + return access; + } + + public static void setJavaUtilZipFileAccess(JavaUtilZipFileAccess access) { + javaUtilZipFileAccess = access; + } + + public static void setJavaAWTAccess(JavaAWTAccess jaa) { + javaAWTAccess = jaa; + } + + public static JavaAWTAccess getJavaAWTAccess() { + // this may return null in which case calling code needs to + // provision for. + return javaAWTAccess; + } + + public static void setJavaAWTFontAccess(JavaAWTFontAccess jafa) { + javaAWTFontAccess = jafa; + } + + public static JavaAWTFontAccess getJavaAWTFontAccess() { + // this may return null in which case calling code needs to + // provision for. + return javaAWTFontAccess; + } + + public static JavaBeansAccess getJavaBeansAccess() { + return javaBeansAccess; + } + + public static void setJavaBeansAccess(JavaBeansAccess access) { + javaBeansAccess = access; + } + + public static JavaUtilResourceBundleAccess getJavaUtilResourceBundleAccess() { + var access = javaUtilResourceBundleAccess; + if (access == null) { + ensureClassInitialized(ResourceBundle.class); + access = javaUtilResourceBundleAccess; + } + return access; + } + + public static void setJavaUtilResourceBundleAccess(JavaUtilResourceBundleAccess access) { + javaUtilResourceBundleAccess = access; + } + + public static JavaObjectInputStreamReadString getJavaObjectInputStreamReadString() { + var access = javaObjectInputStreamReadString; + if (access == null) { + ensureClassInitialized(ObjectInputStream.class); + access = javaObjectInputStreamReadString; + } + return access; + } + + public static void setJavaObjectInputStreamReadString(JavaObjectInputStreamReadString access) { + javaObjectInputStreamReadString = access; + } + + public static JavaObjectInputStreamAccess getJavaObjectInputStreamAccess() { + var access = javaObjectInputStreamAccess; + if (access == null) { + ensureClassInitialized(ObjectInputStream.class); + access = javaObjectInputStreamAccess; + } + return access; + } + + public static void setJavaObjectInputStreamAccess(JavaObjectInputStreamAccess access) { + javaObjectInputStreamAccess = access; + } + + public static JavaObjectInputFilterAccess getJavaObjectInputFilterAccess() { + var access = javaObjectInputFilterAccess; + if (access == null) { + ensureClassInitialized(ObjectInputFilter.Config.class); + access = javaObjectInputFilterAccess; + } + return access; + } + + public static void setJavaObjectInputFilterAccess(JavaObjectInputFilterAccess access) { + javaObjectInputFilterAccess = access; + } + + public static void setJavaIORandomAccessFileAccess(JavaIORandomAccessFileAccess jirafa) { + javaIORandomAccessFileAccess = jirafa; + } + + public static JavaIORandomAccessFileAccess getJavaIORandomAccessFileAccess() { + var access = javaIORandomAccessFileAccess; + if (access == null) { + ensureClassInitialized(RandomAccessFile.class); + access = javaIORandomAccessFileAccess; + } + return access; + } + + public static void setJavaSecuritySignatureAccess(JavaSecuritySignatureAccess jssa) { + javaSecuritySignatureAccess = jssa; + } + + public static JavaSecuritySignatureAccess getJavaSecuritySignatureAccess() { + var access = javaSecuritySignatureAccess; + if (access == null) { + ensureClassInitialized(Signature.class); + access = javaSecuritySignatureAccess; + } + return access; + } + + public static void setJavaSecuritySpecAccess(JavaSecuritySpecAccess jssa) { + javaSecuritySpecAccess = jssa; + } + + public static JavaSecuritySpecAccess getJavaSecuritySpecAccess() { + var access = javaSecuritySpecAccess; + if (access == null) { + ensureClassInitialized(EncodedKeySpec.class); + access = javaSecuritySpecAccess; + } + return access; + } + + public static void setJavaxCryptoSpecAccess(JavaxCryptoSpecAccess jcsa) { + javaxCryptoSpecAccess = jcsa; + } + + public static JavaxCryptoSpecAccess getJavaxCryptoSpecAccess() { + var access = javaxCryptoSpecAccess; + if (access == null) { + ensureClassInitialized(SecretKeySpec.class); + access = javaxCryptoSpecAccess; + } + return access; + } + + public static void setJavaxCryptoSealedObjectAccess(JavaxCryptoSealedObjectAccess jcsoa) { + javaxCryptoSealedObjectAccess = jcsoa; + } + + public static JavaxCryptoSealedObjectAccess getJavaxCryptoSealedObjectAccess() { + var access = javaxCryptoSealedObjectAccess; + if (access == null) { + ensureClassInitialized(SealedObject.class); + access = javaxCryptoSealedObjectAccess; + } + return access; + } + + public static void setJavaxSecurityAccess(JavaxSecurityAccess jsa) { + javaxSecurityAccess = jsa; + } + + public static JavaxSecurityAccess getJavaxSecurityAccess() { + var access = javaxSecurityAccess; + if (access == null) { + ensureClassInitialized(X500Principal.class); + access = javaxSecurityAccess; + } + return access; + } + + private static void ensureClassInitialized(Class c) { + try { + MethodHandles.lookup().ensureInitialized(c); + } catch (IllegalAccessException e) {} + } +} diff --git a/tests/test_data/std/jdk/internal/access/foreign/UnmapperProxy.class b/tests/test_data/std/jdk/internal/access/foreign/UnmapperProxy.class new file mode 100644 index 00000000..1235d5a9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/access/foreign/UnmapperProxy.class differ diff --git a/tests/test_data/std/jdk/internal/access/foreign/UnmapperProxy.java b/tests/test_data/std/jdk/internal/access/foreign/UnmapperProxy.java new file mode 100644 index 00000000..23781301 --- /dev/null +++ b/tests/test_data/std/jdk/internal/access/foreign/UnmapperProxy.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.access.foreign; + +import java.io.FileDescriptor; + +/** + * This proxy interface is required to allow instances of the {@code FileChannelImpl.Unmapper} interface (which is a non-public class + * inside the {@code sun.nio.ch} package) to be accessed from the mapped memory segment factory. + */ +public interface UnmapperProxy { + long address(); + FileDescriptor fileDescriptor(); + boolean isSync(); + void unmap(); +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$AnnotationDefaultMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$AnnotationDefaultMapper.class new file mode 100644 index 00000000..cfd4b86d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$AnnotationDefaultMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$BootstrapMethodsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$BootstrapMethodsMapper.class new file mode 100644 index 00000000..760cefc7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$BootstrapMethodsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CharacterRangeTableMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CharacterRangeTableMapper.class new file mode 100644 index 00000000..8d4f4977 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CharacterRangeTableMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CodeMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CodeMapper.class new file mode 100644 index 00000000..670be80d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CodeMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CompilationIDMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CompilationIDMapper.class new file mode 100644 index 00000000..b47bf758 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$CompilationIDMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ConstantValueMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ConstantValueMapper.class new file mode 100644 index 00000000..a4460d93 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ConstantValueMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$DeprecatedMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$DeprecatedMapper.class new file mode 100644 index 00000000..98d08255 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$DeprecatedMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$EnclosingMethodMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$EnclosingMethodMapper.class new file mode 100644 index 00000000..bbbd1b8c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$EnclosingMethodMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ExceptionsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ExceptionsMapper.class new file mode 100644 index 00000000..2e2337cd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ExceptionsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$InnerClassesMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$InnerClassesMapper.class new file mode 100644 index 00000000..3b4ad38f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$InnerClassesMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LineNumberTableMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LineNumberTableMapper.class new file mode 100644 index 00000000..1b4392a5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LineNumberTableMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LocalVariableTableMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LocalVariableTableMapper.class new file mode 100644 index 00000000..a848e331 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LocalVariableTableMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LocalVariableTypeTableMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LocalVariableTypeTableMapper.class new file mode 100644 index 00000000..451fe0c8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$LocalVariableTypeTableMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$MethodParametersMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$MethodParametersMapper.class new file mode 100644 index 00000000..93352106 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$MethodParametersMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleHashesMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleHashesMapper.class new file mode 100644 index 00000000..9aae69a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleHashesMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleMainClassMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleMainClassMapper.class new file mode 100644 index 00000000..77ef437a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleMainClassMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleMapper.class new file mode 100644 index 00000000..a1592ae3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModulePackagesMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModulePackagesMapper.class new file mode 100644 index 00000000..7e1b40ed Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModulePackagesMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleResolutionMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleResolutionMapper.class new file mode 100644 index 00000000..2df8afa9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleResolutionMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleTargetMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleTargetMapper.class new file mode 100644 index 00000000..1a904ff4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$ModuleTargetMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$NestHostMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$NestHostMapper.class new file mode 100644 index 00000000..63649e5e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$NestHostMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$NestMembersMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$NestMembersMapper.class new file mode 100644 index 00000000..207421e1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$NestMembersMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$PermittedSubclassesMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$PermittedSubclassesMapper.class new file mode 100644 index 00000000..50e4d92a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$PermittedSubclassesMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RecordMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RecordMapper.class new file mode 100644 index 00000000..b1ce3396 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RecordMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleAnnotationsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleAnnotationsMapper.class new file mode 100644 index 00000000..e17ff7c2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleAnnotationsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleParameterAnnotationsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleParameterAnnotationsMapper.class new file mode 100644 index 00000000..80285e78 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleParameterAnnotationsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleTypeAnnotationsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleTypeAnnotationsMapper.class new file mode 100644 index 00000000..ca72f516 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeInvisibleTypeAnnotationsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleAnnotationsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleAnnotationsMapper.class new file mode 100644 index 00000000..43e8fb05 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleAnnotationsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleParameterAnnotationsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleParameterAnnotationsMapper.class new file mode 100644 index 00000000..aaaa8d5d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleParameterAnnotationsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleTypeAnnotationsMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleTypeAnnotationsMapper.class new file mode 100644 index 00000000..237bfd59 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleTypeAnnotationsMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SignatureMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SignatureMapper.class new file mode 100644 index 00000000..9a1dc0bf Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SignatureMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceDebugExtensionMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceDebugExtensionMapper.class new file mode 100644 index 00000000..593aaeb3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceDebugExtensionMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceFileMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceFileMapper.class new file mode 100644 index 00000000..f167cfa3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceFileMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceIDMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceIDMapper.class new file mode 100644 index 00000000..32c84eb1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceIDMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$StackMapTableMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$StackMapTableMapper.class new file mode 100644 index 00000000..d517bc71 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$StackMapTableMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SyntheticMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SyntheticMapper.class new file mode 100644 index 00000000..7b1fda37 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper$SyntheticMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper.class new file mode 100644 index 00000000..54fb3310 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper.java new file mode 100644 index 00000000..9bb3f275 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractAttributeMapper.java @@ -0,0 +1,824 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.Annotation; +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.AttributedElement; +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassReader; +import java.lang.classfile.attribute.*; +import java.util.List; + +import static java.lang.classfile.Attributes.*; + +public sealed abstract class AbstractAttributeMapper> + implements AttributeMapper { + + private final String name; + private final AttributeMapper.AttributeStability stability; + private final boolean allowMultiple; + + protected abstract void writeBody(BufWriter buf, T attr); + + public AbstractAttributeMapper(String name, AttributeMapper.AttributeStability stability) { + this(name, stability, false); + } + + public AbstractAttributeMapper(String name, + AttributeMapper.AttributeStability stability, + boolean allowMultiple) { + this.name = name; + this.stability = stability; + this.allowMultiple = allowMultiple; + } + + @Override + public final String name() { + return name; + } + + @Override + public final void writeAttribute(BufWriter buf, T attr) { + buf.writeIndex(buf.constantPool().utf8Entry(name)); + buf.writeInt(0); + int start = buf.size(); + writeBody(buf, attr); + int written = buf.size() - start; + buf.patchInt(start - 4, 4, written); + } + + @Override + public AttributeMapper.AttributeStability stability() { + return stability; + } + + @Override + public boolean allowMultiple() { + return allowMultiple; + } + + @Override + public String toString() { + return String.format("AttributeMapper[name=%s, allowMultiple=%b, stability=%s]", + name, allowMultiple, stability()); + } + + public static final class AnnotationDefaultMapper extends AbstractAttributeMapper { + public static final AnnotationDefaultMapper INSTANCE = new AnnotationDefaultMapper(); + + private AnnotationDefaultMapper() { + super(NAME_ANNOTATION_DEFAULT, AttributeStability.CP_REFS); + } + + @Override + public AnnotationDefaultAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundAnnotationDefaultAttr(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, AnnotationDefaultAttribute attr) { + attr.defaultValue().writeTo(buf); + } + } + + public static final class BootstrapMethodsMapper extends AbstractAttributeMapper { + public static final BootstrapMethodsMapper INSTANCE = new BootstrapMethodsMapper(); + + private BootstrapMethodsMapper() { + super(NAME_BOOTSTRAP_METHODS, AttributeStability.CP_REFS); + } + + @Override + public BootstrapMethodsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundBootstrapMethodsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, BootstrapMethodsAttribute attr) { + buf.writeList(attr.bootstrapMethods()); + } + } + + public static final class CharacterRangeTableMapper extends AbstractAttributeMapper { + public static final CharacterRangeTableMapper INSTANCE = new CharacterRangeTableMapper(); + + private CharacterRangeTableMapper() { + super(NAME_CHARACTER_RANGE_TABLE, AttributeStability.LABELS, true); + } + + @Override + public CharacterRangeTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundCharacterRangeTableAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, CharacterRangeTableAttribute attr) { + List ranges = attr.characterRangeTable(); + buf.writeU2(ranges.size()); + for (CharacterRangeInfo info : ranges) { + buf.writeU2(info.startPc()); + buf.writeU2(info.endPc()); + buf.writeInt(info.characterRangeStart()); + buf.writeInt(info.characterRangeEnd()); + buf.writeU2(info.flags()); + } + } + } + + public static final class CodeMapper extends AbstractAttributeMapper { + public static final CodeMapper INSTANCE = new CodeMapper(); + + private CodeMapper() { + super(NAME_CODE, AttributeStability.CP_REFS); + } + + @Override + public CodeAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new CodeImpl(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, CodeAttribute attr) { + throw new UnsupportedOperationException("Code attribute does not support direct write"); + } + } + + public static final class CompilationIDMapper extends AbstractAttributeMapper { + public static final CompilationIDMapper INSTANCE = new CompilationIDMapper(); + + private CompilationIDMapper() { + super(NAME_COMPILATION_ID, AttributeStability.CP_REFS); + } + + @Override + public CompilationIDAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundCompilationIDAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, CompilationIDAttribute attr) { + buf.writeIndex(attr.compilationId()); + } + } + + public static final class ConstantValueMapper extends AbstractAttributeMapper { + public static final ConstantValueMapper INSTANCE = new ConstantValueMapper(); + + private ConstantValueMapper() { + super(NAME_CONSTANT_VALUE, AttributeStability.CP_REFS); + } + + @Override + public ConstantValueAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundConstantValueAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ConstantValueAttribute attr) { + buf.writeIndex(attr.constant()); + } + } + + public static final class DeprecatedMapper extends AbstractAttributeMapper { + public static final DeprecatedMapper INSTANCE = new DeprecatedMapper(); + + private DeprecatedMapper() { + super(NAME_DEPRECATED, AttributeStability.STATELESS, true); + } + + @Override + public DeprecatedAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundDeprecatedAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, DeprecatedAttribute attr) { + // empty + } + } + + public static final class EnclosingMethodMapper extends AbstractAttributeMapper { + public static final EnclosingMethodMapper INSTANCE = new EnclosingMethodMapper(); + + private EnclosingMethodMapper() { + super(NAME_ENCLOSING_METHOD, AttributeStability.CP_REFS); + } + + @Override + public EnclosingMethodAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundEnclosingMethodAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, EnclosingMethodAttribute attr) { + buf.writeIndex(attr.enclosingClass()); + buf.writeIndexOrZero(attr.enclosingMethod().orElse(null)); + } + } + + public static final class ExceptionsMapper extends AbstractAttributeMapper { + public static final ExceptionsMapper INSTANCE = new ExceptionsMapper(); + + private ExceptionsMapper() { + super(NAME_EXCEPTIONS, AttributeStability.CP_REFS); + } + + @Override + public ExceptionsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundExceptionsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ExceptionsAttribute attr) { + buf.writeListIndices(attr.exceptions()); + } + } + + public static final class InnerClassesMapper extends AbstractAttributeMapper { + public static final InnerClassesMapper INSTANCE = new InnerClassesMapper(); + + private InnerClassesMapper() { + super(NAME_INNER_CLASSES, AttributeStability.CP_REFS); + } + + @Override + public InnerClassesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundInnerClassesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, InnerClassesAttribute attr) { + List classes = attr.classes(); + buf.writeU2(classes.size()); + for (InnerClassInfo ic : classes) { + buf.writeIndex(ic.innerClass()); + buf.writeIndexOrZero(ic.outerClass().orElse(null)); + buf.writeIndexOrZero(ic.innerName().orElse(null)); + buf.writeU2(ic.flagsMask()); + } + } + } + + public static final class LineNumberTableMapper extends AbstractAttributeMapper { + public static final LineNumberTableMapper INSTANCE = new LineNumberTableMapper(); + + private LineNumberTableMapper() { + super(NAME_LINE_NUMBER_TABLE, AttributeStability.LABELS, true); + } + + @Override + public LineNumberTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLineNumberTableAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LineNumberTableAttribute attr) { + List lines = attr.lineNumbers(); + buf.writeU2(lines.size()); + for (LineNumberInfo line : lines) { + buf.writeU2(line.startPc()); + buf.writeU2(line.lineNumber()); + } + } + } + + public static final class LocalVariableTableMapper extends AbstractAttributeMapper { + public static final LocalVariableTableMapper INSTANCE = new LocalVariableTableMapper(); + + private LocalVariableTableMapper() { + super(NAME_LOCAL_VARIABLE_TABLE, AttributeStability.LABELS, true); + } + + @Override + public LocalVariableTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLocalVariableTableAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LocalVariableTableAttribute attr) { + List infos = attr.localVariables(); + buf.writeU2(infos.size()); + for (LocalVariableInfo info : infos) { + buf.writeU2(info.startPc()); + buf.writeU2(info.length()); + buf.writeIndex(info.name()); + buf.writeIndex(info.type()); + buf.writeU2(info.slot()); + } + } + } + + public static final class LocalVariableTypeTableMapper extends AbstractAttributeMapper { + public static final LocalVariableTypeTableMapper INSTANCE = new LocalVariableTypeTableMapper(); + + private LocalVariableTypeTableMapper() { + super(NAME_LOCAL_VARIABLE_TYPE_TABLE, AttributeStability.LABELS, true); + } + + @Override + public LocalVariableTypeTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundLocalVariableTypeTableAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, LocalVariableTypeTableAttribute attr) { + List infos = attr.localVariableTypes(); + buf.writeU2(infos.size()); + for (LocalVariableTypeInfo info : infos) { + buf.writeU2(info.startPc()); + buf.writeU2(info.length()); + buf.writeIndex(info.name()); + buf.writeIndex(info.signature()); + buf.writeU2(info.slot()); + } + } + } + + public static final class MethodParametersMapper extends AbstractAttributeMapper { + public static final MethodParametersMapper INSTANCE = new MethodParametersMapper(); + + private MethodParametersMapper() { + super(NAME_METHOD_PARAMETERS, AttributeStability.CP_REFS); + } + + @Override + public MethodParametersAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundMethodParametersAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, MethodParametersAttribute attr) { + List parameters = attr.parameters(); + buf.writeU1(parameters.size()); + for (MethodParameterInfo info : parameters) { + buf.writeIndexOrZero(info.name().orElse(null)); + buf.writeU2(info.flagsMask()); + } + } + } + + public static final class ModuleMapper extends AbstractAttributeMapper { + public static final ModuleMapper INSTANCE = new ModuleMapper(); + + private ModuleMapper() { + super(NAME_MODULE, AttributeStability.CP_REFS); + } + + @Override + public ModuleAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleAttribute attr) { + buf.writeIndex(attr.moduleName()); + buf.writeU2(attr.moduleFlagsMask()); + buf.writeIndexOrZero(attr.moduleVersion().orElse(null)); + buf.writeU2(attr.requires().size()); + for (ModuleRequireInfo require : attr.requires()) { + buf.writeIndex(require.requires()); + buf.writeU2(require.requiresFlagsMask()); + buf.writeIndexOrZero(require.requiresVersion().orElse(null)); + } + buf.writeU2(attr.exports().size()); + for (ModuleExportInfo export : attr.exports()) { + buf.writeIndex(export.exportedPackage()); + buf.writeU2(export.exportsFlagsMask()); + buf.writeListIndices(export.exportsTo()); + } + buf.writeU2(attr.opens().size()); + for (ModuleOpenInfo open : attr.opens()) { + buf.writeIndex(open.openedPackage()); + buf.writeU2(open.opensFlagsMask()); + buf.writeListIndices(open.opensTo()); + } + buf.writeListIndices(attr.uses()); + buf.writeU2(attr.provides().size()); + for (ModuleProvideInfo provide : attr.provides()) { + buf.writeIndex(provide.provides()); + buf.writeListIndices(provide.providesWith()); + } + } + } + + public static final class ModuleHashesMapper extends AbstractAttributeMapper { + public static final ModuleHashesMapper INSTANCE = new ModuleHashesMapper(); + + private ModuleHashesMapper() { + super(NAME_MODULE_HASHES, AttributeStability.CP_REFS); + } + + @Override + public ModuleHashesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleHashesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleHashesAttribute attr) { + buf.writeIndex(attr.algorithm()); + List hashes = attr.hashes(); + buf.writeU2(hashes.size()); + for (ModuleHashInfo hash : hashes) { + buf.writeIndex(hash.moduleName()); + buf.writeU2(hash.hash().length); + buf.writeBytes(hash.hash()); + } + } + } + + public static final class ModuleMainClassMapper extends AbstractAttributeMapper { + public static final ModuleMainClassMapper INSTANCE = new ModuleMainClassMapper(); + + private ModuleMainClassMapper() { + super(NAME_MODULE_MAIN_CLASS, AttributeStability.CP_REFS); + } + + @Override + public ModuleMainClassAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleMainClassAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleMainClassAttribute attr) { + buf.writeIndex(attr.mainClass()); + } + } + + public static final class ModulePackagesMapper extends AbstractAttributeMapper { + public static final ModulePackagesMapper INSTANCE = new ModulePackagesMapper(); + + private ModulePackagesMapper() { + super(NAME_MODULE_PACKAGES, AttributeStability.CP_REFS); + } + + @Override + public ModulePackagesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModulePackagesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModulePackagesAttribute attr) { + buf.writeListIndices(attr.packages()); + } + } + + public static final class ModuleResolutionMapper extends AbstractAttributeMapper { + public static final ModuleResolutionMapper INSTANCE = new ModuleResolutionMapper(); + + private ModuleResolutionMapper() { + super(NAME_MODULE_RESOLUTION, AttributeStability.STATELESS); + } + + @Override + public ModuleResolutionAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleResolutionAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleResolutionAttribute attr) { + buf.writeU2(attr.resolutionFlags()); + } + } + + public static final class ModuleTargetMapper extends AbstractAttributeMapper { + public static final ModuleTargetMapper INSTANCE = new ModuleTargetMapper(); + + private ModuleTargetMapper() { + super(NAME_MODULE_TARGET, AttributeStability.CP_REFS); + } + + @Override + public ModuleTargetAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundModuleTargetAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, ModuleTargetAttribute attr) { + buf.writeIndex(attr.targetPlatform()); + } + } + + public static final class NestHostMapper extends AbstractAttributeMapper { + public static final NestHostMapper INSTANCE = new NestHostMapper(); + + private NestHostMapper() { + super(NAME_NEST_HOST, AttributeStability.CP_REFS); + } + + @Override + public NestHostAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundNestHostAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, NestHostAttribute attr) { + buf.writeIndex(attr.nestHost()); + } + } + + public static final class NestMembersMapper extends AbstractAttributeMapper { + public static final NestMembersMapper INSTANCE = new NestMembersMapper(); + + private NestMembersMapper() { + super(NAME_NEST_MEMBERS, AttributeStability.CP_REFS); + } + + @Override + public NestMembersAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundNestMembersAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, NestMembersAttribute attr) { + buf.writeListIndices(attr.nestMembers()); + } + } + + public static final class PermittedSubclassesMapper extends AbstractAttributeMapper { + public static final PermittedSubclassesMapper INSTANCE = new PermittedSubclassesMapper(); + + private PermittedSubclassesMapper() { + super(NAME_PERMITTED_SUBCLASSES, AttributeStability.CP_REFS); + } + + @Override + public PermittedSubclassesAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundPermittedSubclassesAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, PermittedSubclassesAttribute attr) { + buf.writeListIndices(attr.permittedSubclasses()); + } + } + + public static final class RecordMapper extends AbstractAttributeMapper { + public static final RecordMapper INSTANCE = new RecordMapper(); + + private RecordMapper() { + super(NAME_RECORD, AttributeStability.CP_REFS); + } + + @Override + public RecordAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRecordAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RecordAttribute attr) { + List components = attr.components(); + buf.writeU2(components.size()); + for (RecordComponentInfo info : components) { + buf.writeIndex(info.name()); + buf.writeIndex(info.descriptor()); + buf.writeList(info.attributes()); + } + } + } + + public static final class RuntimeInvisibleAnnotationsMapper extends AbstractAttributeMapper { + public static final RuntimeInvisibleAnnotationsMapper INSTANCE = new RuntimeInvisibleAnnotationsMapper(); + + private RuntimeInvisibleAnnotationsMapper() { + super(NAME_RUNTIME_INVISIBLE_ANNOTATIONS, AttributeStability.CP_REFS); + } + + @Override + public RuntimeInvisibleAnnotationsAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + return new BoundAttribute.BoundRuntimeInvisibleAnnotationsAttribute(cf, pos); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeInvisibleAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + } + + public static final class RuntimeInvisibleParameterAnnotationsMapper extends AbstractAttributeMapper { + public static final RuntimeInvisibleParameterAnnotationsMapper INSTANCE = new RuntimeInvisibleParameterAnnotationsMapper(); + + private RuntimeInvisibleParameterAnnotationsMapper() { + super(NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, AttributeStability.CP_REFS); + } + + @Override + public RuntimeInvisibleParameterAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeInvisibleParameterAnnotationsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeInvisibleParameterAnnotationsAttribute attr) { + List> lists = attr.parameterAnnotations(); + buf.writeU1(lists.size()); + for (List list : lists) + buf.writeList(list); + } + } + + public static final class RuntimeInvisibleTypeAnnotationsMapper extends AbstractAttributeMapper { + public static final RuntimeInvisibleTypeAnnotationsMapper INSTANCE = new RuntimeInvisibleTypeAnnotationsMapper(); + + private RuntimeInvisibleTypeAnnotationsMapper() { + super(NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, AttributeStability.UNSTABLE); + } + + @Override + public RuntimeInvisibleTypeAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeInvisibleTypeAnnotationsAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeInvisibleTypeAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + } + + public static final class RuntimeVisibleAnnotationsMapper extends AbstractAttributeMapper { + public static final RuntimeVisibleAnnotationsMapper INSTANCE = new RuntimeVisibleAnnotationsMapper(); + + private RuntimeVisibleAnnotationsMapper() { + super(NAME_RUNTIME_VISIBLE_ANNOTATIONS, AttributeStability.CP_REFS); + } + + @Override + public RuntimeVisibleAnnotationsAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + return new BoundAttribute.BoundRuntimeVisibleAnnotationsAttribute(cf, pos); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeVisibleAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + } + + public static final class RuntimeVisibleParameterAnnotationsMapper extends AbstractAttributeMapper { + public static final RuntimeVisibleParameterAnnotationsMapper INSTANCE = new RuntimeVisibleParameterAnnotationsMapper(); + + private RuntimeVisibleParameterAnnotationsMapper() { + super(NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, AttributeStability.CP_REFS); + } + + @Override + public RuntimeVisibleParameterAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeVisibleParameterAnnotationsAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeVisibleParameterAnnotationsAttribute attr) { + List> lists = attr.parameterAnnotations(); + buf.writeU1(lists.size()); + for (List list : lists) + buf.writeList(list); + } + } + + public static final class RuntimeVisibleTypeAnnotationsMapper extends AbstractAttributeMapper { + public static final RuntimeVisibleTypeAnnotationsMapper INSTANCE = new RuntimeVisibleTypeAnnotationsMapper(); + + private RuntimeVisibleTypeAnnotationsMapper() { + super(NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS, AttributeStability.UNSTABLE); + } + + @Override + public RuntimeVisibleTypeAnnotationsAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundRuntimeVisibleTypeAnnotationsAttribute(e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, RuntimeVisibleTypeAnnotationsAttribute attr) { + buf.writeList(attr.annotations()); + } + } + + public static final class SignatureMapper extends AbstractAttributeMapper { + public static final SignatureMapper INSTANCE = new SignatureMapper(); + + private SignatureMapper() { + super(NAME_SIGNATURE, AttributeStability.CP_REFS); + } + + @Override + public SignatureAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSignatureAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SignatureAttribute attr) { + buf.writeIndex(attr.signature()); + } + } + + public static final class SourceDebugExtensionMapper extends AbstractAttributeMapper { + public static final SourceDebugExtensionMapper INSTANCE = new SourceDebugExtensionMapper(); + + private SourceDebugExtensionMapper() { + super(NAME_SOURCE_DEBUG_EXTENSION, AttributeStability.STATELESS); + } + + @Override + public SourceDebugExtensionAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceDebugExtensionAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SourceDebugExtensionAttribute attr) { + buf.writeBytes(attr.contents()); + } + } + + public static final class SourceFileMapper extends AbstractAttributeMapper { + public static final SourceFileMapper INSTANCE = new SourceFileMapper(); + + private SourceFileMapper() { + super(NAME_SOURCE_FILE, AttributeStability.CP_REFS); + } + + @Override + public SourceFileAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceFileAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SourceFileAttribute attr) { + buf.writeIndex(attr.sourceFile()); + } + } + + public static final class SourceIDMapper extends AbstractAttributeMapper { + public static final SourceIDMapper INSTANCE = new SourceIDMapper(); + + private SourceIDMapper() { + super(NAME_SOURCE_ID, AttributeStability.CP_REFS); + } + + @Override + public SourceIDAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSourceIDAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SourceIDAttribute attr) { + buf.writeIndex(attr.sourceId()); + } + } + + public static final class StackMapTableMapper extends AbstractAttributeMapper { + public static final StackMapTableMapper INSTANCE = new StackMapTableMapper(); + + private StackMapTableMapper() { + super(NAME_STACK_MAP_TABLE, AttributeStability.LABELS); + } + + @Override + public StackMapTableAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundStackMapTableAttribute((CodeImpl)e, cf, this, p); + } + + @Override + protected void writeBody(BufWriter b, StackMapTableAttribute attr) { + StackMapDecoder.writeFrames(b, attr.entries()); + } + } + + public static final class SyntheticMapper extends AbstractAttributeMapper { + public static final SyntheticMapper INSTANCE = new SyntheticMapper(); + + private SyntheticMapper() { + super(NAME_SYNTHETIC, AttributeStability.STATELESS, true); + } + + @Override + public SyntheticAttribute readAttribute(AttributedElement e, ClassReader cf, int p) { + return new BoundAttribute.BoundSyntheticAttribute(cf, this, p); + } + + @Override + protected void writeBody(BufWriter buf, SyntheticAttribute attr) { + // empty + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractBoundLocalVariable.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractBoundLocalVariable.class new file mode 100644 index 00000000..869e5d6e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractBoundLocalVariable.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractBoundLocalVariable.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractBoundLocalVariable.java new file mode 100644 index 00000000..d3f49e31 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractBoundLocalVariable.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.Label; +import java.lang.classfile.constantpool.Utf8Entry; + +public class AbstractBoundLocalVariable + extends AbstractElement { + protected final CodeImpl code; + protected final int offset; + private Utf8Entry nameEntry; + private Utf8Entry secondaryEntry; + + public AbstractBoundLocalVariable(CodeImpl code, int offset) { + this.code = code; + this.offset = offset; + } + + protected int nameIndex() { + return code.classReader.readU2(offset + 4); + } + + public Utf8Entry name() { + if (nameEntry == null) + nameEntry = code.constantPool().entryByIndex(nameIndex(), Utf8Entry.class); + return nameEntry; + } + + protected int secondaryIndex() { + return code.classReader.readU2(offset + 6); + } + + protected Utf8Entry secondaryEntry() { + if (secondaryEntry == null) + secondaryEntry = code.constantPool().entryByIndex(secondaryIndex(), Utf8Entry.class); + return secondaryEntry; + } + + public Label startScope() { + return code.getLabel(startPc()); + } + + public Label endScope() { + return code.getLabel(startPc() + length()); + } + + public int startPc() { + return code.classReader.readU2(offset); + } + + public int length() { + return code.classReader.readU2(offset+2); + } + + public int slot() { + return code.classReader.readU2(offset + 8); + } + + public boolean writeTo(BufWriter b) { + var lc = ((BufWriterImpl)b).labelContext(); + int startBci = lc.labelToBci(startScope()); + int endBci = lc.labelToBci(endScope()); + if (startBci == -1 || endBci == -1) { + return false; + } + int length = endBci - startBci; + b.writeU2(startBci); + b.writeU2(length); + if (b.canWriteDirect(code.constantPool())) { + b.writeU2(nameIndex()); + b.writeU2(secondaryIndex()); + } + else { + b.writeIndex(name()); + b.writeIndex(secondaryEntry()); + } + b.writeU2(slot()); + return true; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractDirectBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractDirectBuilder.class new file mode 100644 index 00000000..6be7f61b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractDirectBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractDirectBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractDirectBuilder.java new file mode 100644 index 00000000..13c2a5fb --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractDirectBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; + +import java.lang.classfile.Attribute; + +public class AbstractDirectBuilder { + protected final SplitConstantPool constantPool; + protected final ClassFileImpl context; + protected final AttributeHolder attributes = new AttributeHolder(); + protected M original; + + public AbstractDirectBuilder(SplitConstantPool constantPool, ClassFileImpl context) { + this.constantPool = constantPool; + this.context = context; + } + + public SplitConstantPool constantPool() { + return constantPool; + } + + public Optional original() { + return Optional.ofNullable(original); + } + + public void setOriginal(M original) { + this.original = original; + } + + public void writeAttribute(Attribute a) { + if (Util.isAttributeAllowed(a, context.attributesProcessingOption())) { + attributes.withAttribute(a); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractElement.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractElement.class new file mode 100644 index 00000000..580bfe2f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractElement.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractElement.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractElement.java new file mode 100644 index 00000000..37081ca5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractElement.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +public abstract class AbstractElement { + public AbstractElement() { } + + public void writeTo(DirectCodeBuilder builder) { + throw new UnsupportedOperationException(); + } + + public void writeTo(DirectClassBuilder builder) { + throw new UnsupportedOperationException(); + } + + public void writeTo(DirectMethodBuilder builder) { + throw new UnsupportedOperationException(); + } + + public void writeTo(DirectFieldBuilder builder) { + throw new UnsupportedOperationException(); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundArgumentConstantInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundArgumentConstantInstruction.class new file mode 100644 index 00000000..f8a3cbf5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundArgumentConstantInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundBranchInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundBranchInstruction.class new file mode 100644 index 00000000..7c36153e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundBranchInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundFieldInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundFieldInstruction.class new file mode 100644 index 00000000..901ab76f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundFieldInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundIncrementInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundIncrementInstruction.class new file mode 100644 index 00000000..037a3a46 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundIncrementInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInstruction.class new file mode 100644 index 00000000..dba846f8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeDynamicInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeDynamicInstruction.class new file mode 100644 index 00000000..0e4b2139 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeDynamicInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInstruction.class new file mode 100644 index 00000000..f51ee7d3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInterfaceInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInterfaceInstruction.class new file mode 100644 index 00000000..daddeedb Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundInvokeInterfaceInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundJsrInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundJsrInstruction.class new file mode 100644 index 00000000..df4181da Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundJsrInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLoadConstantInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLoadConstantInstruction.class new file mode 100644 index 00000000..4aa33bb6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLoadConstantInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLoadInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLoadInstruction.class new file mode 100644 index 00000000..63747de6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLoadInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLookupSwitchInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLookupSwitchInstruction.class new file mode 100644 index 00000000..854bbe1b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundLookupSwitchInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewMultidimensionalArrayInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewMultidimensionalArrayInstruction.class new file mode 100644 index 00000000..c43577d6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewMultidimensionalArrayInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewObjectInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewObjectInstruction.class new file mode 100644 index 00000000..247c33f8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewObjectInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewPrimitiveArrayInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewPrimitiveArrayInstruction.class new file mode 100644 index 00000000..29504bdd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewPrimitiveArrayInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewReferenceArrayInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewReferenceArrayInstruction.class new file mode 100644 index 00000000..4225c19e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundNewReferenceArrayInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundRetInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundRetInstruction.class new file mode 100644 index 00000000..55c7f13a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundRetInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundStoreInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundStoreInstruction.class new file mode 100644 index 00000000..277d7e6a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundStoreInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundTableSwitchInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundTableSwitchInstruction.class new file mode 100644 index 00000000..d122ce58 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundTableSwitchInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundTypeCheckInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundTypeCheckInstruction.class new file mode 100644 index 00000000..519a3f75 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$BoundTypeCheckInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$SwitchCaseImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$SwitchCaseImpl.class new file mode 100644 index 00000000..c07aaed7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$SwitchCaseImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArgumentConstantInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArgumentConstantInstruction.class new file mode 100644 index 00000000..69f407b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArgumentConstantInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArrayLoadInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArrayLoadInstruction.class new file mode 100644 index 00000000..d7cb7e57 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArrayLoadInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArrayStoreInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArrayStoreInstruction.class new file mode 100644 index 00000000..39b77471 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundArrayStoreInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundBranchInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundBranchInstruction.class new file mode 100644 index 00000000..4716d8d5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundBranchInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundConvertInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundConvertInstruction.class new file mode 100644 index 00000000..91dced49 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundConvertInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundFieldInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundFieldInstruction.class new file mode 100644 index 00000000..0eaa090f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundFieldInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundIncrementInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundIncrementInstruction.class new file mode 100644 index 00000000..e3c74cf3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundIncrementInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInstruction.class new file mode 100644 index 00000000..06fead7c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundIntrinsicConstantInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundIntrinsicConstantInstruction.class new file mode 100644 index 00000000..931492e8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundIntrinsicConstantInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInvokeDynamicInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInvokeDynamicInstruction.class new file mode 100644 index 00000000..ab423bc7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInvokeDynamicInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInvokeInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInvokeInstruction.class new file mode 100644 index 00000000..7a55dc92 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundInvokeInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundJsrInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundJsrInstruction.class new file mode 100644 index 00000000..3b4ce3e6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundJsrInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadConstantInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadConstantInstruction.class new file mode 100644 index 00000000..5c30a57a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadConstantInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadInstruction.class new file mode 100644 index 00000000..e93d6892 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLoadInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLookupSwitchInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLookupSwitchInstruction.class new file mode 100644 index 00000000..5ad00808 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundLookupSwitchInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundMonitorInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundMonitorInstruction.class new file mode 100644 index 00000000..bf55c540 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundMonitorInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewMultidimensionalArrayInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewMultidimensionalArrayInstruction.class new file mode 100644 index 00000000..f4564b7c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewMultidimensionalArrayInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewObjectInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewObjectInstruction.class new file mode 100644 index 00000000..1dd295a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewObjectInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewPrimitiveArrayInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewPrimitiveArrayInstruction.class new file mode 100644 index 00000000..9b3b869a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewPrimitiveArrayInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewReferenceArrayInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewReferenceArrayInstruction.class new file mode 100644 index 00000000..63cf7da5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNewReferenceArrayInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNopInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNopInstruction.class new file mode 100644 index 00000000..da252361 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundNopInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundOperatorInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundOperatorInstruction.class new file mode 100644 index 00000000..abfc1bf4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundOperatorInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundRetInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundRetInstruction.class new file mode 100644 index 00000000..ddd03c53 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundRetInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundReturnInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundReturnInstruction.class new file mode 100644 index 00000000..3b1bb650 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundReturnInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundStackInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundStackInstruction.class new file mode 100644 index 00000000..f386857d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundStackInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundStoreInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundStoreInstruction.class new file mode 100644 index 00000000..da19d550 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundStoreInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundTableSwitchInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundTableSwitchInstruction.class new file mode 100644 index 00000000..764cf992 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundTableSwitchInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundThrowInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundThrowInstruction.class new file mode 100644 index 00000000..4713fed4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundThrowInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundTypeCheckInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundTypeCheckInstruction.class new file mode 100644 index 00000000..f59b7c6b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction$UnboundTypeCheckInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction.class new file mode 100644 index 00000000..7dede9e3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction.java new file mode 100644 index 00000000..0e528cd0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractInstruction.java @@ -0,0 +1,1453 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.lang.classfile.ClassFile; +import java.lang.classfile.Instruction; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.instruction.SwitchCase; +import java.lang.classfile.constantpool.FieldRefEntry; +import java.lang.classfile.constantpool.InterfaceMethodRefEntry; +import java.lang.classfile.constantpool.InvokeDynamicEntry; +import java.lang.classfile.constantpool.LoadableConstantEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.instruction.ArrayLoadInstruction; +import java.lang.classfile.instruction.ArrayStoreInstruction; +import java.lang.classfile.instruction.BranchInstruction; +import java.lang.classfile.instruction.ConstantInstruction; +import java.lang.classfile.instruction.ConvertInstruction; +import java.lang.classfile.instruction.DiscontinuedInstruction; +import java.lang.classfile.instruction.FieldInstruction; +import java.lang.classfile.instruction.IncrementInstruction; +import java.lang.classfile.instruction.InvokeDynamicInstruction; +import java.lang.classfile.instruction.InvokeInstruction; +import java.lang.classfile.instruction.LoadInstruction; +import java.lang.classfile.instruction.LookupSwitchInstruction; +import java.lang.classfile.instruction.MonitorInstruction; +import java.lang.classfile.instruction.NewMultiArrayInstruction; +import java.lang.classfile.instruction.NewObjectInstruction; +import java.lang.classfile.instruction.NewPrimitiveArrayInstruction; +import java.lang.classfile.instruction.NewReferenceArrayInstruction; +import java.lang.classfile.instruction.NopInstruction; +import java.lang.classfile.instruction.OperatorInstruction; +import java.lang.classfile.instruction.ReturnInstruction; +import java.lang.classfile.instruction.StackInstruction; +import java.lang.classfile.instruction.StoreInstruction; +import java.lang.classfile.instruction.TableSwitchInstruction; +import java.lang.classfile.instruction.ThrowInstruction; +import java.lang.classfile.instruction.TypeCheckInstruction; +import java.lang.classfile.Label; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; + +public abstract sealed class AbstractInstruction + extends AbstractElement + implements Instruction { + + private static final String + FMT_ArgumentConstant = "ArgumentConstant[OP=%s, val=%s]", + FMT_Branch = "Branch[OP=%s]", + FMT_Field = "Field[OP=%s, field=%s.%s:%s]", + FMT_Increment = "Increment[OP=%s, slot=%d, val=%d]", + FMT_Invoke = "Invoke[OP=%s, m=%s.%s%s]", + FMT_InvokeDynamic = "InvokeDynamic[OP=%s, bsm=%s %s]", + FMT_InvokeInterface = "InvokeInterface[OP=%s, m=%s.%s%s]", + FMT_Load = "Load[OP=%s, slot=%d]", + FMT_LoadConstant = "LoadConstant[OP=%s, val=%s]", + FMT_LookupSwitch = "LookupSwitch[OP=%s]", + FMT_NewMultiArray = "NewMultiArray[OP=%s, type=%s[%d]]", + FMT_NewObj = "NewObj[OP=%s, type=%s]", + FMT_NewPrimitiveArray = "NewPrimitiveArray[OP=%s, type=%s]", + FMT_NewRefArray = "NewRefArray[OP=%s, type=%s]", + FMT_Return = "Return[OP=%s]", + FMT_Store = "Store[OP=%s, slot=%d]", + FMT_TableSwitch = "TableSwitch[OP=%s]", + FMT_Throw = "Throw[OP=%s]", + FMT_TypeCheck = "TypeCheck[OP=%s, type=%s]", + FMT_Unbound = "%s[op=%s]", + FMT_Discontinued = "Discontinued[OP=%s]"; + + final Opcode op; + final int size; + + @Override + public Opcode opcode() { + return op; + } + + @Override + public int sizeInBytes() { + return size; + } + + public AbstractInstruction(Opcode op, int size) { + this.op = op; + this.size = size; + } + + @Override + public abstract void writeTo(DirectCodeBuilder writer); + + public abstract static sealed class BoundInstruction extends AbstractInstruction { + final CodeImpl code; + final int pos; + + protected BoundInstruction(Opcode op, int size, CodeImpl code, int pos) { + super(op, size); + this.code = code; + this.pos = pos; + } + + protected Label offsetToLabel(int offset) { + return code.getLabel(pos - code.codeStart + offset); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + // Override this if the instruction has any CP references or labels! + code.classReader.copyBytesTo(writer.bytecodesBufWriter, pos, size); + } + } + + public static final class BoundLoadInstruction + extends BoundInstruction implements LoadInstruction { + + public BoundLoadInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public String toString() { + return String.format(FMT_Load, this.opcode(), slot()); + } + + @Override + public int slot() { + return switch (size) { + case 2 -> code.classReader.readU1(pos + 1); + case 4 -> code.classReader.readU2(pos + 2); + default -> throw new IllegalArgumentException("Unexpected op size: " + op.sizeIfFixed() + " -- " + op); + }; + } + + } + + public static final class BoundStoreInstruction + extends BoundInstruction implements StoreInstruction { + + public BoundStoreInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public String toString() { + return String.format(FMT_Store, this.opcode(), slot()); + } + + @Override + public int slot() { + return switch (size) { + case 2 -> code.classReader.readU1(pos + 1); + case 4 -> code.classReader.readU2(pos + 2); + default -> throw new IllegalArgumentException("Unexpected op size: " + size + " -- " + op); + }; + } + + } + + public static final class BoundIncrementInstruction + extends BoundInstruction implements IncrementInstruction { + + public BoundIncrementInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public int slot() { + return size == 6 ? code.classReader.readU2(pos + 2) : code.classReader.readU1(pos + 1); + } + + @Override + public int constant() { + return size == 6 ? code.classReader.readS2(pos + 4) : (byte) code.classReader.readS1(pos + 2); + } + + @Override + public String toString() { + return String.format(FMT_Increment, this.opcode(), slot(), constant()); + } + + } + + public static final class BoundBranchInstruction + extends BoundInstruction implements BranchInstruction { + + public BoundBranchInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public Label target() { + return offsetToLabel(branchByteOffset()); + } + + public int branchByteOffset() { + return size == 3 + ? (int) (short) code.classReader.readU2(pos + 1) + : code.classReader.readInt(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(opcode(), target()); + } + + @Override + public String toString() { + return String.format(FMT_Branch, this.opcode()); + } + + } + + public record SwitchCaseImpl(int caseValue, Label target) + implements SwitchCase { + } + + public static final class BoundLookupSwitchInstruction + extends BoundInstruction implements LookupSwitchInstruction { + + // will always need size, cache everything to there + private final int afterPad; + private final int npairs; + + BoundLookupSwitchInstruction(Opcode op, CodeImpl code, int pos) { + super(op, size(code, code.codeStart, pos), code, pos); + + this.afterPad = pos + 1 + ((4 - ((pos + 1 - code.codeStart) & 3)) & 3); + this.npairs = code.classReader.readInt(afterPad + 4); + if (npairs < 0 || npairs > code.codeLength >> 3) { + throw new IllegalArgumentException("Invalid lookupswitch npairs value: " + npairs); + } + } + + static int size(CodeImpl code, int codeStart, int pos) { + int afterPad = pos + 1 + ((4 - ((pos + 1 - codeStart) & 3)) & 3); + int pad = afterPad - (pos + 1); + int npairs = code.classReader.readInt(afterPad + 4); + return 1 + pad + 8 + npairs * 8; + } + + private int defaultOffset() { + return code.classReader.readInt(afterPad); + } + + @Override + public List cases() { + var cases = new SwitchCase[npairs]; + for (int i = 0; i < npairs; ++i) { + int z = afterPad + 8 + 8 * i; + cases[i] = SwitchCase.of(code.classReader.readInt(z), offsetToLabel(code.classReader.readInt(z + 4))); + } + return List.of(cases); + } + + @Override + public Label defaultTarget() { + return offsetToLabel(defaultOffset()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLookupSwitch(defaultTarget(), cases()); + } + + @Override + public String toString() { + return String.format(FMT_LookupSwitch, this.opcode()); + } + + } + + public static final class BoundTableSwitchInstruction + extends BoundInstruction implements TableSwitchInstruction { + + BoundTableSwitchInstruction(Opcode op, CodeImpl code, int pos) { + super(op, size(code, code.codeStart, pos), code, pos); + } + + static int size(CodeImpl code, int codeStart, int pos) { + int ap = pos + 1 + ((4 - ((pos + 1 - codeStart) & 3)) & 3); + int pad = ap - (pos + 1); + int low = code.classReader.readInt(ap + 4); + int high = code.classReader.readInt(ap + 8); + if (high < low || high - low > code.codeLength >> 2) { + throw new IllegalArgumentException("Invalid tableswitch values low: " + low + " high: " + high); + } + int cnt = high - low + 1; + return 1 + pad + 12 + cnt * 4; + } + + private int afterPadding() { + int p = pos; + return p + 1 + ((4 - ((p + 1 - code.codeStart) & 3)) & 3); + } + + @Override + public Label defaultTarget() { + return offsetToLabel(defaultOffset()); + } + + @Override + public int lowValue() { + return code.classReader.readInt(afterPadding() + 4); + } + + @Override + public int highValue() { + return code.classReader.readInt(afterPadding() + 8); + } + + @Override + public List cases() { + int low = lowValue(); + int high = highValue(); + int defOff = defaultOffset(); + var cases = new ArrayList(high - low + 1); + int z = afterPadding() + 12; + for (int i = lowValue(); i <= high; ++i) { + int off = code.classReader.readInt(z); + if (defOff != off) { + cases.add(SwitchCase.of(i, offsetToLabel(off))); + } + z += 4; + } + return Collections.unmodifiableList(cases); + } + + private int defaultOffset() { + return code.classReader.readInt(afterPadding()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTableSwitch(lowValue(), highValue(), defaultTarget(), cases()); + } + + @Override + public String toString() { + return String.format(FMT_TableSwitch, this.opcode()); + } + + } + + public static final class BoundFieldInstruction + extends BoundInstruction implements FieldInstruction { + + private FieldRefEntry fieldEntry; + + public BoundFieldInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public FieldRefEntry field() { + if (fieldEntry == null) + fieldEntry = code.classReader.readEntry(pos + 1, FieldRefEntry.class); + return fieldEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeFieldAccess(op, field()); + } + + @Override + public String toString() { + return String.format(FMT_Field, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + + } + + public static final class BoundInvokeInstruction + extends BoundInstruction implements InvokeInstruction { + MemberRefEntry methodEntry; + + public BoundInvokeInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public MemberRefEntry method() { + if (methodEntry == null) + methodEntry = code.classReader.readEntry(pos + 1, MemberRefEntry.class); + return methodEntry; + } + + @Override + public boolean isInterface() { + return method().tag() == ClassFile.TAG_INTERFACEMETHODREF; + } + + @Override + public int count() { + return 0; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeInvokeNormal(op, method()); + } + + @Override + public String toString() { + return String.format(FMT_Invoke, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + + } + + public static final class BoundInvokeInterfaceInstruction + extends BoundInstruction implements InvokeInstruction { + InterfaceMethodRefEntry methodEntry; + + public BoundInvokeInterfaceInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public MemberRefEntry method() { + if (methodEntry == null) + methodEntry = code.classReader.readEntry(pos + 1, InterfaceMethodRefEntry.class); + return methodEntry; + } + + @Override + public int count() { + return code.classReader.readU1(pos + 3); + } + + @Override + public boolean isInterface() { + return true; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeInvokeInterface(op, (InterfaceMethodRefEntry) method(), count()); + } + + @Override + public String toString() { + return String.format(FMT_InvokeInterface, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + + } + + public static final class BoundInvokeDynamicInstruction + extends BoundInstruction implements InvokeDynamicInstruction { + InvokeDynamicEntry indyEntry; + + BoundInvokeDynamicInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public InvokeDynamicEntry invokedynamic() { + if (indyEntry == null) + indyEntry = code.classReader.readEntry(pos + 1, InvokeDynamicEntry.class); + return indyEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeInvokeDynamic(invokedynamic()); + } + + @Override + public String toString() { + return String.format(FMT_InvokeDynamic, this.opcode(), bootstrapMethod(), bootstrapArgs()); + } + + } + + public static final class BoundNewObjectInstruction + extends BoundInstruction implements NewObjectInstruction { + ClassEntry classEntry; + + BoundNewObjectInstruction(CodeImpl code, int pos) { + super(Opcode.NEW, Opcode.NEW.sizeIfFixed(), code, pos); + } + + @Override + public ClassEntry className() { + if (classEntry == null) + classEntry = code.classReader.readClassEntry(pos + 1); + return classEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeNewObject(className()); + } + + @Override + public String toString() { + return String.format(FMT_NewObj, this.opcode(), className().asInternalName()); + } + + } + + public static final class BoundNewPrimitiveArrayInstruction + extends BoundInstruction implements NewPrimitiveArrayInstruction { + + public BoundNewPrimitiveArrayInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public TypeKind typeKind() { + return TypeKind.fromNewarrayCode(code.classReader.readU1(pos + 1)); + } + + @Override + public String toString() { + return String.format(FMT_NewPrimitiveArray, this.opcode(), typeKind()); + } + + } + + public static final class BoundNewReferenceArrayInstruction + extends BoundInstruction implements NewReferenceArrayInstruction { + + public BoundNewReferenceArrayInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public ClassEntry componentType() { + return code.classReader.readClassEntry(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeNewReferenceArray(componentType()); + } + + @Override + public String toString() { + return String.format(FMT_NewRefArray, this.opcode(), componentType().asInternalName()); + } + } + + public static final class BoundNewMultidimensionalArrayInstruction + extends BoundInstruction implements NewMultiArrayInstruction { + + public BoundNewMultidimensionalArrayInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public int dimensions() { + return code.classReader.readU1(pos + 3); + } + + @Override + public ClassEntry arrayType() { + return code.classReader.readClassEntry(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeNewMultidimensionalArray(dimensions(), arrayType()); + } + + @Override + public String toString() { + return String.format(FMT_NewMultiArray, this.opcode(), arrayType().asInternalName(), dimensions()); + } + + } + + public static final class BoundTypeCheckInstruction + extends BoundInstruction implements TypeCheckInstruction { + ClassEntry typeEntry; + + public BoundTypeCheckInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public ClassEntry type() { + if (typeEntry == null) + typeEntry = code.classReader.readClassEntry(pos + 1); + return typeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeTypeCheck(op, type()); + } + + @Override + public String toString() { + return String.format(FMT_TypeCheck, this.opcode(), type().asInternalName()); + } + + } + + public static final class BoundArgumentConstantInstruction + extends BoundInstruction implements ConstantInstruction.ArgumentConstantInstruction { + + public BoundArgumentConstantInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public Integer constantValue() { + return constantInt(); + } + + public int constantInt() { + return size == 3 ? code.classReader.readS2(pos + 1) : code.classReader.readS1(pos + 1); + } + + @Override + public String toString() { + return String.format(FMT_ArgumentConstant, this.opcode(), constantValue()); + } + + } + + public static final class BoundLoadConstantInstruction + extends BoundInstruction implements ConstantInstruction.LoadConstantInstruction { + + public BoundLoadConstantInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public LoadableConstantEntry constantEntry() { + return code.classReader.entryByIndex(op == Opcode.LDC + ? code.classReader.readU1(pos + 1) + : code.classReader.readU2(pos + 1), + LoadableConstantEntry.class); + } + + @Override + public ConstantDesc constantValue() { + return constantEntry().constantValue(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (writer.canWriteDirect(code.constantPool())) + super.writeTo(writer); + else + writer.writeLoadConstant(op, constantEntry()); + } + + @Override + public String toString() { + return String.format(FMT_LoadConstant, this.opcode(), constantValue()); + } + + } + + public static final class BoundJsrInstruction + extends BoundInstruction implements DiscontinuedInstruction.JsrInstruction { + + public BoundJsrInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public Label target() { + return offsetToLabel(branchByteOffset()); + } + + public int branchByteOffset() { + return size == 3 + ? code.classReader.readS2(pos + 1) + : code.classReader.readInt(pos + 1); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(opcode(), target()); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + + } + + public static final class BoundRetInstruction + extends BoundInstruction implements DiscontinuedInstruction.RetInstruction { + + public BoundRetInstruction(Opcode op, CodeImpl code, int pos) { + super(op, op.sizeIfFixed(), code, pos); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + + @Override + public int slot() { + return switch (size) { + case 2 -> code.classReader.readU1(pos + 1); + case 4 -> code.classReader.readU2(pos + 2); + default -> throw new IllegalArgumentException("Unexpected op size: " + op.sizeIfFixed() + " -- " + op); + }; + } + + } + + public abstract static sealed class UnboundInstruction extends AbstractInstruction { + + UnboundInstruction(Opcode op) { + super(op, op.sizeIfFixed()); + } + + @Override + // Override this if there is anything more that just the bytecode + public void writeTo(DirectCodeBuilder writer) { + writer.writeBytecode(op); + } + + @Override + public String toString() { + return String.format(FMT_Unbound, this.getClass().getSimpleName(), op); + } + } + + public static final class UnboundLoadInstruction + extends UnboundInstruction implements LoadInstruction { + final int slot; + + public UnboundLoadInstruction(Opcode op, int slot) { + super(op); + this.slot = slot; + } + + @Override + public int slot() { + return slot; + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLocalVar(op, slot); + } + + @Override + public String toString() { + return String.format(FMT_Load, this.opcode(), slot()); + } + + } + + public static final class UnboundStoreInstruction + extends UnboundInstruction implements StoreInstruction { + final int slot; + + public UnboundStoreInstruction(Opcode op, int slot) { + super(op); + this.slot = slot; + } + + @Override + public int slot() { + return slot; + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLocalVar(op, slot); + } + + @Override + public String toString() { + return String.format(FMT_Store, this.opcode(), slot()); + } + + } + + public static final class UnboundIncrementInstruction + extends UnboundInstruction implements IncrementInstruction { + final int slot; + final int constant; + + public UnboundIncrementInstruction(int slot, int constant) { + super(slot <= 255 && constant < 128 && constant > -127 + ? Opcode.IINC + : Opcode.IINC_W); + this.slot = slot; + this.constant = constant; + } + + @Override + public int slot() { + return slot; + } + + @Override + public int constant() { + return constant; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeIncrement(slot, constant); + } + + @Override + public String toString() { + return String.format(FMT_Increment, this.opcode(), slot(), constant()); + } + } + + public static final class UnboundBranchInstruction + extends UnboundInstruction implements BranchInstruction { + final Label target; + + public UnboundBranchInstruction(Opcode op, Label target) { + super(op); + this.target = target; + } + + @Override + public Label target() { + return target; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(op, target); + } + + @Override + public String toString() { + return String.format(FMT_Branch, this.opcode()); + } + } + + public static final class UnboundLookupSwitchInstruction + extends UnboundInstruction implements LookupSwitchInstruction { + + private final Label defaultTarget; + private final List cases; + + public UnboundLookupSwitchInstruction(Label defaultTarget, List cases) { + super(Opcode.LOOKUPSWITCH); + this.defaultTarget = defaultTarget; + this.cases = List.copyOf(cases); + } + + @Override + public List cases() { + return cases; + } + + @Override + public Label defaultTarget() { + return defaultTarget; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLookupSwitch(defaultTarget, cases); + } + + @Override + public String toString() { + return String.format(FMT_LookupSwitch, this.opcode()); + } + } + + public static final class UnboundTableSwitchInstruction + extends UnboundInstruction implements TableSwitchInstruction { + + private final int lowValue, highValue; + private final Label defaultTarget; + private final List cases; + + public UnboundTableSwitchInstruction(int lowValue, int highValue, Label defaultTarget, List cases) { + super(Opcode.TABLESWITCH); + this.lowValue = lowValue; + this.highValue = highValue; + this.defaultTarget = defaultTarget; + this.cases = List.copyOf(cases); + } + + @Override + public int lowValue() { + return lowValue; + } + + @Override + public int highValue() { + return highValue; + } + + @Override + public Label defaultTarget() { + return defaultTarget; + } + + @Override + public List cases() { + return cases; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTableSwitch(lowValue, highValue, defaultTarget, cases); + } + + @Override + public String toString() { + return String.format(FMT_TableSwitch, this.opcode()); + } + } + + public static final class UnboundReturnInstruction + extends UnboundInstruction implements ReturnInstruction { + + public UnboundReturnInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + + @Override + public String toString() { + return String.format(FMT_Return, this.opcode()); + } + + } + + public static final class UnboundThrowInstruction + extends UnboundInstruction implements ThrowInstruction { + + public UnboundThrowInstruction() { + super(Opcode.ATHROW); + } + + @Override + public String toString() { + return String.format(FMT_Throw, this.opcode()); + } + + } + + public static final class UnboundFieldInstruction + extends UnboundInstruction implements FieldInstruction { + final FieldRefEntry fieldEntry; + + public UnboundFieldInstruction(Opcode op, + FieldRefEntry fieldEntry) { + super(op); + this.fieldEntry = fieldEntry; + } + + @Override + public FieldRefEntry field() { + return fieldEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeFieldAccess(op, fieldEntry); + } + + @Override + public String toString() { + return String.format(FMT_Field, this.opcode(), this.owner().asInternalName(), this.name().stringValue(), this.type().stringValue()); + } + } + + public static final class UnboundInvokeInstruction + extends UnboundInstruction implements InvokeInstruction { + final MemberRefEntry methodEntry; + + public UnboundInvokeInstruction(Opcode op, MemberRefEntry methodEntry) { + super(op); + this.methodEntry = methodEntry; + } + + @Override + public MemberRefEntry method() { + return methodEntry; + } + + @Override + public boolean isInterface() { + return op == Opcode.INVOKEINTERFACE || methodEntry instanceof InterfaceMethodRefEntry; + } + + @Override + public int count() { + return op == Opcode.INVOKEINTERFACE + ? Util.parameterSlots(Util.methodTypeSymbol(methodEntry.nameAndType())) + 1 + : 0; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + if (op == Opcode.INVOKEINTERFACE) + writer.writeInvokeInterface(op, (InterfaceMethodRefEntry) method(), count()); + else + writer.writeInvokeNormal(op, method()); + } + + @Override + public String toString() { + return String.format(FMT_Invoke, this.opcode(), owner().asInternalName(), name().stringValue(), type().stringValue()); + } + } + + public static final class UnboundInvokeDynamicInstruction + extends UnboundInstruction implements InvokeDynamicInstruction { + final InvokeDynamicEntry indyEntry; + + public UnboundInvokeDynamicInstruction(InvokeDynamicEntry indyEntry) { + super(Opcode.INVOKEDYNAMIC); + this.indyEntry = indyEntry; + } + + @Override + public InvokeDynamicEntry invokedynamic() { + return indyEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeInvokeDynamic(invokedynamic()); + } + + @Override + public String toString() { + return String.format(FMT_InvokeDynamic, this.opcode(), bootstrapMethod(), bootstrapArgs()); + } + } + + public static final class UnboundNewObjectInstruction + extends UnboundInstruction implements NewObjectInstruction { + final ClassEntry classEntry; + + public UnboundNewObjectInstruction(ClassEntry classEntry) { + super(Opcode.NEW); + this.classEntry = classEntry; + } + + @Override + public ClassEntry className() { + return classEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewObject(className()); + } + + @Override + public String toString() { + return String.format(FMT_NewObj, this.opcode(), className().asInternalName()); + } + } + + public static final class UnboundNewPrimitiveArrayInstruction + extends UnboundInstruction implements NewPrimitiveArrayInstruction { + final TypeKind typeKind; + + public UnboundNewPrimitiveArrayInstruction(TypeKind typeKind) { + super(Opcode.NEWARRAY); + this.typeKind = typeKind; + } + + @Override + public TypeKind typeKind() { + return typeKind; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewPrimitiveArray(typeKind.newarrayCode()); + } + + @Override + public String toString() { + return String.format(FMT_NewPrimitiveArray, this.opcode(), typeKind()); + } + } + + public static final class UnboundNewReferenceArrayInstruction + extends UnboundInstruction implements NewReferenceArrayInstruction { + final ClassEntry componentTypeEntry; + + public UnboundNewReferenceArrayInstruction(ClassEntry componentTypeEntry) { + super(Opcode.ANEWARRAY); + this.componentTypeEntry = componentTypeEntry; + } + + @Override + public ClassEntry componentType() { + return componentTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewReferenceArray(componentType()); + } + + @Override + public String toString() { + return String.format(FMT_NewRefArray, this.opcode(), componentType().asInternalName()); + } + } + + public static final class UnboundNewMultidimensionalArrayInstruction + extends UnboundInstruction implements NewMultiArrayInstruction { + final ClassEntry arrayTypeEntry; + final int dimensions; + + public UnboundNewMultidimensionalArrayInstruction(ClassEntry arrayTypeEntry, + int dimensions) { + super(Opcode.MULTIANEWARRAY); + this.arrayTypeEntry = arrayTypeEntry; + this.dimensions = dimensions; + } + + @Override + public int dimensions() { + return dimensions; + } + + @Override + public ClassEntry arrayType() { + return arrayTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeNewMultidimensionalArray(dimensions(), arrayType()); + } + + @Override + public String toString() { + return String.format(FMT_NewMultiArray, this.opcode(), arrayType().asInternalName(), dimensions()); + } + + } + + public static final class UnboundArrayLoadInstruction + extends UnboundInstruction implements ArrayLoadInstruction { + + public UnboundArrayLoadInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + } + + public static final class UnboundArrayStoreInstruction + extends UnboundInstruction implements ArrayStoreInstruction { + + public UnboundArrayStoreInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + } + + public static final class UnboundTypeCheckInstruction + extends UnboundInstruction implements TypeCheckInstruction { + final ClassEntry typeEntry; + + public UnboundTypeCheckInstruction(Opcode op, ClassEntry typeEntry) { + super(op); + this.typeEntry = typeEntry; + } + + @Override + public ClassEntry type() { + return typeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeTypeCheck(op, type()); + } + + @Override + public String toString() { + return String.format(FMT_TypeCheck, this.opcode(), type().asInternalName()); + } + } + + public static final class UnboundStackInstruction + extends UnboundInstruction implements StackInstruction { + + public UnboundStackInstruction(Opcode op) { + super(op); + } + + } + + public static final class UnboundConvertInstruction + extends UnboundInstruction implements ConvertInstruction { + + public UnboundConvertInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind fromType() { + return op.primaryTypeKind(); + } + + @Override + public TypeKind toType() { + return op.secondaryTypeKind(); + } + } + + public static final class UnboundOperatorInstruction + extends UnboundInstruction implements OperatorInstruction { + + public UnboundOperatorInstruction(Opcode op) { + super(op); + } + + @Override + public TypeKind typeKind() { + return op.primaryTypeKind(); + } + } + + public static final class UnboundIntrinsicConstantInstruction + extends UnboundInstruction implements ConstantInstruction.IntrinsicConstantInstruction { + final ConstantDesc constant; + + public UnboundIntrinsicConstantInstruction(Opcode op) { + super(op); + constant = op.constantValue(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + super.writeTo(writer); + } + + @Override + public ConstantDesc constantValue() { + return constant; + } + } + + public static final class UnboundArgumentConstantInstruction + extends UnboundInstruction implements ConstantInstruction.ArgumentConstantInstruction { + final int value; + + public UnboundArgumentConstantInstruction(Opcode op, int value) { + super(op); + this.value = value; + } + + @Override + public Integer constantValue() { + return value; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeArgumentConstant(op, value); + } + + @Override + public String toString() { + return String.format(FMT_ArgumentConstant, this.opcode(), constantValue()); + } + } + + public static final class UnboundLoadConstantInstruction + extends UnboundInstruction implements ConstantInstruction.LoadConstantInstruction { + final LoadableConstantEntry constant; + + public UnboundLoadConstantInstruction(Opcode op, LoadableConstantEntry constant) { + super(op); + this.constant = constant; + } + + @Override + public LoadableConstantEntry constantEntry() { + return constant; + } + + @Override + public ConstantDesc constantValue() { + return constant.constantValue(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLoadConstant(op, constant); + } + + @Override + public String toString() { + return String.format(FMT_LoadConstant, this.opcode(), constantValue()); + } + } + + public static final class UnboundMonitorInstruction + extends UnboundInstruction implements MonitorInstruction { + + public UnboundMonitorInstruction(Opcode op) { + super(op); + } + + } + + public static final class UnboundNopInstruction + extends UnboundInstruction implements NopInstruction { + + public UnboundNopInstruction() { + super(Opcode.NOP); + } + + } + + public static final class UnboundJsrInstruction + extends UnboundInstruction implements DiscontinuedInstruction.JsrInstruction { + final Label target; + + public UnboundJsrInstruction(Opcode op, Label target) { + super(op); + this.target = target; + } + + @Override + public Label target() { + return target; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeBranch(op, target); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + } + + public static final class UnboundRetInstruction + extends UnboundInstruction implements DiscontinuedInstruction.RetInstruction { + final int slot; + + public UnboundRetInstruction(Opcode op, int slot) { + super(op); + this.slot = slot; + } + + @Override + public int slot() { + return slot; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.writeLocalVar(op, slot); + } + + @Override + public String toString() { + return String.format(FMT_Discontinued, this.opcode()); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractDynamicConstantPoolEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractDynamicConstantPoolEntry.class new file mode 100644 index 00000000..9ca1d4c6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractDynamicConstantPoolEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractMemberRefEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractMemberRefEntry.class new file mode 100644 index 00000000..2eb935b9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractMemberRefEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractNamedEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractNamedEntry.class new file mode 100644 index 00000000..0476b31a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractNamedEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefEntry.class new file mode 100644 index 00000000..61c5739e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefsEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefsEntry.class new file mode 100644 index 00000000..5f32c608 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefsEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl.class new file mode 100644 index 00000000..5aae4dc8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ConstantDynamicEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ConstantDynamicEntryImpl.class new file mode 100644 index 00000000..789a91f2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ConstantDynamicEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$CpException.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$CpException.class new file mode 100644 index 00000000..e279e5ce Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$CpException.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$DoubleEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$DoubleEntryImpl.class new file mode 100644 index 00000000..89fc4d43 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$DoubleEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$FieldRefEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$FieldRefEntryImpl.class new file mode 100644 index 00000000..62372405 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$FieldRefEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$FloatEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$FloatEntryImpl.class new file mode 100644 index 00000000..f62e7e39 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$FloatEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$IntegerEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$IntegerEntryImpl.class new file mode 100644 index 00000000..67b0e26b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$IntegerEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$InterfaceMethodRefEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$InterfaceMethodRefEntryImpl.class new file mode 100644 index 00000000..1d01aa12 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$InterfaceMethodRefEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$InvokeDynamicEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$InvokeDynamicEntryImpl.class new file mode 100644 index 00000000..5dd87de5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$InvokeDynamicEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$LongEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$LongEntryImpl.class new file mode 100644 index 00000000..4d057b80 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$LongEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodHandleEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodHandleEntryImpl.class new file mode 100644 index 00000000..3d9404a3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodHandleEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodRefEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodRefEntryImpl.class new file mode 100644 index 00000000..e0400d66 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodRefEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodTypeEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodTypeEntryImpl.class new file mode 100644 index 00000000..dbeaa362 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$MethodTypeEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ModuleEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ModuleEntryImpl.class new file mode 100644 index 00000000..f7e28f30 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$ModuleEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$NameAndTypeEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$NameAndTypeEntryImpl.class new file mode 100644 index 00000000..0d33546e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$NameAndTypeEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$PackageEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$PackageEntryImpl.class new file mode 100644 index 00000000..7fc482bf Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$PackageEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$PrimitiveEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$PrimitiveEntry.class new file mode 100644 index 00000000..6a20db80 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$PrimitiveEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$StringEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$StringEntryImpl.class new file mode 100644 index 00000000..ce238ef3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$StringEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl$State.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl$State.class new file mode 100644 index 00000000..7e64a3cd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl$State.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl.class new file mode 100644 index 00000000..ef0d83e7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry.class new file mode 100644 index 00000000..c7307a9b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry.java new file mode 100644 index 00000000..f846ca6f --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPoolEntry.java @@ -0,0 +1,1201 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.*; +import java.lang.invoke.TypeDescriptor; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import java.lang.classfile.ClassFile; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantDynamicEntry; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.BufWriter; +import java.lang.classfile.constantpool.DoubleEntry; +import java.lang.classfile.constantpool.FieldRefEntry; +import java.lang.classfile.constantpool.FloatEntry; +import java.lang.classfile.constantpool.IntegerEntry; +import java.lang.classfile.constantpool.InterfaceMethodRefEntry; +import java.lang.classfile.constantpool.InvokeDynamicEntry; +import java.lang.classfile.constantpool.LongEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.constantpool.MethodHandleEntry; +import java.lang.classfile.constantpool.MethodRefEntry; +import java.lang.classfile.constantpool.MethodTypeEntry; +import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.PackageEntry; +import java.lang.classfile.constantpool.PoolEntry; +import java.lang.classfile.constantpool.StringEntry; +import java.lang.classfile.constantpool.Utf8Entry; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.ArraysSupport; + +public abstract sealed class AbstractPoolEntry { + /* + Invariant: a {CP,BSM} entry for pool P refer only to {CP,BSM} entries + from P or P's parent. This is enforced by the various xxxEntry methods + in SplitConstantPool. As a result, code in this file can use writeU2 + instead of writeIndex. + + Cloning of entries may be a no-op if the entry is already on the right pool + (which implies that the referenced entries will also be on the right pool.) + */ + + private static final int TAG_SMEAR = 0x13C4B2D1; + static final int NON_ZERO = 0x40000000; + + public static int hash1(int tag, int x1) { + return (tag * TAG_SMEAR + x1) | NON_ZERO; + } + + public static int hash2(int tag, int x1, int x2) { + return (tag * TAG_SMEAR + x1 + 31 * x2) | NON_ZERO; + } + + // Ensure that hash is never zero + public static int hashString(int stringHash) { + return stringHash | NON_ZERO; + } + + public static Utf8Entry rawUtf8EntryFromStandardAttributeName(String name) { + //assuming standard attribute names are all US_ASCII + var raw = name.getBytes(StandardCharsets.US_ASCII); + return new Utf8EntryImpl(null, 0, raw, 0, raw.length); + } + + @SuppressWarnings("unchecked") + public static T maybeClone(ConstantPoolBuilder cp, T entry) { + return (T)((AbstractPoolEntry)entry).clone(cp); + } + + final ConstantPool constantPool; + public final byte tag; + private final int index; + private final int hash; + + private AbstractPoolEntry(ConstantPool constantPool, int tag, int index, int hash) { + this.tag = (byte) tag; + this.index = index; + this.hash = hash; + this.constantPool = constantPool; + } + + public ConstantPool constantPool() { return constantPool; } + + public int index() { return index; } + + @Override + public int hashCode() { + return hash; + } + + public byte tag() { + return tag; + } + + public int width() { + return (tag == ClassFile.TAG_LONG || tag == ClassFile.TAG_DOUBLE) ? 2 : 1; + } + + abstract PoolEntry clone(ConstantPoolBuilder cp); + + public static final class Utf8EntryImpl extends AbstractPoolEntry implements Utf8Entry { + // Processing UTF8 from the constant pool is one of the more expensive + // operations, and often, we don't actually need access to the constant + // as a string. So there are multiple layers of laziness in UTF8 + // constants. In the first stage, all we do is record the range of + // bytes in the classfile. If the size or hashCode is needed, then we + // process the raw bytes into a byte[] or char[], but do not inflate + // a String. If a string is needed, it too is inflated lazily. + // If we construct a Utf8Entry from a string, we generate the encoding + // at write time. + + enum State { RAW, BYTE, CHAR, STRING } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + private State state; + private final byte[] rawBytes; // null if initialized directly from a string + private final int offset; + private final int rawLen; + // Set in any state other than RAW + private int hash; + private int charLen; + // Set in CHAR state + private char[] chars; + // Only set in STRING state + private String stringValue; + + Utf8EntryImpl(ConstantPool cpm, int index, + byte[] rawBytes, int offset, int rawLen) { + super(cpm, ClassFile.TAG_UTF8, index, 0); + this.rawBytes = rawBytes; + this.offset = offset; + this.rawLen = rawLen; + this.state = State.RAW; + } + + Utf8EntryImpl(ConstantPool cpm, int index, String s) { + this(cpm, index, s, hashString(s.hashCode())); + } + + Utf8EntryImpl(ConstantPool cpm, int index, String s, int hash) { + super(cpm, ClassFile.TAG_UTF8, index, 0); + this.rawBytes = null; + this.offset = 0; + this.rawLen = 0; + this.state = State.STRING; + this.stringValue = s; + this.charLen = s.length(); + this.hash = hash; + } + + Utf8EntryImpl(ConstantPool cpm, int index, Utf8EntryImpl u) { + super(cpm, ClassFile.TAG_UTF8, index, 0); + this.rawBytes = u.rawBytes; + this.offset = u.offset; + this.rawLen = u.rawLen; + this.state = u.state; + this.hash = u.hash; + this.charLen = u.charLen; + this.chars = u.chars; + this.stringValue = u.stringValue; + } + + /** + * {@jvms 4.4.7} String content is encoded in modified UTF-8. + * + * Modified UTF-8 strings are encoded so that code point sequences that + * contain only non-null ASCII characters can be represented using only 1 + * byte per code point, but all code points in the Unicode codespace can be + * represented. + * + * Modified UTF-8 strings are not null-terminated. + * + * Code points in the range '\u0001' to '\u007F' are represented by a single + * byte. + * + * The null code point ('\u0000') and code points in the range '\u0080' to + * '\u07FF' are represented by a pair of bytes. + * + * Code points in the range '\u0800' to '\uFFFF' are represented by 3 bytes. + * + * Characters with code points above U+FFFF (so-called supplementary + * characters) are represented by separately encoding the two surrogate code + * units of their UTF-16 representation. Each of the surrogate code units is + * represented by three bytes. This means supplementary characters are + * represented by six bytes. + * + * The bytes of multibyte characters are stored in the class file in + * big-endian (high byte first) order. + * + * There are two differences between this format and the "standard" UTF-8 + * format. First, the null character (char)0 is encoded using the 2-byte + * format rather than the 1-byte format, so that modified UTF-8 strings + * never have embedded nulls. Second, only the 1-byte, 2-byte, and 3-byte + * formats of standard UTF-8 are used. The Java Virtual Machine does not + * recognize the four-byte format of standard UTF-8; it uses its own + * two-times-three-byte format instead. + */ + private void inflate() { + int singleBytes = JLA.countPositives(rawBytes, offset, rawLen); + int hash = ArraysSupport.hashCodeOfUnsigned(rawBytes, offset, singleBytes, 0); + if (singleBytes == rawLen) { + this.hash = hashString(hash); + charLen = rawLen; + state = State.BYTE; + } + else { + char[] chararr = new char[rawLen]; + int chararr_count = singleBytes; + // Inflate prefix of bytes to characters + JLA.inflateBytesToChars(rawBytes, offset, chararr, 0, singleBytes); + + int px = offset + singleBytes; + int utfend = offset + rawLen; + while (px < utfend) { + int c = (int) rawBytes[px] & 0xff; + switch (c >> 4) { + case 0, 1, 2, 3, 4, 5, 6, 7: { + // 0xxx xxxx + px++; + chararr[chararr_count++] = (char) c; + hash = 31 * hash + c; + break; + } + case 12, 13: { + // 110x xxxx 10xx xxxx + px += 2; + if (px > utfend) { + throw new CpException("malformed input: partial character at end"); + } + int char2 = rawBytes[px - 1]; + if ((char2 & 0xC0) != 0x80) { + throw new CpException("malformed input around byte " + px); + } + char v = (char) (((c & 0x1F) << 6) | (char2 & 0x3F)); + chararr[chararr_count++] = v; + hash = 31 * hash + v; + break; + } + case 14: { + // 1110 xxxx 10xx xxxx 10xx xxxx + px += 3; + if (px > utfend) { + throw new CpException("malformed input: partial character at end"); + } + int char2 = rawBytes[px - 2]; + int char3 = rawBytes[px - 1]; + if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) { + throw new CpException("malformed input around byte " + (px - 1)); + } + char v = (char) (((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | (char3 & 0x3F)); + chararr[chararr_count++] = v; + hash = 31 * hash + v; + break; + } + default: + // 10xx xxxx, 1111 xxxx + throw new CpException("malformed input around byte " + px); + } + } + this.hash = hashString(hash); + charLen = chararr_count; + this.chars = chararr; + state = State.CHAR; + } + + } + + @Override + public Utf8EntryImpl clone(ConstantPoolBuilder cp) { + if (cp.canWriteDirect(constantPool)) + return this; + return (state == State.STRING && rawBytes == null) + ? (Utf8EntryImpl) cp.utf8Entry(stringValue) + : ((SplitConstantPool) cp).maybeCloneUtf8Entry(this); + } + + @Override + public int hashCode() { + if (state == State.RAW) + inflate(); + return hash; + } + + @Override + public String toString() { + if (state == State.RAW) + inflate(); + if (state != State.STRING) { + stringValue = (chars != null) + ? new String(chars, 0, charLen) + : new String(rawBytes, offset, charLen, StandardCharsets.ISO_8859_1); + state = State.STRING; + } + return stringValue; + } + + @Override + public String stringValue() { + return toString(); + } + + @Override + public ConstantDesc constantValue() { + return stringValue(); + } + + @Override + public int length() { + if (state == State.RAW) + inflate(); + return charLen; + } + + @Override + public char charAt(int index) { + if (state == State.STRING) + return stringValue.charAt(index); + if (state == State.RAW) + inflate(); + return (chars != null) + ? chars[index] + : (char) rawBytes[index + offset]; + } + + @Override + public CharSequence subSequence(int start, int end) { + return toString().subSequence(start, end); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof Utf8EntryImpl u) { + return equalsUtf8(u); + } + return false; + } + + public boolean equalsUtf8(Utf8EntryImpl u) { + if (hashCode() != u.hashCode() + || length() != u.length()) + return false; + if (rawBytes != null && u.rawBytes != null) + return Arrays.equals(rawBytes, offset, offset + rawLen, + u.rawBytes, u.offset, u.offset + u.rawLen); + else if ((state == State.STRING && u.state == State.STRING)) + return stringValue.equals(u.stringValue); + else + return stringValue().equals(u.stringValue()); + } + + @Override + public boolean equalsString(String s) { + if (state == State.RAW) + inflate(); + switch (state) { + case STRING: + return stringValue.equals(s); + case CHAR: + if (charLen != s.length() || hash != hashString(s.hashCode())) + return false; + for (int i=0; i 65535) { + throw new IllegalArgumentException("string too long"); + } + pool.writeU1(tag); + pool.writeU2(charLen); + for (int i = 0; i < charLen; ++i) { + char c = stringValue.charAt(i); + if (c >= '\001' && c <= '\177') { + // Optimistic writing -- hope everything is bytes + // If not, we bail out, and alternate path patches the length + pool.writeU1((byte) c); + } + else { + int charLength = stringValue.length(); + int byteLength = i; + char c1; + for (int j = i; j < charLength; ++j) { + c1 = (stringValue).charAt(j); + if (c1 >= '\001' && c1 <= '\177') { + byteLength++; + } else if (c1 > '\u07FF') { + byteLength += 3; + } else { + byteLength += 2; + } + } + if (byteLength > 65535) { + throw new IllegalArgumentException(); + } + int byteLengthFinal = byteLength; + pool.patchInt(pool.size() - i - 2, 2, byteLengthFinal); + for (int j = i; j < charLength; ++j) { + c1 = (stringValue).charAt(j); + if (c1 >= '\001' && c1 <= '\177') { + pool.writeU1((byte) c1); + } else if (c1 > '\u07FF') { + pool.writeU1((byte) (0xE0 | c1 >> 12 & 0xF)); + pool.writeU1((byte) (0x80 | c1 >> 6 & 0x3F)); + pool.writeU1((byte) (0x80 | c1 & 0x3F)); + } else { + pool.writeU1((byte) (0xC0 | c1 >> 6 & 0x1F)); + pool.writeU1((byte) (0x80 | c1 & 0x3F)); + } + } + break; + } + } + } + } + } + + abstract static sealed class AbstractRefEntry extends AbstractPoolEntry { + protected final T ref1; + + public AbstractRefEntry(ConstantPool constantPool, int tag, int index, T ref1) { + super(constantPool, tag, index, hash1(tag, ref1.index())); + this.ref1 = ref1; + } + + public T ref1() { + return ref1; + } + + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU2(ref1.index()); + } + + @Override + public String toString() { + return tag() + " " + ref1(); + } + } + + abstract static sealed class AbstractRefsEntry + extends AbstractPoolEntry { + protected final T ref1; + protected final U ref2; + + public AbstractRefsEntry(ConstantPool constantPool, int tag, int index, T ref1, U ref2) { + super(constantPool, tag, index, hash2(tag, ref1.index(), ref2.index())); + this.ref1 = ref1; + this.ref2 = ref2; + } + + public T ref1() { + return ref1; + } + + public U ref2() { + return ref2; + } + + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU2(ref1.index()); + pool.writeU2(ref2.index()); + } + + @Override + public String toString() { + return tag() + " " + ref1 + "-" + ref2; + } + } + + abstract static sealed class AbstractNamedEntry extends AbstractRefEntry { + + public AbstractNamedEntry(ConstantPool constantPool, int tag, int index, Utf8EntryImpl ref1) { + super(constantPool, tag, index, ref1); + } + + public Utf8Entry name() { + return ref1; + } + + public String asInternalName() { + return ref1.stringValue(); + } + + @Override + public boolean equals(Object o) { + if (o == this) { return true; } + if (o instanceof AbstractNamedEntry ne) { + return tag == ne.tag() && name().equals(ref1()); + } + return false; + } + } + + public static final class ClassEntryImpl extends AbstractNamedEntry implements ClassEntry { + + public ClassDesc sym = null; + + ClassEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) { + super(cpm, ClassFile.TAG_CLASS, index, name); + } + + @Override + public ClassEntry clone(ConstantPoolBuilder cp) { + if (cp.canWriteDirect(constantPool)) { + return this; + } else { + ClassEntryImpl ret = (ClassEntryImpl)cp.classEntry(ref1); + ret.sym = sym; + return ret; + } + } + + @Override + public ClassDesc asSymbol() { + var sym = this.sym; + if (sym != null) { + return sym; + } + return this.sym = Util.toClassDesc(asInternalName()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ClassEntryImpl cce) { + return cce.name().equals(this.name()); + } else if (o instanceof ClassEntry c) { + return c.asSymbol().equals(this.asSymbol()); + } + return false; + } + } + + public static final class PackageEntryImpl extends AbstractNamedEntry implements PackageEntry { + + PackageEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) { + super(cpm, ClassFile.TAG_PACKAGE, index, name); + } + + @Override + public PackageEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.packageEntry(ref1); + } + + @Override + public PackageDesc asSymbol() { + return PackageDesc.ofInternalName(asInternalName()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof PackageEntry p) { + return name().equals(p.name()); + } + return false; + } + } + + public static final class ModuleEntryImpl extends AbstractNamedEntry implements ModuleEntry { + + ModuleEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name) { + super(cpm, ClassFile.TAG_MODULE, index, name); + } + + @Override + public ModuleEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.moduleEntry(ref1); + } + + @Override + public ModuleDesc asSymbol() { + return ModuleDesc.of(asInternalName()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof ModuleEntryImpl m) { + return name().equals(m.name()); + } + return false; + } + } + + public static final class NameAndTypeEntryImpl extends AbstractRefsEntry + implements NameAndTypeEntry { + + public TypeDescriptor typeSym = null; + + NameAndTypeEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl name, Utf8EntryImpl type) { + super(cpm, ClassFile.TAG_NAMEANDTYPE, index, name, type); + } + + @Override + public Utf8Entry name() { + return ref1; + } + + @Override + public Utf8Entry type() { + return ref2; + } + + public ClassDesc fieldTypeSymbol() { + if (typeSym instanceof ClassDesc cd) { + return cd; + } else { + return (ClassDesc)(typeSym = ClassDesc.ofDescriptor(ref2.stringValue())); + } + } + + public MethodTypeDesc methodTypeSymbol() { + if (typeSym instanceof MethodTypeDesc mtd) { + return mtd; + } else { + return (MethodTypeDesc)(typeSym = MethodTypeDesc.ofDescriptor(ref2.stringValue())); + } + } + + @Override + public NameAndTypeEntry clone(ConstantPoolBuilder cp) { + if (cp.canWriteDirect(constantPool)) { + return this; + } else { + var ret = (NameAndTypeEntryImpl)cp.nameAndTypeEntry(ref1, ref2); + ret.typeSym = typeSym; + return ret; + } + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof NameAndTypeEntryImpl nat) { + return name().equals(nat.name()) && type().equals(nat.type()); + } + return false; + } + } + + public abstract static sealed class AbstractMemberRefEntry + extends AbstractRefsEntry + implements MemberRefEntry { + + AbstractMemberRefEntry(ConstantPool cpm, int tag, int index, ClassEntryImpl owner, + NameAndTypeEntryImpl nameAndType) { + super(cpm, tag, index, owner, nameAndType); + } + + @Override + public ClassEntryImpl owner() { + return ref1; + } + + @Override + public NameAndTypeEntryImpl nameAndType() { + return ref2; + } + + @Override + public String toString() { + return tag() + " " + owner().asInternalName() + "." + nameAndType().name().stringValue() + + "-" + nameAndType().type().stringValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof AbstractMemberRefEntry m) { + return tag == m.tag() + && owner().equals(m.owner()) + && nameAndType().equals(m.nameAndType()); + } + return false; + } + } + + public static final class FieldRefEntryImpl extends AbstractMemberRefEntry implements FieldRefEntry { + + FieldRefEntryImpl(ConstantPool cpm, int index, + ClassEntryImpl owner, NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_FIELDREF, index, owner, nameAndType); + } + + @Override + public FieldRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.fieldRefEntry(ref1, ref2); + } + } + + public static final class MethodRefEntryImpl extends AbstractMemberRefEntry implements MethodRefEntry { + + MethodRefEntryImpl(ConstantPool cpm, int index, + ClassEntryImpl owner, NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_METHODREF, index, owner, nameAndType); + } + + @Override + public MethodRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodRefEntry(ref1, ref2); + } + } + + public static final class InterfaceMethodRefEntryImpl extends AbstractMemberRefEntry implements InterfaceMethodRefEntry { + + InterfaceMethodRefEntryImpl(ConstantPool cpm, int index, ClassEntryImpl owner, + NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_INTERFACEMETHODREF, index, owner, nameAndType); + } + + @Override + public InterfaceMethodRefEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.interfaceMethodRefEntry(ref1, ref2); + } + } + + public abstract static sealed class AbstractDynamicConstantPoolEntry extends AbstractPoolEntry { + + private final int bsmIndex; + private BootstrapMethodEntryImpl bootstrapMethod; + private final NameAndTypeEntryImpl nameAndType; + + AbstractDynamicConstantPoolEntry(ConstantPool cpm, int tag, int index, int hash, BootstrapMethodEntryImpl bootstrapMethod, + NameAndTypeEntryImpl nameAndType) { + super(cpm, tag, index, hash); + this.bsmIndex = bootstrapMethod.bsmIndex(); + this.bootstrapMethod = bootstrapMethod; + this.nameAndType = nameAndType; + } + + AbstractDynamicConstantPoolEntry(ConstantPool cpm, int tag, int index, int hash, int bsmIndex, + NameAndTypeEntryImpl nameAndType) { + super(cpm, tag, index, hash); + this.bsmIndex = bsmIndex; + this.bootstrapMethod = null; + this.nameAndType = nameAndType; + } + + /** + * @return the bootstrapMethod + */ + public BootstrapMethodEntryImpl bootstrap() { + if (bootstrapMethod == null) { + bootstrapMethod = (BootstrapMethodEntryImpl) constantPool.bootstrapMethodEntry(bsmIndex); + } + return bootstrapMethod; + } + + /** + * @return the bsmIndex + */ + public int bootstrapMethodIndex() { + return bsmIndex; + } + + /** + * @return the nameAndType + */ + public NameAndTypeEntryImpl nameAndType() { + return nameAndType; + } + + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU2(bsmIndex); + pool.writeU2(nameAndType.index()); + } + + @Override + public String toString() { + return tag() + " " + bootstrap() + "." + nameAndType().name().stringValue() + + "-" + nameAndType().type().stringValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof AbstractDynamicConstantPoolEntry d) { + return this.tag() == d.tag() + && bootstrap().equals(d.bootstrap()) + && nameAndType.equals(d.nameAndType()); + } + return false; + } + } + + public static final class InvokeDynamicEntryImpl + extends AbstractDynamicConstantPoolEntry + implements InvokeDynamicEntry { + + InvokeDynamicEntryImpl(ConstantPool cpm, int index, int hash, BootstrapMethodEntryImpl bootstrapMethod, + NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_INVOKEDYNAMIC, index, hash, bootstrapMethod, nameAndType); + } + + InvokeDynamicEntryImpl(ConstantPool cpm, int index, int bsmIndex, + NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_INVOKEDYNAMIC, index, hash2(ClassFile.TAG_INVOKEDYNAMIC, bsmIndex, nameAndType.index()), + bsmIndex, nameAndType); + } + + @Override + public InvokeDynamicEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.invokeDynamicEntry(bootstrap(), nameAndType()); + } + } + + public static final class ConstantDynamicEntryImpl extends AbstractDynamicConstantPoolEntry + implements ConstantDynamicEntry { + + ConstantDynamicEntryImpl(ConstantPool cpm, int index, int hash, BootstrapMethodEntryImpl bootstrapMethod, + NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_CONSTANTDYNAMIC, index, hash, bootstrapMethod, nameAndType); + } + + ConstantDynamicEntryImpl(ConstantPool cpm, int index, int bsmIndex, + NameAndTypeEntryImpl nameAndType) { + super(cpm, ClassFile.TAG_CONSTANTDYNAMIC, index, hash2(ClassFile.TAG_CONSTANTDYNAMIC, bsmIndex, nameAndType.index()), + bsmIndex, nameAndType); + } + + @Override + public ConstantDynamicEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.constantDynamicEntry(bootstrap(), nameAndType()); + } + } + + public static final class MethodHandleEntryImpl extends AbstractPoolEntry + implements MethodHandleEntry { + + private final int refKind; + private final AbstractPoolEntry.AbstractMemberRefEntry reference; + + MethodHandleEntryImpl(ConstantPool cpm, int index, int hash, int refKind, AbstractPoolEntry.AbstractMemberRefEntry + reference) { + super(cpm, ClassFile.TAG_METHODHANDLE, index, hash); + this.refKind = refKind; + this.reference = reference; + } + + MethodHandleEntryImpl(ConstantPool cpm, int index, int refKind, AbstractPoolEntry.AbstractMemberRefEntry + reference) { + super(cpm, ClassFile.TAG_METHODHANDLE, index, hash2(ClassFile.TAG_METHODHANDLE, refKind, reference.index())); + this.refKind = refKind; + this.reference = reference; + } + + @Override + public int kind() { + return refKind; + } + + @Override + public AbstractPoolEntry.AbstractMemberRefEntry reference() { + return reference; + } + + @Override + public DirectMethodHandleDesc asSymbol() { + return MethodHandleDesc.of( + DirectMethodHandleDesc.Kind.valueOf(kind(), reference() instanceof InterfaceMethodRefEntry), + ((MemberRefEntry) reference()).owner().asSymbol(), + ((MemberRefEntry) reference()).nameAndType().name().stringValue(), + ((MemberRefEntry) reference()).nameAndType().type().stringValue()); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeU1(refKind); + pool.writeU2(reference.index()); + } + + @Override + public MethodHandleEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.methodHandleEntry(refKind, reference); + } + + @Override + public String toString() { + return tag() + " " + kind() + ":" + ((MemberRefEntry) reference()).owner().asInternalName() + "." + ((MemberRefEntry) reference()).nameAndType().name().stringValue() + + "-" + ((MemberRefEntry) reference()).nameAndType().type().stringValue(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof MethodHandleEntryImpl m) { + return kind() == m.kind() + && reference.equals(m.reference()); + } + return false; + } + } + + public static final class MethodTypeEntryImpl + extends AbstractRefEntry + implements MethodTypeEntry { + + public MethodTypeDesc sym = null; + + MethodTypeEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl descriptor) { + super(cpm, ClassFile.TAG_METHODTYPE, index, descriptor); + } + + @Override + public Utf8Entry descriptor() { + return ref1; + } + + @Override + public MethodTypeEntry clone(ConstantPoolBuilder cp) { + if (cp.canWriteDirect(constantPool)) { + return this; + } else { + var ret = (MethodTypeEntryImpl)cp.methodTypeEntry(ref1); + ret.sym = sym; + return ret; + } + } + + @Override + public MethodTypeDesc asSymbol() { + var sym = this.sym; + if (sym != null) { + return sym; + } + return this.sym = MethodTypeDesc.ofDescriptor(descriptor().stringValue()); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof MethodTypeEntryImpl m) { + return descriptor().equals(m.descriptor()); + } + return false; + } + } + + public static final class StringEntryImpl + extends AbstractRefEntry + implements StringEntry { + + StringEntryImpl(ConstantPool cpm, int index, Utf8EntryImpl utf8) { + super(cpm, ClassFile.TAG_STRING, index, utf8); + } + + @Override + public Utf8EntryImpl utf8() { + return ref1; + } + + @Override + public String stringValue() { + return ref1.toString(); + } + + @Override + public ConstantDesc constantValue() { + return stringValue(); + } + + @Override + public StringEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.stringEntry(ref1); + } + + @Override + public String toString() { + return tag() + " \"" + stringValue() + "\""; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof StringEntryImpl s) { + // check utf8 rather allocating a string + return utf8().equals(s.utf8()); + } + return false; + } + + + } + + abstract static sealed class PrimitiveEntry + extends AbstractPoolEntry { + protected final T val; + + public PrimitiveEntry(ConstantPool constantPool, int tag, int index, T val) { + super(constantPool, tag, index, hash1(tag, val.hashCode())); + this.val = val; + } + + public T value() { + return val; + } + + public ConstantDesc constantValue() { + return value(); + } + + @Override + public String toString() { + return "" + tag() + value(); + } + } + + public static final class IntegerEntryImpl extends PrimitiveEntry + implements IntegerEntry { + + IntegerEntryImpl(ConstantPool cpm, int index, int i) { + super(cpm, ClassFile.TAG_INTEGER, index, i); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeInt(val); + } + + @Override + public IntegerEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.intEntry(val); + } + + @Override + public int intValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof IntegerEntryImpl e) { + return intValue() == e.intValue(); + } + return false; + } + } + + public static final class FloatEntryImpl extends PrimitiveEntry + implements FloatEntry { + + FloatEntryImpl(ConstantPool cpm, int index, float f) { + super(cpm, ClassFile.TAG_FLOAT, index, f); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeFloat(val); + } + + @Override + public FloatEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.floatEntry(val); + } + + @Override + public float floatValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof FloatEntryImpl e) { + return floatValue() == e.floatValue(); + } + return false; + } + } + + public static final class LongEntryImpl extends PrimitiveEntry implements LongEntry { + + LongEntryImpl(ConstantPool cpm, int index, long l) { + super(cpm, ClassFile.TAG_LONG, index, l); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeLong(val); + } + + @Override + public LongEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.longEntry(val); + } + + @Override + public long longValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof LongEntryImpl e) { + return longValue() == e.longValue(); + } + return false; + } + } + + public static final class DoubleEntryImpl extends PrimitiveEntry implements DoubleEntry { + + DoubleEntryImpl(ConstantPool cpm, int index, double d) { + super(cpm, ClassFile.TAG_DOUBLE, index, d); + } + + @Override + public void writeTo(BufWriter pool) { + pool.writeU1(tag); + pool.writeDouble(val); + } + + @Override + public DoubleEntry clone(ConstantPoolBuilder cp) { + return cp.canWriteDirect(constantPool) ? this : cp.doubleEntry(val); + } + + @Override + public double doubleValue() { + return value(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof DoubleEntryImpl e) { + return doubleValue() == e.doubleValue(); + } + return false; + } + } + + static class CpException extends RuntimeException { + static final long serialVersionUID = 32L; + + CpException(String s) { + super(s); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$AbstractLocalPseudo.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$AbstractLocalPseudo.class new file mode 100644 index 00000000..945e42dd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$AbstractLocalPseudo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$ExceptionCatchImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$ExceptionCatchImpl.class new file mode 100644 index 00000000..f556ffc5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$ExceptionCatchImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundCharacterRange.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundCharacterRange.class new file mode 100644 index 00000000..e3b20fef Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundCharacterRange.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundLocalVariable.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundLocalVariable.class new file mode 100644 index 00000000..8a464d8c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundLocalVariable.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundLocalVariableType.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundLocalVariableType.class new file mode 100644 index 00000000..08684ee7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction$UnboundLocalVariableType.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction.class new file mode 100644 index 00000000..904b0201 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction.java new file mode 100644 index 00000000..ffc36037 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractPseudoInstruction.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.classfile.instruction.CharacterRange; +import java.lang.classfile.instruction.ExceptionCatch; +import java.lang.classfile.instruction.LocalVariable; +import java.lang.classfile.instruction.LocalVariableType; +import java.lang.classfile.Label; +import java.lang.classfile.PseudoInstruction; + +public abstract sealed class AbstractPseudoInstruction + extends AbstractElement + implements PseudoInstruction { + + @Override + public abstract void writeTo(DirectCodeBuilder writer); + + public static final class ExceptionCatchImpl + extends AbstractPseudoInstruction + implements ExceptionCatch { + + public final ClassEntry catchTypeEntry; + public final Label handler; + public final Label tryStart; + public final Label tryEnd; + + public ExceptionCatchImpl(Label handler, Label tryStart, Label tryEnd, + ClassEntry catchTypeEntry) { + this.catchTypeEntry = catchTypeEntry; + this.handler = handler; + this.tryStart = tryStart; + this.tryEnd = tryEnd; + } + + public ExceptionCatchImpl(Label handler, Label tryStart, Label tryEnd, + Optional catchTypeEntry) { + this.catchTypeEntry = catchTypeEntry.orElse(null); + this.handler = handler; + this.tryStart = tryStart; + this.tryEnd = tryEnd; + } + + @Override + public Label tryStart() { + return tryStart; + } + + @Override + public Label handler() { + return handler; + } + + @Override + public Label tryEnd() { + return tryEnd; + } + + @Override + public Optional catchType() { + return Optional.ofNullable(catchTypeEntry); + } + + ClassEntry catchTypeEntry() { + return catchTypeEntry; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addHandler(this); + } + + @Override + public String toString() { + return String.format("ExceptionCatch[catchType=%s]", catchTypeEntry == null ? "" : catchTypeEntry.name().stringValue()); + } + } + + public static final class UnboundCharacterRange + extends AbstractPseudoInstruction + implements CharacterRange { + + public final Label startScope; + public final Label endScope; + public final int characterRangeStart; + public final int characterRangeEnd; + public final int flags; + + public UnboundCharacterRange(Label startScope, Label endScope, int characterRangeStart, + int characterRangeEnd, int flags) { + this.startScope = startScope; + this.endScope = endScope; + this.characterRangeStart = characterRangeStart; + this.characterRangeEnd = characterRangeEnd; + this.flags = flags; + } + + @Override + public Label startScope() { + return startScope; + } + + @Override + public Label endScope() { + return endScope; + } + + @Override + public int characterRangeStart() { + return characterRangeStart; + } + + @Override + public int characterRangeEnd() { + return characterRangeEnd; + } + + @Override + public int flags() { + return flags; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addCharacterRange(this); + } + + } + + private abstract static sealed class AbstractLocalPseudo extends AbstractPseudoInstruction { + protected final int slot; + protected final Utf8Entry name; + protected final Utf8Entry descriptor; + protected final Label startScope; + protected final Label endScope; + + public AbstractLocalPseudo(int slot, Utf8Entry name, Utf8Entry descriptor, Label startScope, Label endScope) { + this.slot = slot; + this.name = name; + this.descriptor = descriptor; + this.startScope = startScope; + this.endScope = endScope; + } + + public int slot() { + return slot; + } + + public Utf8Entry name() { + return name; + } + + public String nameString() { + return name.stringValue(); + } + + public Label startScope() { + return startScope; + } + + public Label endScope() { + return endScope; + } + + public boolean writeTo(BufWriter b) { + var lc = ((BufWriterImpl)b).labelContext(); + int startBci = lc.labelToBci(startScope()); + int endBci = lc.labelToBci(endScope()); + if (startBci == -1 || endBci == -1) { + return false; + } + int length = endBci - startBci; + b.writeU2(startBci); + b.writeU2(length); + b.writeIndex(name); + b.writeIndex(descriptor); + b.writeU2(slot()); + return true; + } + } + + public static final class UnboundLocalVariable extends AbstractLocalPseudo + implements LocalVariable { + + public UnboundLocalVariable(int slot, Utf8Entry name, Utf8Entry descriptor, Label startScope, Label endScope) { + super(slot, name, descriptor, startScope, endScope); + } + + @Override + public Utf8Entry type() { + return descriptor; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariable(this); + } + + @Override + public String toString() { + return "LocalVariable[Slot=" + slot() + + ", name=" + nameString() + + ", descriptor='" + type().stringValue() + + "']"; + } + } + + public static final class UnboundLocalVariableType extends AbstractLocalPseudo + implements LocalVariableType { + + public UnboundLocalVariableType(int slot, Utf8Entry name, Utf8Entry signature, Label startScope, Label endScope) { + super(slot, name, signature, startScope, endScope); + } + + @Override + public Utf8Entry signature() { + return descriptor; + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariableType(this); + } + + @Override + public String toString() { + return "LocalVariableType[Slot=" + slot() + + ", name=" + nameString() + + ", signature='" + signature().stringValue() + + "']"; + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractUnboundModel.class b/tests/test_data/std/jdk/internal/classfile/impl/AbstractUnboundModel.class new file mode 100644 index 00000000..d699314c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AbstractUnboundModel.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AbstractUnboundModel.java b/tests/test_data/std/jdk/internal/classfile/impl/AbstractUnboundModel.java new file mode 100644 index 00000000..6aa046eb --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AbstractUnboundModel.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributedElement; +import java.lang.classfile.ClassFileElement; +import java.lang.classfile.CompoundElement; + +public abstract sealed class AbstractUnboundModel + extends AbstractElement + implements CompoundElement, AttributedElement + permits BufferedCodeBuilder.Model, BufferedFieldBuilder.Model, BufferedMethodBuilder.Model { + private final List elements; + private List> attributes; + + public AbstractUnboundModel(List elements) { + this.elements = elements; + } + + @Override + public void forEachElement(Consumer consumer) { + elements.forEach(consumer); + } + + @Override + public Stream elementStream() { + return elements.stream(); + } + + @Override + public List elementList() { + return elements; + } + + @Override + public List> attributes() { + if (attributes == null) + attributes = elements.stream() + .filter(e -> e instanceof Attribute) + .>map(e -> (Attribute) e) + .toList(); + return attributes; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AccessFlagsImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AccessFlagsImpl.class new file mode 100644 index 00000000..ebf44ce1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AccessFlagsImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AccessFlagsImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/AccessFlagsImpl.java new file mode 100644 index 00000000..537110d2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AccessFlagsImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Set; +import java.lang.classfile.AccessFlags; +import java.lang.reflect.AccessFlag; + +public final class AccessFlagsImpl extends AbstractElement + implements AccessFlags { + + private final AccessFlag.Location location; + private final int flagsMask; + private Set flags; + + public AccessFlagsImpl(AccessFlag.Location location, AccessFlag... flags) { + this.location = location; + this.flagsMask = Util.flagsToBits(location, flags); + this.flags = Set.of(flags); + } + + public AccessFlagsImpl(AccessFlag.Location location, int mask) { + this.location = location; + this.flagsMask = mask; + } + + @Override + public int flagsMask() { + return flagsMask; + } + + @Override + public Set flags() { + if (flags == null) + flags = AccessFlag.maskToAccessFlags(flagsMask, location); + return flags; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setFlags(flagsMask); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.setFlags(flagsMask); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + builder.setFlags(flagsMask); + } + + @Override + public AccessFlag.Location location() { + return location; + } + + @Override + public boolean has(AccessFlag flag) { + return Util.has(location, flagsMask, flag); + } + + @Override + public String toString() { + return String.format("AccessFlags[flags=%d]", flagsMask); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$AnnotationElementImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$AnnotationElementImpl.class new file mode 100644 index 00000000..fb35665e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$AnnotationElementImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfAnnotationImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfAnnotationImpl.class new file mode 100644 index 00000000..637cac0b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfAnnotationImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfArrayImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfArrayImpl.class new file mode 100644 index 00000000..503b6662 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfArrayImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfBooleanImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfBooleanImpl.class new file mode 100644 index 00000000..33b0b72c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfBooleanImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfByteImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfByteImpl.class new file mode 100644 index 00000000..ec08bb62 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfByteImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfCharacterImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfCharacterImpl.class new file mode 100644 index 00000000..702e5264 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfCharacterImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfClassImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfClassImpl.class new file mode 100644 index 00000000..8d980844 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfClassImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfConstantImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfConstantImpl.class new file mode 100644 index 00000000..2dcbde85 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfConstantImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfDoubleImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfDoubleImpl.class new file mode 100644 index 00000000..57f6ea2f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfDoubleImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfEnumImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfEnumImpl.class new file mode 100644 index 00000000..2855064f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfEnumImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfFloatImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfFloatImpl.class new file mode 100644 index 00000000..0907819c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfFloatImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfIntegerImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfIntegerImpl.class new file mode 100644 index 00000000..6f2b56c4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfIntegerImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfLongImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfLongImpl.class new file mode 100644 index 00000000..9ada7a08 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfLongImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfShortImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfShortImpl.class new file mode 100644 index 00000000..b1f439b2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfShortImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfStringImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfStringImpl.class new file mode 100644 index 00000000..604df986 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl$OfStringImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl.class new file mode 100644 index 00000000..f8fbaeca Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl.java new file mode 100644 index 00000000..4fc2d5cf --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationImpl.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.*; +import java.lang.classfile.constantpool.*; + +import java.lang.constant.ConstantDesc; +import java.util.List; + +import static java.lang.classfile.ClassFile.*; + +public final class AnnotationImpl implements Annotation { + private final Utf8Entry className; + private final List elements; + + public AnnotationImpl(Utf8Entry className, + List elems) { + this.className = className; + this.elements = List.copyOf(elems); + } + + @Override + public Utf8Entry className() { + return className; + } + + @Override + public List elements() { + return elements; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeIndex(className()); + buf.writeList(elements()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Annotation["); + sb.append(className().stringValue()); + List evps = elements(); + if (!evps.isEmpty()) + sb.append(" ["); + for (AnnotationElement evp : evps) { + sb.append(evp.name().stringValue()) + .append("=") + .append(evp.value().toString()) + .append(", "); + } + if (!evps.isEmpty()) { + sb.delete(sb.length()-1, sb.length()); + sb.append("]"); + } + sb.append("]"); + return sb.toString(); + } + + public record AnnotationElementImpl(Utf8Entry name, + AnnotationValue value) + implements AnnotationElement { + + @Override + public void writeTo(BufWriter buf) { + buf.writeIndex(name()); + value().writeTo(buf); + } + } + + public sealed interface OfConstantImpl extends AnnotationValue.OfConstant + permits AnnotationImpl.OfStringImpl, AnnotationImpl.OfDoubleImpl, + AnnotationImpl.OfFloatImpl, AnnotationImpl.OfLongImpl, + AnnotationImpl.OfIntegerImpl, AnnotationImpl.OfShortImpl, + AnnotationImpl.OfCharacterImpl, AnnotationImpl.OfByteImpl, + AnnotationImpl.OfBooleanImpl { + + @Override + default void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(constant()); + } + + @Override + default ConstantDesc constantValue() { + return constant().constantValue(); + } + + } + + public record OfStringImpl(Utf8Entry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfString { + + @Override + public char tag() { + return AEV_STRING; + } + + @Override + public String stringValue() { + return constant().stringValue(); + } + } + + public record OfDoubleImpl(DoubleEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfDouble { + + @Override + public char tag() { + return AEV_DOUBLE; + } + + @Override + public double doubleValue() { + return constant().doubleValue(); + } + } + + public record OfFloatImpl(FloatEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfFloat { + + @Override + public char tag() { + return AEV_FLOAT; + } + + @Override + public float floatValue() { + return constant().floatValue(); + } + } + + public record OfLongImpl(LongEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfLong { + + @Override + public char tag() { + return AEV_LONG; + } + + @Override + public long longValue() { + return constant().longValue(); + } + } + + public record OfIntegerImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfInteger { + + @Override + public char tag() { + return AEV_INT; + } + + @Override + public int intValue() { + return constant().intValue(); + } + } + + public record OfShortImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfShort { + + @Override + public char tag() { + return AEV_SHORT; + } + + @Override + public short shortValue() { + return (short)constant().intValue(); + } + } + + public record OfCharacterImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfCharacter { + + @Override + public char tag() { + return AEV_CHAR; + } + + @Override + public char charValue() { + return (char)constant().intValue(); + } + } + + public record OfByteImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfByte { + + @Override + public char tag() { + return AEV_BYTE; + } + + @Override + public byte byteValue() { + return (byte)constant().intValue(); + } + } + + public record OfBooleanImpl(IntegerEntry constant) + implements AnnotationImpl.OfConstantImpl, AnnotationValue.OfBoolean { + + @Override + public char tag() { + return AEV_BOOLEAN; + } + + @Override + public boolean booleanValue() { + return constant().intValue() == 1; + } + } + + public record OfArrayImpl(List values) + implements AnnotationValue.OfArray { + + public OfArrayImpl(List values) { + this.values = List.copyOf(values); + } + + @Override + public char tag() { + return AEV_ARRAY; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeList(values); + } + + } + + public record OfEnumImpl(Utf8Entry className, Utf8Entry constantName) + implements AnnotationValue.OfEnum { + @Override + public char tag() { + return AEV_ENUM; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(className); + buf.writeIndex(constantName); + } + + } + + public record OfAnnotationImpl(Annotation annotation) + implements AnnotationValue.OfAnnotation { + @Override + public char tag() { + return AEV_ANNOTATION; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + annotation.writeTo(buf); + } + + } + + public record OfClassImpl(Utf8Entry className) + implements AnnotationValue.OfClass { + @Override + public char tag() { + return AEV_CLASS; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU1(tag()); + buf.writeIndex(className); + } + + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationReader.class b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationReader.class new file mode 100644 index 00000000..b4033f0b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationReader.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AnnotationReader.java b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationReader.java new file mode 100644 index 00000000..c082878a --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AnnotationReader.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.classfile.Annotation; +import java.lang.classfile.AnnotationElement; +import java.lang.classfile.AnnotationValue; +import java.lang.classfile.ClassReader; +import java.lang.classfile.constantpool.*; +import java.lang.classfile.TypeAnnotation; +import static java.lang.classfile.ClassFile.*; +import static java.lang.classfile.TypeAnnotation.TargetInfo.*; + +import java.util.List; +import java.lang.classfile.Label; +import java.lang.classfile.constantpool.Utf8Entry; +import jdk.internal.access.SharedSecrets; + +class AnnotationReader { + private AnnotationReader() { } + + public static List readAnnotations(ClassReader classReader, int p) { + int pos = p; + int numAnnotations = classReader.readU2(pos); + var annos = new Object[numAnnotations]; + pos += 2; + for (int i = 0; i < numAnnotations; ++i) { + annos[i] = readAnnotation(classReader, pos); + pos = skipAnnotation(classReader, pos); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(annos); + } + + public static AnnotationValue readElementValue(ClassReader classReader, int p) { + char tag = (char) classReader.readU1(p); + ++p; + return switch (tag) { + case AEV_BYTE -> new AnnotationImpl.OfByteImpl(classReader.readEntry(p, IntegerEntry.class)); + case AEV_CHAR -> new AnnotationImpl.OfCharacterImpl(classReader.readEntry(p, IntegerEntry.class)); + case AEV_DOUBLE -> new AnnotationImpl.OfDoubleImpl(classReader.readEntry(p, DoubleEntry.class)); + case AEV_FLOAT -> new AnnotationImpl.OfFloatImpl(classReader.readEntry(p, FloatEntry.class)); + case AEV_INT -> new AnnotationImpl.OfIntegerImpl(classReader.readEntry(p, IntegerEntry.class)); + case AEV_LONG -> new AnnotationImpl.OfLongImpl(classReader.readEntry(p, LongEntry.class)); + case AEV_SHORT -> new AnnotationImpl.OfShortImpl(classReader.readEntry(p, IntegerEntry.class)); + case AEV_BOOLEAN -> new AnnotationImpl.OfBooleanImpl(classReader.readEntry(p, IntegerEntry.class)); + case AEV_STRING -> new AnnotationImpl.OfStringImpl(classReader.readUtf8Entry(p)); + case AEV_ENUM -> new AnnotationImpl.OfEnumImpl(classReader.readUtf8Entry(p), classReader.readUtf8Entry(p + 2)); + case AEV_CLASS -> new AnnotationImpl.OfClassImpl(classReader.readUtf8Entry(p)); + case AEV_ANNOTATION -> new AnnotationImpl.OfAnnotationImpl(readAnnotation(classReader, p)); + case AEV_ARRAY -> { + int numValues = classReader.readU2(p); + p += 2; + var values = new Object[numValues]; + for (int i = 0; i < numValues; ++i) { + values[i] = readElementValue(classReader, p); + p = skipElementValue(classReader, p); + } + yield new AnnotationImpl.OfArrayImpl(SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(values)); + } + default -> throw new IllegalArgumentException( + "Unexpected tag '%s' in AnnotationValue, pos = %d".formatted(tag, p - 1)); + }; + } + + public static List readTypeAnnotations(ClassReader classReader, int p, LabelContext lc) { + int numTypeAnnotations = classReader.readU2(p); + p += 2; + var annotations = new Object[numTypeAnnotations]; + for (int i = 0; i < numTypeAnnotations; ++i) { + annotations[i] = readTypeAnnotation(classReader, p, lc); + p = skipTypeAnnotation(classReader, p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(annotations); + } + + public static List> readParameterAnnotations(ClassReader classReader, int p) { + int cnt = classReader.readU1(p++); + var pas = new Object[cnt]; + for (int i = 0; i < cnt; ++i) { + pas[i] = readAnnotations(classReader, p); + p = skipAnnotations(classReader, p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(pas); + } + + private static int skipElementValue(ClassReader classReader, int p) { + char tag = (char) classReader.readU1(p); + ++p; + return switch (tag) { + case 'B', 'C', 'D', 'F', 'I', 'J', 'S', 'Z', 's', 'c' -> p + 2; + case 'e' -> p + 4; + case '@' -> skipAnnotation(classReader, p); + case '[' -> { + int numValues = classReader.readU2(p); + p += 2; + for (int i = 0; i < numValues; ++i) { + p = skipElementValue(classReader, p); + } + yield p; + } + default -> throw new IllegalArgumentException( + "Unexpected tag '%s' in AnnotationValue, pos = %d".formatted(tag, p - 1)); + }; + } + + private static Annotation readAnnotation(ClassReader classReader, int p) { + Utf8Entry annotationClass = classReader.entryByIndex(classReader.readU2(p), Utf8Entry.class); + p += 2; + List elems = readAnnotationElementValuePairs(classReader, p); + return new AnnotationImpl(annotationClass, elems); + } + + private static int skipAnnotations(ClassReader classReader, int p) { + int numAnnotations = classReader.readU2(p); + p += 2; + for (int i = 0; i < numAnnotations; ++i) + p = skipAnnotation(classReader, p); + return p; + } + + private static int skipAnnotation(ClassReader classReader, int p) { + return skipElementValuePairs(classReader, p + 2); + } + + private static List readAnnotationElementValuePairs(ClassReader classReader, int p) { + int numElementValuePairs = classReader.readU2(p); + p += 2; + var annotationElements = new Object[numElementValuePairs]; + for (int i = 0; i < numElementValuePairs; ++i) { + Utf8Entry elementName = classReader.readUtf8Entry(p); + p += 2; + AnnotationValue value = readElementValue(classReader, p); + annotationElements[i] = new AnnotationImpl.AnnotationElementImpl(elementName, value); + p = skipElementValue(classReader, p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(annotationElements); + } + + private static int skipElementValuePairs(ClassReader classReader, int p) { + int numElementValuePairs = classReader.readU2(p); + p += 2; + for (int i = 0; i < numElementValuePairs; ++i) { + p = skipElementValue(classReader, p + 2); + } + return p; + } + + private static Label getLabel(LabelContext lc, int bciOffset, int targetType, int p) { + //helper method to avoid NPE + if (lc == null) throw new IllegalArgumentException("Unexpected targetType '%d' in TypeAnnotation outside of Code attribute, pos = %d".formatted(targetType, p - 1)); + return lc.getLabel(bciOffset); + } + + private static TypeAnnotation readTypeAnnotation(ClassReader classReader, int p, LabelContext lc) { + int targetType = classReader.readU1(p++); + var targetInfo = switch (targetType) { + case TAT_CLASS_TYPE_PARAMETER -> + ofClassTypeParameter(classReader.readU1(p)); + case TAT_METHOD_TYPE_PARAMETER -> + ofMethodTypeParameter(classReader.readU1(p)); + case TAT_CLASS_EXTENDS -> + ofClassExtends(classReader.readU2(p)); + case TAT_CLASS_TYPE_PARAMETER_BOUND -> + ofClassTypeParameterBound(classReader.readU1(p), classReader.readU1(p + 1)); + case TAT_METHOD_TYPE_PARAMETER_BOUND -> + ofMethodTypeParameterBound(classReader.readU1(p), classReader.readU1(p + 1)); + case TAT_FIELD -> + ofField(); + case TAT_METHOD_RETURN -> + ofMethodReturn(); + case TAT_METHOD_RECEIVER -> + ofMethodReceiver(); + case TAT_METHOD_FORMAL_PARAMETER -> + ofMethodFormalParameter(classReader.readU1(p)); + case TAT_THROWS -> + ofThrows(classReader.readU2(p)); + case TAT_LOCAL_VARIABLE -> + ofLocalVariable(readLocalVarEntries(classReader, p, lc, targetType)); + case TAT_RESOURCE_VARIABLE -> + ofResourceVariable(readLocalVarEntries(classReader, p, lc, targetType)); + case TAT_EXCEPTION_PARAMETER -> + ofExceptionParameter(classReader.readU2(p)); + case TAT_INSTANCEOF -> + ofInstanceofExpr(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_NEW -> + ofNewExpr(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_CONSTRUCTOR_REFERENCE -> + ofConstructorReference(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_METHOD_REFERENCE -> + ofMethodReference(getLabel(lc, classReader.readU2(p), targetType, p)); + case TAT_CAST -> + ofCastExpr(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT -> + ofConstructorInvocationTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_METHOD_INVOCATION_TYPE_ARGUMENT -> + ofMethodInvocationTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT -> + ofConstructorReferenceTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + case TAT_METHOD_REFERENCE_TYPE_ARGUMENT -> + ofMethodReferenceTypeArgument(getLabel(lc, classReader.readU2(p), targetType, p), classReader.readU1(p + 2)); + default -> + throw new IllegalArgumentException("Unexpected targetType '%d' in TypeAnnotation, pos = %d".formatted(targetType, p - 1)); + }; + p += targetInfo.size(); + int pathLength = classReader.readU1(p++); + TypeAnnotation.TypePathComponent[] typePath = new TypeAnnotation.TypePathComponent[pathLength]; + for (int i = 0; i < pathLength; ++i) { + int typePathKindTag = classReader.readU1(p++); + int typeArgumentIndex = classReader.readU1(p++); + typePath[i] = switch (typePathKindTag) { + case 0 -> TypeAnnotation.TypePathComponent.ARRAY; + case 1 -> TypeAnnotation.TypePathComponent.INNER_TYPE; + case 2 -> TypeAnnotation.TypePathComponent.WILDCARD; + case 3 -> new UnboundAttribute.TypePathComponentImpl(TypeAnnotation.TypePathComponent.Kind.TYPE_ARGUMENT, typeArgumentIndex); + default -> throw new IllegalArgumentException("Unknown type annotation path component kind: " + typePathKindTag); + }; + } + // the annotation info for this annotation + Utf8Entry type = classReader.readUtf8Entry(p); + p += 2; + return TypeAnnotation.of(targetInfo, List.of(typePath), type, + readAnnotationElementValuePairs(classReader, p)); + } + + private static List readLocalVarEntries(ClassReader classReader, int p, LabelContext lc, int targetType) { + int tableLength = classReader.readU2(p); + p += 2; + var entries = new Object[tableLength]; + for (int i = 0; i < tableLength; ++i) { + int startPc = classReader.readU2(p); + entries[i] = TypeAnnotation.LocalVarTargetInfo.of( + getLabel(lc, startPc, targetType, p), + getLabel(lc, startPc + classReader.readU2(p + 2), targetType, p - 2), + classReader.readU2(p + 4)); + p += 6; + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(entries); + } + + private static int skipTypeAnnotation(ClassReader classReader, int p) { + int targetType = classReader.readU1(p++); + p += switch (targetType) { + case 0x13, 0x14, 0x15 -> 0; + case 0x00, 0x01, 0x16 -> 1; + case 0x10, 0x11, 0x12, 0x17, 0x42, 0x43, 0x44, 0x45, 0x46 -> 2; + case 0x47, 0x48, 0x49, 0x4A, 0x4B -> 3; + case 0x40, 0x41 -> 2 + classReader.readU2(p) * 6; + default -> throw new IllegalArgumentException( + "Unexpected targetType '%d' in TypeAnnotation, pos = %d".formatted(targetType, p - 1)); + }; + int pathLength = classReader.readU1(p++); + p += pathLength * 2; + + // the annotation info for this annotation + p += 2; + p = skipElementValuePairs(classReader, p); + return p; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AttributeHolder.class b/tests/test_data/std/jdk/internal/classfile/impl/AttributeHolder.class new file mode 100644 index 00000000..a843194e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/AttributeHolder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/AttributeHolder.java b/tests/test_data/std/jdk/internal/classfile/impl/AttributeHolder.java new file mode 100644 index 00000000..be5176ad --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/AttributeHolder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; + +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.BufWriter; + +public class AttributeHolder { + private final List> attributes = new ArrayList<>(); + + public > void withAttribute(Attribute a) { + if (a == null) + return; + + @SuppressWarnings("unchecked") + AttributeMapper am = (AttributeMapper) a.attributeMapper(); + if (!am.allowMultiple() && isPresent(am)) { + remove(am); + } + attributes.add(a); + } + + public int size() { + return attributes.size(); + } + + public void writeTo(BufWriter buf) { + buf.writeU2(attributes.size()); + for (Attribute a : attributes) + a.writeTo(buf); + } + + boolean isPresent(AttributeMapper am) { + for (Attribute a : attributes) + if (a.attributeMapper() == am) + return true; + return false; + } + + private void remove(AttributeMapper am) { + attributes.removeIf(a -> a.attributeMapper() == am); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BlockCodeBuilderImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/BlockCodeBuilderImpl.class new file mode 100644 index 00000000..8eb8cd0f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BlockCodeBuilderImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BlockCodeBuilderImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/BlockCodeBuilderImpl.java new file mode 100644 index 00000000..66e974b4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BlockCodeBuilderImpl.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.Label; +import java.lang.classfile.TypeKind; +import java.lang.classfile.instruction.LabelTarget; + +import java.util.Objects; +import java.lang.classfile.Instruction; + +public final class BlockCodeBuilderImpl + extends NonterminalCodeBuilder + implements CodeBuilder.BlockCodeBuilder { + private final Label startLabel, endLabel, breakLabel; + private boolean reachable = true; + private boolean hasInstructions = false; + private int topLocal; + private int terminalMaxLocals; + + public BlockCodeBuilderImpl(CodeBuilder parent, Label breakLabel) { + super(parent); + this.startLabel = parent.newLabel(); + this.endLabel = parent.newLabel(); + this.breakLabel = Objects.requireNonNull(breakLabel); + } + + public void start() { + topLocal = topLocal(parent); + terminalMaxLocals = terminal.curTopLocal(); + parent.with((LabelTarget) startLabel); + } + + public void end() { + parent.with((LabelTarget) endLabel); + if (terminalMaxLocals != terminal.curTopLocal()) { + throw new IllegalStateException("Interference in local variable slot management"); + } + } + + public boolean reachable() { + return reachable; + } + + public boolean isEmpty() { + return !hasInstructions; + } + + private int topLocal(CodeBuilder parent) { + return switch (parent) { + case BlockCodeBuilderImpl b -> b.topLocal; + case ChainedCodeBuilder b -> b.terminal.curTopLocal(); + case TerminalCodeBuilder b -> b.curTopLocal(); + }; + } + + @Override + public CodeBuilder with(CodeElement element) { + parent.with(element); + + hasInstructions |= element instanceof Instruction; + + if (reachable) { + if (element instanceof Instruction i && i.opcode().isUnconditionalBranch()) + reachable = false; + } + else if (element instanceof LabelTarget) { + reachable = true; + } + return this; + } + + @Override + public Label startLabel() { + return startLabel; + } + + @Override + public Label endLabel() { + return endLabel; + } + + @Override + public int allocateLocal(TypeKind typeKind) { + int retVal = topLocal; + topLocal += typeKind.slotSize(); + return retVal; + } + + @Override + public Label breakLabel() { + return breakLabel; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.class new file mode 100644 index 00000000..da954d19 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.java new file mode 100644 index 00000000..d6876a82 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; + +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.BootstrapMethodEntry; +import java.lang.classfile.BufWriter; +import java.lang.classfile.constantpool.LoadableConstantEntry; +import java.lang.classfile.constantpool.MethodHandleEntry; + +import static jdk.internal.classfile.impl.AbstractPoolEntry.MethodHandleEntryImpl; + +public final class BootstrapMethodEntryImpl implements BootstrapMethodEntry { + + final int index; + final int hash; + private final ConstantPool constantPool; + private final MethodHandleEntryImpl handle; + private final List arguments; + + BootstrapMethodEntryImpl(ConstantPool constantPool, int bsmIndex, int hash, + MethodHandleEntryImpl handle, + List arguments) { + this.index = bsmIndex; + this.hash = hash; + this.constantPool = constantPool; + this.handle = handle; + this.arguments = List.copyOf(arguments); + } + + @Override + public ConstantPool constantPool() { + return constantPool; + } + + @Override + public MethodHandleEntry bootstrapMethod() { + return handle; + } + + @Override + public List arguments() { + return arguments; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BootstrapMethodEntry e + && e.bootstrapMethod().equals(handle) + && e.arguments().equals(arguments); + } + + static int computeHashCode(MethodHandleEntryImpl handle, + List arguments) { + return (31 * handle.hashCode() + arguments.hashCode()) | AbstractPoolEntry.NON_ZERO; + } + + @Override + public int bsmIndex() { return index; } + + @Override + public int hashCode() { + return hash; + } + + @Override + public void writeTo(BufWriter writer) { + writer.writeIndex(bootstrapMethod()); + writer.writeListIndices(arguments()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$1.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$1.class new file mode 100644 index 00000000..26a08d40 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundAnnotationDefaultAttr.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundAnnotationDefaultAttr.class new file mode 100644 index 00000000..b2fa13ed Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundAnnotationDefaultAttr.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundBootstrapMethodsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundBootstrapMethodsAttribute.class new file mode 100644 index 00000000..70feb2d0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundBootstrapMethodsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCharacterRangeTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCharacterRangeTableAttribute.class new file mode 100644 index 00000000..79946161 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCharacterRangeTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCodeAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCodeAttribute.class new file mode 100644 index 00000000..65331423 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCodeAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCompilationIDAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCompilationIDAttribute.class new file mode 100644 index 00000000..b02ac40b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundCompilationIDAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundConstantValueAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundConstantValueAttribute.class new file mode 100644 index 00000000..3e1df984 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundConstantValueAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundDeprecatedAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundDeprecatedAttribute.class new file mode 100644 index 00000000..27777aef Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundDeprecatedAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundEnclosingMethodAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundEnclosingMethodAttribute.class new file mode 100644 index 00000000..cdc79927 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundEnclosingMethodAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundExceptionsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundExceptionsAttribute.class new file mode 100644 index 00000000..d7a32535 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundExceptionsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundInnerClassesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundInnerClassesAttribute.class new file mode 100644 index 00000000..2734764f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundInnerClassesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLineNumberTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLineNumberTableAttribute.class new file mode 100644 index 00000000..7ae63126 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLineNumberTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTableAttribute.class new file mode 100644 index 00000000..5fe49acd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTypeTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTypeTableAttribute.class new file mode 100644 index 00000000..aa120925 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundLocalVariableTypeTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundMethodParametersAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundMethodParametersAttribute.class new file mode 100644 index 00000000..99037df0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundMethodParametersAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleAttribute.class new file mode 100644 index 00000000..233fed31 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleHashesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleHashesAttribute.class new file mode 100644 index 00000000..7b9b5d9c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleHashesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleMainClassAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleMainClassAttribute.class new file mode 100644 index 00000000..e1947927 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleMainClassAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModulePackagesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModulePackagesAttribute.class new file mode 100644 index 00000000..643fcfa8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModulePackagesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleResolutionAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleResolutionAttribute.class new file mode 100644 index 00000000..886f9d0b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleResolutionAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleTargetAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleTargetAttribute.class new file mode 100644 index 00000000..36aa8d61 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundModuleTargetAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundNestHostAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundNestHostAttribute.class new file mode 100644 index 00000000..819e1bc1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundNestHostAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundNestMembersAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundNestMembersAttribute.class new file mode 100644 index 00000000..b243244c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundNestMembersAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundPermittedSubclassesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundPermittedSubclassesAttribute.class new file mode 100644 index 00000000..ee373072 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundPermittedSubclassesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRecordAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRecordAttribute.class new file mode 100644 index 00000000..7a7eea7f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRecordAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleAnnotationsAttribute.class new file mode 100644 index 00000000..a1ff16e3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleParameterAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleParameterAnnotationsAttribute.class new file mode 100644 index 00000000..0a324591 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleParameterAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleTypeAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleTypeAnnotationsAttribute.class new file mode 100644 index 00000000..ecb528d6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeInvisibleTypeAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleAnnotationsAttribute.class new file mode 100644 index 00000000..157988e0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleParameterAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleParameterAnnotationsAttribute.class new file mode 100644 index 00000000..cabdedb1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleParameterAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleTypeAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleTypeAnnotationsAttribute.class new file mode 100644 index 00000000..5499f796 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundRuntimeVisibleTypeAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSignatureAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSignatureAttribute.class new file mode 100644 index 00000000..0847d208 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSignatureAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceDebugExtensionAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceDebugExtensionAttribute.class new file mode 100644 index 00000000..4e0530c2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceDebugExtensionAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceFileAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceFileAttribute.class new file mode 100644 index 00000000..2026d131 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceFileAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceIDAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceIDAttribute.class new file mode 100644 index 00000000..6767873a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSourceIDAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundStackMapTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundStackMapTableAttribute.class new file mode 100644 index 00000000..94d0cd00 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundStackMapTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSyntheticAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSyntheticAttribute.class new file mode 100644 index 00000000..60c2a32c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundSyntheticAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundUnknownAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundUnknownAttribute.class new file mode 100644 index 00000000..6d3cc577 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute$BoundUnknownAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute.class new file mode 100644 index 00000000..c7dcec75 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute.java b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute.java new file mode 100644 index 00000000..6fea4e44 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BoundAttribute.java @@ -0,0 +1,1066 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import java.lang.classfile.*; +import java.lang.classfile.attribute.*; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.constantpool.ConstantValueEntry; +import java.lang.classfile.constantpool.LoadableConstantEntry; +import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.PackageEntry; +import java.lang.classfile.constantpool.Utf8Entry; +import jdk.internal.access.SharedSecrets; + +import static java.lang.classfile.Attributes.*; + +public abstract sealed class BoundAttribute> + extends AbstractElement + implements Attribute { + + static final int NAME_AND_LENGTH_PREFIX = 6; + private final AttributeMapper mapper; + final ClassReaderImpl classReader; + final int payloadStart; + + BoundAttribute(ClassReader classReader, AttributeMapper mapper, int payloadStart) { + this.mapper = mapper; + this.classReader = (ClassReaderImpl)classReader; + this.payloadStart = payloadStart; + } + + public int payloadLen() { + return classReader.readInt(payloadStart - 4); + } + + @Override + public String attributeName() { + return mapper.name(); + } + + @Override + public AttributeMapper attributeMapper() { + return mapper; + } + + public byte[] contents() { + return classReader.readBytes(payloadStart, payloadLen()); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + builder.writeAttribute(this); + } + + @Override + @SuppressWarnings("unchecked") + public void writeTo(BufWriter buf) { + if (!buf.canWriteDirect(classReader)) + attributeMapper().writeAttribute(buf, (T) this); + else + classReader.copyBytesTo(buf, payloadStart - NAME_AND_LENGTH_PREFIX, payloadLen() + NAME_AND_LENGTH_PREFIX); + } + + public ConstantPool constantPool() { + return classReader; + } + + @Override + public String toString() { + return String.format("Attribute[name=%s]", mapper.name()); + } + + List readEntryList(int p) { + int cnt = classReader.readU2(p); + p += 2; + var entries = new Object[cnt]; + int end = p + (cnt * 2); + for (int i = 0; p < end; i++, p += 2) { + entries[i] = classReader.readEntry(p); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(entries); + } + + public static List> readAttributes(AttributedElement enclosing, ClassReader reader, int pos, + Function> customAttributes) { + int size = reader.readU2(pos); + var filled = new ArrayList>(size); + int p = pos + 2; + int cfLen = reader.classfileLength(); + var apo = ((ClassReaderImpl)reader).context().attributesProcessingOption(); + for (int i = 0; i < size; ++i) { + Utf8Entry name = reader.readUtf8Entry(p); + int len = reader.readInt(p + 2); + p += 6; + if (len < 0 || len > cfLen - p) { + throw new IllegalArgumentException("attribute " + name.stringValue() + " too big to handle"); + } + + var mapper = standardAttribute(name); + if (mapper == null) { + mapper = customAttributes.apply(name); + } + if (mapper != null) { + filled.add((Attribute)mapper.readAttribute(enclosing, reader, p)); + } else { + AttributeMapper fakeMapper = new AttributeMapper<>() { + @Override + public String name() { + return name.stringValue(); + } + + @Override + public UnknownAttribute readAttribute(AttributedElement enclosing, ClassReader cf, int pos) { + // Will never get called + throw new UnsupportedOperationException(); + } + + @Override + public void writeAttribute(BufWriter buf, UnknownAttribute attr) { + buf.writeIndex(name); + var cont = attr.contents(); + buf.writeInt(cont.length); + buf.writeBytes(cont); + } + + @Override + public boolean allowMultiple() { + return true; + } + + @Override + public AttributeMapper.AttributeStability stability() { + return AttributeStability.UNKNOWN; + } + }; + filled.add(new BoundUnknownAttribute(reader, fakeMapper, p)); + } + p += len; + } + return Collections.unmodifiableList(filled); + } + + public static final class BoundUnknownAttribute extends BoundAttribute + implements UnknownAttribute { + public BoundUnknownAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundStackMapTableAttribute + extends BoundAttribute + implements StackMapTableAttribute { + final MethodModel method; + final LabelContext ctx; + List entries = null; + + public BoundStackMapTableAttribute(CodeImpl code, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + method = code.parent().orElseThrow(); + ctx = code; + } + + @Override + public List entries() { + if (entries == null) { + entries = new StackMapDecoder(classReader, payloadStart, ctx, StackMapDecoder.initFrameLocals(method)).entries(); + } + return entries; + } + } + + public static final class BoundSyntheticAttribute extends BoundAttribute + implements SyntheticAttribute { + public BoundSyntheticAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundLineNumberTableAttribute + extends BoundAttribute + implements LineNumberTableAttribute { + private List lineNumbers = null; + + public BoundLineNumberTableAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List lineNumbers() { + if (lineNumbers == null) { + int nLn = classReader.readU2(payloadStart); + LineNumberInfo[] elements = new LineNumberInfo[nLn]; + int p = payloadStart + 2; + int pEnd = p + (nLn * 4); + for (int i = 0; p < pEnd; p += 4, i++) { + int startPc = classReader.readU2(p); + int lineNumber = classReader.readU2(p + 2); + elements[i] = LineNumberInfo.of(startPc, lineNumber); + } + lineNumbers = List.of(elements); + } + return lineNumbers; + } + } + + public static final class BoundCharacterRangeTableAttribute extends BoundAttribute implements CharacterRangeTableAttribute { + private List characterRangeTable = null; + + public BoundCharacterRangeTableAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List characterRangeTable() { + if (characterRangeTable == null) { + int nLn = classReader.readU2(payloadStart); + CharacterRangeInfo[] elements = new CharacterRangeInfo[nLn]; + int p = payloadStart + 2; + int pEnd = p + (nLn * 14); + for (int i = 0; p < pEnd; p += 14, i++) { + int startPc = classReader.readU2(p); + int endPc = classReader.readU2(p + 2); + int characterRangeStart = classReader.readInt(p + 4); + int characterRangeEnd = classReader.readInt(p + 8); + int flags = classReader.readU2(p + 12); + elements[i] = CharacterRangeInfo.of(startPc, endPc, characterRangeStart, characterRangeEnd, flags); + } + characterRangeTable = List.of(elements); + } + return characterRangeTable; + } + } + + public static final class BoundLocalVariableTableAttribute + extends BoundAttribute + implements LocalVariableTableAttribute { + private final CodeImpl codeAttribute; + private List localVars = null; + + public BoundLocalVariableTableAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + codeAttribute = (CodeImpl) enclosing; + } + + @Override + public List localVariables() { + if (localVars == null) { + int cnt = classReader.readU2(payloadStart); + BoundLocalVariable[] elements = new BoundLocalVariable[cnt]; + int p = payloadStart + 2; + int pEnd = p + (cnt * 10); + for (int i = 0; p < pEnd; p += 10, i++) { + elements[i] = new BoundLocalVariable(codeAttribute, p); + } + localVars = List.of(elements); + } + return localVars; + } + } + + public static final class BoundLocalVariableTypeTableAttribute + extends BoundAttribute + implements LocalVariableTypeTableAttribute { + private final CodeImpl codeAttribute; + private List localVars = null; + + public BoundLocalVariableTypeTableAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.codeAttribute = (CodeImpl) enclosing; + } + + @Override + public List localVariableTypes() { + if (localVars == null) { + final int cnt = classReader.readU2(payloadStart); + BoundLocalVariableType[] elements = new BoundLocalVariableType[cnt]; + int p = payloadStart + 2; + int pEnd = p + (cnt * 10); + for (int i = 0; p < pEnd; p += 10, i++) { + elements[i] = new BoundLocalVariableType(codeAttribute, p); + } + localVars = List.of(elements); + } + return localVars; + } + } + + public static final class BoundMethodParametersAttribute extends BoundAttribute + implements MethodParametersAttribute { + private List parameters = null; + + public BoundMethodParametersAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List parameters() { + if (parameters == null) { + final int cnt = classReader.readU1(payloadStart); + MethodParameterInfo[] elements = new MethodParameterInfo[cnt]; + int p = payloadStart + 1; + int pEnd = p + (cnt * 4); + for (int i = 0; p < pEnd; p += 4, i++) { + Utf8Entry name = classReader.readUtf8EntryOrNull(p); + int accessFlags = classReader.readU2(p + 2); + elements[i] = MethodParameterInfo.of(Optional.ofNullable(name), accessFlags); + } + parameters = List.of(elements); + } + return parameters; + } + } + + public static final class BoundModuleHashesAttribute extends BoundAttribute + implements ModuleHashesAttribute { + private List hashes = null; + + public BoundModuleHashesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry algorithm() { + return classReader.readUtf8Entry(payloadStart); + } + + @Override + public List hashes() { + if (hashes == null) { + final int cnt = classReader.readU2(payloadStart + 2); + ModuleHashInfo[] elements = new ModuleHashInfo[cnt]; + int p = payloadStart + 4; + //System.err.printf("%5d: ModuleHashesAttr alg = %s, cnt = %d%n", pos, algorithm(), cnt); + for (int i = 0; i < cnt; ++i) { + ModuleEntry module = classReader.readModuleEntry(p); + int hashLength = classReader.readU2(p + 2); + //System.err.printf("%5d: [%d] module = %s, hashLength = %d%n", p, i, module, hashLength); + p += 4; + elements[i] = ModuleHashInfo.of(module, classReader.readBytes(p, hashLength)); + p += hashLength; + } + hashes = List.of(elements); + } + return hashes; + } + } + + public static final class BoundRecordAttribute extends BoundAttribute + implements RecordAttribute { + private List components = null; + + public BoundRecordAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List components() { + if (components == null) { + final int cnt = classReader.readU2(payloadStart); + RecordComponentInfo[] elements = new RecordComponentInfo[cnt]; + int p = payloadStart + 2; + for (int i = 0; i < cnt; i++) { + elements[i] = new BoundRecordComponentInfo(classReader, p); + p = classReader.skipAttributeHolder(p + 4); + } + components = List.of(elements); + } + return components; + } + } + + public static final class BoundDeprecatedAttribute extends BoundAttribute + implements DeprecatedAttribute { + public BoundDeprecatedAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundSignatureAttribute extends BoundAttribute + implements SignatureAttribute { + public BoundSignatureAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry signature() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundSourceFileAttribute extends BoundAttribute + implements SourceFileAttribute { + public BoundSourceFileAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry sourceFile() { + return classReader.readUtf8Entry(payloadStart); + } + + } + + public static final class BoundModuleMainClassAttribute extends BoundAttribute implements ModuleMainClassAttribute { + public BoundModuleMainClassAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ClassEntry mainClass() { + return classReader.readClassEntry(payloadStart); + } + } + + public static final class BoundNestHostAttribute extends BoundAttribute + implements NestHostAttribute { + public BoundNestHostAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ClassEntry nestHost() { + return classReader.readClassEntry(payloadStart); + } + } + + public static final class BoundSourceDebugExtensionAttribute extends BoundAttribute + implements SourceDebugExtensionAttribute { + public BoundSourceDebugExtensionAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + } + + public static final class BoundConstantValueAttribute extends BoundAttribute + implements ConstantValueAttribute { + public BoundConstantValueAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ConstantValueEntry constant() { + return classReader.readEntry(payloadStart, ConstantValueEntry.class); + } + + } + + public static final class BoundModuleTargetAttribute extends BoundAttribute + implements ModuleTargetAttribute { + public BoundModuleTargetAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry targetPlatform() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundCompilationIDAttribute extends BoundAttribute + implements CompilationIDAttribute { + public BoundCompilationIDAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry compilationId() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundSourceIDAttribute extends BoundAttribute + implements SourceIDAttribute { + public BoundSourceIDAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public Utf8Entry sourceId() { + return classReader.readUtf8Entry(payloadStart); + } + } + + public static final class BoundModuleResolutionAttribute extends BoundAttribute + implements ModuleResolutionAttribute { + public BoundModuleResolutionAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public int resolutionFlags() { + return classReader.readU2(payloadStart); + } + } + + public static final class BoundExceptionsAttribute extends BoundAttribute + implements ExceptionsAttribute { + private List exceptions = null; + + public BoundExceptionsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List exceptions() { + if (exceptions == null) { + exceptions = readEntryList(payloadStart); + } + return exceptions; + } + } + + public static final class BoundModuleAttribute extends BoundAttribute + implements ModuleAttribute { + private List requires = null; + private List exports = null; + private List opens = null; + private List uses = null; + private List provides = null; + + public BoundModuleAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ModuleEntry moduleName() { + return classReader.readModuleEntry(payloadStart); + } + + @Override + public int moduleFlagsMask() { + return classReader.readU2(payloadStart + 2); + } + + @Override + public Optional moduleVersion() { + return Optional.ofNullable(classReader.readUtf8EntryOrNull(payloadStart + 4)); + } + + @Override + public List requires() { + if (requires == null) { + structure(); + } + return requires; + } + + @Override + public List exports() { + if (exports == null) { + structure(); + } + return exports; + } + + @Override + public List opens() { + if (opens == null) { + structure(); + } + return opens; + } + + @Override + public List uses() { + if (uses == null) { + structure(); + } + return uses; + } + + @Override + public List provides() { + if (provides == null) { + structure(); + } + return provides; + } + + private void structure() { + int p = payloadStart + 8; + + { + int cnt = classReader.readU2(payloadStart + 6); + ModuleRequireInfo[] elements = new ModuleRequireInfo[cnt]; + int end = p + (cnt * 6); + for (int i = 0; p < end; p += 6, i++) { + elements[i] = ModuleRequireInfo.of(classReader.readModuleEntry(p), + classReader.readU2(p + 2), + classReader.readEntryOrNull(p + 4, Utf8Entry.class)); + } + requires = List.of(elements); + } + + { + int cnt = classReader.readU2(p); + p += 2; + ModuleExportInfo[] elements = new ModuleExportInfo[cnt]; + for (int i = 0; i < cnt; i++) { + PackageEntry pe = classReader.readPackageEntry(p); + int exportFlags = classReader.readU2(p + 2); + p += 4; + List exportsTo = readEntryList(p); + p += 2 + exportsTo.size() * 2; + elements[i] = ModuleExportInfo.of(pe, exportFlags, exportsTo); + } + exports = List.of(elements); + } + + { + int cnt = classReader.readU2(p); + p += 2; + ModuleOpenInfo[] elements = new ModuleOpenInfo[cnt]; + for (int i = 0; i < cnt; i++) { + PackageEntry po = classReader.readPackageEntry(p); + int opensFlags = classReader.readU2(p + 2); + p += 4; + List opensTo = readEntryList(p); + p += 2 + opensTo.size() * 2; + elements[i] = ModuleOpenInfo.of(po, opensFlags, opensTo); + } + opens = List.of(elements); + } + + { + uses = readEntryList(p); + p += 2 + uses.size() * 2; + int cnt = classReader.readU2(p); + p += 2; + ModuleProvideInfo[] elements = new ModuleProvideInfo[cnt]; + provides = new ArrayList<>(cnt); + for (int i = 0; i < cnt; i++) { + ClassEntry c = classReader.readClassEntry(p); + p += 2; + List providesWith = readEntryList(p); + p += 2 + providesWith.size() * 2; + elements[i] = ModuleProvideInfo.of(c, providesWith); + } + provides = List.of(elements); + } + } + } + + public static final class BoundModulePackagesAttribute extends BoundAttribute + implements ModulePackagesAttribute { + private List packages = null; + + public BoundModulePackagesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List packages() { + if (packages == null) { + packages = readEntryList(payloadStart); + } + return packages; + } + } + + public static final class BoundNestMembersAttribute extends BoundAttribute + implements NestMembersAttribute { + + private List members = null; + + public BoundNestMembersAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List nestMembers() { + if (members == null) { + members = readEntryList(payloadStart); + } + return members; + } + } + + public static final class BoundBootstrapMethodsAttribute extends BoundAttribute + implements BootstrapMethodsAttribute { + + private List bootstraps = null; + private final int size; + + public BoundBootstrapMethodsAttribute(ClassReader reader, AttributeMapper mapper, int pos) { + super(reader, mapper, pos); + size = classReader.readU2(pos); + } + + @Override + public int bootstrapMethodsSize() { + return size; + } + + @Override + public List bootstrapMethods() { + if (bootstraps == null) { + BootstrapMethodEntry[] bs = new BootstrapMethodEntry[size]; + int p = payloadStart + 2; + for (int i = 0; i < size; ++i) { + final AbstractPoolEntry.MethodHandleEntryImpl handle + = (AbstractPoolEntry.MethodHandleEntryImpl) classReader.readMethodHandleEntry(p); + final List args = readEntryList(p + 2); + p += 4 + args.size() * 2; + int hash = BootstrapMethodEntryImpl.computeHashCode(handle, args); + bs[i] = new BootstrapMethodEntryImpl(classReader, i, hash, handle, args); + } + bootstraps = List.of(bs); + } + return bootstraps; + } + } + + public static final class BoundInnerClassesAttribute extends BoundAttribute + implements InnerClassesAttribute { + private List classes; + + public BoundInnerClassesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List classes() { + if (classes == null) { + final int cnt = classReader.readU2(payloadStart); + int p = payloadStart + 2; + InnerClassInfo[] elements = new InnerClassInfo[cnt]; + for (int i = 0; i < cnt; i++) { + ClassEntry innerClass = classReader.readClassEntry(p); + var outerClass = classReader.readEntryOrNull(p + 2, ClassEntry.class); + var innerName = classReader.readEntryOrNull(p + 4, Utf8Entry.class); + int flags = classReader.readU2(p + 6); + p += 8; + elements[i] = InnerClassInfo.of(innerClass, Optional.ofNullable(outerClass), Optional.ofNullable(innerName), flags); + } + classes = List.of(elements); + } + return classes; + } + } + + public static final class BoundEnclosingMethodAttribute extends BoundAttribute + implements EnclosingMethodAttribute { + public BoundEnclosingMethodAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public ClassEntry enclosingClass() { + return classReader.readClassEntry(payloadStart); + } + + @Override + public Optional enclosingMethod() { + return Optional.ofNullable(classReader.readEntryOrNull(payloadStart + 2, NameAndTypeEntry.class)); + } + } + + public static final class BoundAnnotationDefaultAttr + extends BoundAttribute + implements AnnotationDefaultAttribute { + private AnnotationValue annotationValue; + + public BoundAnnotationDefaultAttr(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public AnnotationValue defaultValue() { + if (annotationValue == null) + annotationValue = AnnotationReader.readElementValue(classReader, payloadStart); + return annotationValue; + } + } + + public static final class BoundRuntimeVisibleTypeAnnotationsAttribute extends BoundAttribute + implements RuntimeVisibleTypeAnnotationsAttribute { + + private final LabelContext labelContext; + + public BoundRuntimeVisibleTypeAnnotationsAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.labelContext = (enclosing instanceof LabelContext lc) ? lc : null; + } + + @Override + public List annotations() { + return AnnotationReader.readTypeAnnotations(classReader, payloadStart, labelContext); + } + } + + public static final class BoundRuntimeInvisibleTypeAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleTypeAnnotationsAttribute { + public BoundRuntimeInvisibleTypeAnnotationsAttribute(AttributedElement enclosing, ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + this.labelContext = (enclosing instanceof LabelContext lc) ? lc : null; + } + + private final LabelContext labelContext; + + @Override + public List annotations() { + return AnnotationReader.readTypeAnnotations(classReader, payloadStart, labelContext); + } + } + + public static final class BoundRuntimeVisibleParameterAnnotationsAttribute + extends BoundAttribute + implements RuntimeVisibleParameterAnnotationsAttribute { + + public BoundRuntimeVisibleParameterAnnotationsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List> parameterAnnotations() { + return AnnotationReader.readParameterAnnotations(classReader, payloadStart); + } + } + + public static final class BoundRuntimeInvisibleParameterAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleParameterAnnotationsAttribute { + + public BoundRuntimeInvisibleParameterAnnotationsAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List> parameterAnnotations() { + return AnnotationReader.readParameterAnnotations(classReader, payloadStart); + } + } + + public static final class BoundRuntimeInvisibleAnnotationsAttribute + extends BoundAttribute + implements RuntimeInvisibleAnnotationsAttribute { + private List inflated; + + public BoundRuntimeInvisibleAnnotationsAttribute(ClassReader cf, + int payloadStart) { + super(cf, Attributes.runtimeInvisibleAnnotations(), payloadStart); + } + + @Override + public List annotations() { + if (inflated == null) + inflated = AnnotationReader.readAnnotations(classReader, payloadStart); + return inflated; + } + } + + public static final class BoundRuntimeVisibleAnnotationsAttribute + extends BoundAttribute + implements RuntimeVisibleAnnotationsAttribute { + private List inflated; + + public BoundRuntimeVisibleAnnotationsAttribute(ClassReader cf, + int payloadStart) { + super(cf, Attributes.runtimeVisibleAnnotations(), payloadStart); + } + + @Override + public List annotations() { + if (inflated == null) + inflated = AnnotationReader.readAnnotations(classReader, payloadStart); + return inflated; + } + } + + public static final class BoundPermittedSubclassesAttribute extends BoundAttribute + implements PermittedSubclassesAttribute { + private List permittedSubclasses = null; + + public BoundPermittedSubclassesAttribute(ClassReader cf, AttributeMapper mapper, int pos) { + super(cf, mapper, pos); + } + + @Override + public List permittedSubclasses() { + if (permittedSubclasses == null) { + permittedSubclasses = readEntryList(payloadStart); + } + return permittedSubclasses; + } + } + + public abstract static sealed class BoundCodeAttribute + extends BoundAttribute + implements CodeAttribute + permits CodeImpl { + protected final int codeStart; + protected final int codeLength; + protected final int codeEnd; + protected final int attributePos; + protected final int exceptionHandlerPos; + protected final int exceptionHandlerCnt; + protected final MethodModel enclosingMethod; + + public BoundCodeAttribute(AttributedElement enclosing, + ClassReader reader, + AttributeMapper mapper, + int payloadStart) { + super(reader, mapper, payloadStart); + this.codeLength = classReader.readInt(payloadStart + 4); + this.enclosingMethod = (MethodModel) enclosing; + this.codeStart = payloadStart + 8; + this.codeEnd = codeStart + codeLength; + this.exceptionHandlerPos = codeEnd; + this.exceptionHandlerCnt = classReader.readU2(exceptionHandlerPos); + this.attributePos = exceptionHandlerPos + 2 + exceptionHandlerCnt * 8; + } + + // CodeAttribute + + @Override + public int maxStack() { + return classReader.readU2(payloadStart); + } + + @Override + public int maxLocals() { + return classReader.readU2(payloadStart + 2); + } + + @Override + public int codeLength() { + return codeLength; + } + + @Override + public byte[] codeArray() { + return classReader.readBytes(payloadStart + 8, codeLength()); + } + } + + /** + * {@return the attribute mapper for a standard attribute} + * + * @param name the name of the attribute to find + */ + public static AttributeMapper standardAttribute(Utf8Entry name) { + // critical bootstrap path, so no lambdas nor method handles here + return switch (name.hashCode()) { + case 0x46699ff2 -> + name.equalsString(NAME_ANNOTATION_DEFAULT) ? annotationDefault() : null; + case 0x5208e184 -> + name.equalsString(NAME_BOOTSTRAP_METHODS) ? bootstrapMethods() : null; + case 0xcb60907a -> + name.equalsString(NAME_CHARACTER_RANGE_TABLE) ? characterRangeTable() : null; + case 0x4020220d -> + name.equalsString(NAME_CODE) ? code() : null; + case 0xc20dd1fe -> + name.equalsString(NAME_COMPILATION_ID) ? compilationId() : null; + case 0xcab1940d -> + name.equalsString(NAME_CONSTANT_VALUE) ? constantValue() : null; + case 0x558641d3 -> + name.equalsString(NAME_DEPRECATED) ? deprecated() : null; + case 0x51d443cd -> + name.equalsString(NAME_ENCLOSING_METHOD) ? enclosingMethod() : null; + case 0x687c1624 -> + name.equalsString(NAME_EXCEPTIONS) ? exceptions() : null; + case 0x7adb2910 -> + name.equalsString(NAME_INNER_CLASSES) ? innerClasses() : null; + case 0x653f0551 -> + name.equalsString(NAME_LINE_NUMBER_TABLE) ? lineNumberTable() : null; + case 0x64c75927 -> + name.equalsString(NAME_LOCAL_VARIABLE_TABLE) ? localVariableTable() : null; + case 0x6697f98d -> + name.equalsString(NAME_LOCAL_VARIABLE_TYPE_TABLE) ? localVariableTypeTable() : null; + case 0xdbb0cdcb -> + name.equalsString(NAME_METHOD_PARAMETERS) ? methodParameters() : null; + case 0xc9b0928c -> + name.equalsString(NAME_MODULE) ? module() : null; + case 0x41cd27e8 -> + name.equalsString(NAME_MODULE_HASHES) ? moduleHashes() : null; + case 0x7deb0a13 -> + name.equalsString(NAME_MODULE_MAIN_CLASS) ? moduleMainClass() : null; + case 0x6706ff99 -> + name.equalsString(NAME_MODULE_PACKAGES) ? modulePackages() : null; + case 0x60272858 -> + name.equalsString(NAME_MODULE_RESOLUTION) ? moduleResolution() : null; + case 0x5646d73d -> + name.equalsString(NAME_MODULE_TARGET) ? moduleTarget() : null; + case 0x50336c40 -> + name.equalsString(NAME_NEST_HOST) ? nestHost() : null; + case 0x4735ab81 -> + name.equalsString(NAME_NEST_MEMBERS) ? nestMembers() : null; + case 0x7100d9fe -> + name.equalsString(NAME_PERMITTED_SUBCLASSES) ? permittedSubclasses() : null; + case 0xd1ab5871 -> + name.equalsString(NAME_RECORD) ? record() : null; + case 0x7588550f -> + name.equalsString(NAME_RUNTIME_INVISIBLE_ANNOTATIONS) ? runtimeInvisibleAnnotations() : null; + case 0xcc74da30 -> + name.equalsString(NAME_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS) ? runtimeInvisibleParameterAnnotations() : null; + case 0xf67697f5 -> + name.equalsString(NAME_RUNTIME_INVISIBLE_TYPE_ANNOTATIONS) ? runtimeInvisibleTypeAnnotations() : null; + case 0xe0837d2a -> + name.equalsString(NAME_RUNTIME_VISIBLE_ANNOTATIONS) ? runtimeVisibleAnnotations() : null; + case 0xc945a075 -> + name.equalsString(NAME_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS) ? runtimeVisibleParameterAnnotations() : null; + case 0x611a3a90 -> + name.equalsString(NAME_RUNTIME_VISIBLE_TYPE_ANNOTATIONS) ? runtimeVisibleTypeAnnotations() : null; + case 0xf76fb898 -> + name.equalsString(NAME_SIGNATURE) ? signature() : null; + case 0x6b41b047 -> + name.equalsString(NAME_SOURCE_DEBUG_EXTENSION) ? sourceDebugExtension() : null; + case 0x748c2857 -> + name.equalsString(NAME_SOURCE_FILE) ? sourceFile() : null; + case 0x6bf13a96 -> + name.equalsString(NAME_SOURCE_ID) ? sourceId() : null; + case 0xfa85ee5a -> + name.equalsString(NAME_STACK_MAP_TABLE) ? stackMapTable() : null; + case 0xf2670725 -> + name.equalsString(NAME_SYNTHETIC) ? synthetic() : null; + default -> null; + }; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundCharacterRange.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundCharacterRange.class new file mode 100644 index 00000000..9b3fcf6a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundCharacterRange.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundCharacterRange.java b/tests/test_data/std/jdk/internal/classfile/impl/BoundCharacterRange.java new file mode 100644 index 00000000..374ba5bc --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BoundCharacterRange.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.classfile.Label; +import java.lang.classfile.instruction.CharacterRange; + +public final class BoundCharacterRange + extends AbstractElement + implements CharacterRange { + + private final CodeImpl code; + private final int offset; + + public BoundCharacterRange(CodeImpl code, int offset) { + this.code = code; + this.offset = offset; + } + + int startPc() { + return code.classReader.readU2(offset); + } + + int endPc() { + return code.classReader.readU2(offset + 2); + } + + @Override + public int characterRangeStart() { + return code.classReader.readInt(offset + 4); + } + + @Override + public int characterRangeEnd() { + return code.classReader.readInt(offset + 8); + } + + @Override + public int flags() { + return code.classReader.readU2(offset + 12); + } + + @Override + public Label startScope() { + return code.getLabel(startPc()); + } + + @Override + public Label endScope() { + return code.getLabel(endPc() + 1); + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.addCharacterRange(this); + } + + @Override + public String toString() { + return String.format("CharacterRange[startScope=%s, endScope=%s, characterRangeStart=%s, characterRangeEnd=%s, flags=%d]", + startScope(), endScope(), characterRangeStart(), characterRangeEnd(), flags()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariable.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariable.class new file mode 100644 index 00000000..b8cabb95 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariable.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariable.java b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariable.java new file mode 100644 index 00000000..a5953c86 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariable.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.classfile.attribute.LocalVariableInfo; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.classfile.instruction.LocalVariable; + +public final class BoundLocalVariable + extends AbstractBoundLocalVariable + implements LocalVariableInfo, + LocalVariable { + + public BoundLocalVariable(CodeImpl code, int offset) { + super(code, offset); + } + + @Override + public Utf8Entry type() { + return secondaryEntry(); + } + + @Override + public ClassDesc typeSymbol() { + return ClassDesc.ofDescriptor(type().stringValue()); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariable(this); + } + + @Override + public String toString() { + return String.format("LocalVariable[name=%s, slot=%d, type=%s]", name().stringValue(), slot(), type().stringValue()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariableType.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariableType.class new file mode 100644 index 00000000..53e4c9e8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariableType.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariableType.java b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariableType.java new file mode 100644 index 00000000..9fff650a --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BoundLocalVariableType.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.attribute.LocalVariableTypeInfo; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.classfile.instruction.LocalVariableType; + +public final class BoundLocalVariableType + extends AbstractBoundLocalVariable + implements LocalVariableTypeInfo, + LocalVariableType { + + public BoundLocalVariableType(CodeImpl code, int offset) { + super(code, offset); + } + + @Override + public Utf8Entry signature() { + return secondaryEntry(); + } + + @Override + public void writeTo(DirectCodeBuilder writer) { + writer.addLocalVariableType(this); + } + + @Override + public String toString() { + return String.format("LocalVariableType[name=%s, slot=%d, signature=%s]", name().stringValue(), slot(), signature().stringValue()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundRecordComponentInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/BoundRecordComponentInfo.class new file mode 100644 index 00000000..7e838a47 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BoundRecordComponentInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BoundRecordComponentInfo.java b/tests/test_data/std/jdk/internal/classfile/impl/BoundRecordComponentInfo.java new file mode 100644 index 00000000..d2e0dc07 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BoundRecordComponentInfo.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; + +import java.lang.classfile.Attribute; +import java.lang.classfile.ClassReader; +import java.lang.classfile.attribute.RecordComponentInfo; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class BoundRecordComponentInfo + implements RecordComponentInfo { + + private final ClassReader reader; + private final int startPos, attributesPos; + private List> attributes; + + public BoundRecordComponentInfo(ClassReader reader, int startPos) { + this.reader = reader; + this.startPos = startPos; + attributesPos = startPos + 4; + } + + @Override + public Utf8Entry name() { + return reader.readUtf8Entry(startPos); + } + + @Override + public Utf8Entry descriptor() { + return reader.readUtf8Entry(startPos + 2); + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(null, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufWriterImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/BufWriterImpl.class new file mode 100644 index 00000000..3938c931 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufWriterImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufWriterImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/BufWriterImpl.java new file mode 100644 index 00000000..87161588 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BufWriterImpl.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.WritableElement; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.PoolEntry; + +public final class BufWriterImpl implements BufWriter { + + private final ConstantPoolBuilder constantPool; + private final ClassFileImpl context; + private LabelContext labelContext; + private final ClassEntry thisClass; + private final int majorVersion; + byte[] elems; + int offset = 0; + + public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context) { + this(constantPool, context, 64, null, 0); + } + + public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context, int initialSize) { + this(constantPool, context, initialSize, null, 0); + } + + public BufWriterImpl(ConstantPoolBuilder constantPool, ClassFileImpl context, int initialSize, ClassEntry thisClass, int majorVersion) { + this.constantPool = constantPool; + this.context = context; + elems = new byte[initialSize]; + this.thisClass = thisClass; + this.majorVersion = majorVersion; + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + public LabelContext labelContext() { + return labelContext; + } + + public void setLabelContext(LabelContext labelContext) { + this.labelContext = labelContext; + } + @Override + public boolean canWriteDirect(ConstantPool other) { + return constantPool.canWriteDirect(other); + } + + public ClassEntry thisClass() { + return thisClass; + } + + public int getMajorVersion() { + return majorVersion; + } + + public ClassFileImpl context() { + return context; + } + + @Override + public void writeU1(int x) { + writeIntBytes(1, x); + } + + @Override + public void writeU2(int x) { + writeIntBytes(2, x); + } + + @Override + public void writeInt(int x) { + writeIntBytes(4, x); + } + + @Override + public void writeFloat(float x) { + writeInt(Float.floatToIntBits(x)); + } + + @Override + public void writeLong(long x) { + writeIntBytes(8, x); + } + + @Override + public void writeDouble(double x) { + writeLong(Double.doubleToLongBits(x)); + } + + @Override + public void writeBytes(byte[] arr) { + writeBytes(arr, 0, arr.length); + } + + @Override + public void writeBytes(BufWriter other) { + BufWriterImpl o = (BufWriterImpl) other; + writeBytes(o.elems, 0, o.offset); + } + + @Override + public void writeBytes(byte[] arr, int start, int length) { + reserveSpace(length); + System.arraycopy(arr, start, elems, offset, length); + offset += length; + } + + @Override + public void patchInt(int offset, int size, int value) { + int prevOffset = this.offset; + this.offset = offset; + writeIntBytes(size, value); + this.offset = prevOffset; + } + + @Override + public void writeIntBytes(int intSize, long intValue) { + reserveSpace(intSize); + for (int i = 0; i < intSize; i++) { + elems[offset++] = (byte) ((intValue >> 8 * (intSize - i - 1)) & 0xFF); + } + } + + @Override + public void reserveSpace(int freeBytes) { + if (offset + freeBytes > elems.length) { + int newsize = elems.length * 2; + while (offset + freeBytes > newsize) { + newsize *= 2; + } + elems = Arrays.copyOf(elems, newsize); + } + } + + @Override + public int size() { + return offset; + } + + public ByteBuffer asByteBuffer() { + return ByteBuffer.wrap(elems, 0, offset).slice(); + } + + @Override + public void copyTo(byte[] array, int bufferOffset) { + System.arraycopy(elems, 0, array, bufferOffset, size()); + } + + // writeIndex methods ensure that any CP info written + // is relative to the correct constant pool + + @Override + public void writeIndex(PoolEntry entry) { + int idx = AbstractPoolEntry.maybeClone(constantPool, entry).index(); + if (idx < 1 || idx > Character.MAX_VALUE) + throw new IllegalArgumentException(idx + " is not a valid index. Entry: " + entry); + writeU2(idx); + } + + @Override + public void writeIndexOrZero(PoolEntry entry) { + if (entry == null || entry.index() == 0) + writeU2(0); + else + writeIndex(entry); + } + + @Override + public> void writeList(List list) { + writeU2(list.size()); + for (T t : list) { + t.writeTo(this); + } + } + + @Override + public void writeListIndices(List list) { + writeU2(list.size()); + for (PoolEntry info : list) { + writeIndex(info); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder$Model$1.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder$Model$1.class new file mode 100644 index 00000000..2331c43d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder$Model$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder$Model.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder$Model.class new file mode 100644 index 00000000..e2897a21 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder$Model.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder.class new file mode 100644 index 00000000..f22a3de1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder.java new file mode 100644 index 00000000..8603c77a --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BufferedCodeBuilder.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; +import java.lang.classfile.TypeKind; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.Label; +import java.lang.classfile.MethodModel; +import java.lang.classfile.instruction.ExceptionCatch; +import java.lang.classfile.instruction.IncrementInstruction; +import java.lang.classfile.instruction.LoadInstruction; +import java.lang.classfile.instruction.StoreInstruction; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +public final class BufferedCodeBuilder + implements TerminalCodeBuilder { + private final SplitConstantPool constantPool; + private final ClassFileImpl context; + private final List elements = new ArrayList<>(); + private final LabelImpl startLabel, endLabel; + private final CodeModel original; + private final MethodInfo methodInfo; + private boolean finished; + private int maxLocals; + + public BufferedCodeBuilder(MethodInfo methodInfo, + SplitConstantPool constantPool, + ClassFileImpl context, + CodeModel original) { + this.constantPool = constantPool; + this.context = context; + this.startLabel = new LabelImpl(this, -1); + this.endLabel = new LabelImpl(this, -1); + this.original = original; + this.methodInfo = methodInfo; + this.maxLocals = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodTypeSymbol()); + if (original != null) + this.maxLocals = Math.max(this.maxLocals, original.maxLocals()); + + elements.add(startLabel); + } + + @Override + public Optional original() { + return Optional.ofNullable(original); + } + + @Override + public Label newLabel() { + return new LabelImpl(this, -1); + } + + @Override + public Label startLabel() { + return startLabel; + } + + @Override + public Label endLabel() { + return endLabel; + } + + @Override + public int receiverSlot() { + return methodInfo.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return methodInfo.parameterSlot(paramNo); + } + + public int curTopLocal() { + return maxLocals; + } + + @Override + public int allocateLocal(TypeKind typeKind) { + int retVal = maxLocals; + maxLocals += typeKind.slotSize(); + return retVal; + } + + @Override + public Label getLabel(int bci) { + throw new UnsupportedOperationException("Lookup by BCI not supported by BufferedCodeBuilder"); + } + + @Override + public int labelToBci(Label label) { + throw new UnsupportedOperationException("Label mapping not supported by BufferedCodeBuilder"); + } + + @Override + public void setLabelTarget(Label label, int bci) { + throw new UnsupportedOperationException("Label mapping not supported by BufferedCodeBuilder"); + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + @Override + public CodeBuilder with(CodeElement element) { + if (finished) + throw new IllegalStateException("Can't add elements after traversal"); + elements.add(element); + return this; + } + + @Override + public String toString() { + return String.format("CodeModel[id=%d]", System.identityHashCode(this)); + } + + public BufferedCodeBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + public CodeModel toModel() { + if (!finished) { + elements.add(endLabel); + finished = true; + } + return new Model(); + } + + public final class Model + extends AbstractUnboundModel + implements CodeModel { + + private Model() { + super(elements); + } + + @Override + public List exceptionHandlers() { + return elements.stream() + .filter(x -> x instanceof ExceptionCatch) + .map(x -> (ExceptionCatch) x) + .toList(); + } + + @Override + public int maxLocals() { + for (CodeElement element : elements) { + if (element instanceof LoadInstruction i) + maxLocals = Math.max(maxLocals, i.slot() + i.typeKind().slotSize()); + else if (element instanceof StoreInstruction i) + maxLocals = Math.max(maxLocals, i.slot() + i.typeKind().slotSize()); + else if (element instanceof IncrementInstruction i) + maxLocals = Math.max(maxLocals, i.slot() + 1); + } + return maxLocals; + } + + @Override + public int maxStack() { + throw new UnsupportedOperationException("nyi"); + } + + @Override + public Optional parent() { + return Optional.empty(); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.withCode(new Consumer<>() { + @Override + public void accept(CodeBuilder cb) { + forEachElement(cb); + } + }); + } + + public void writeTo(BufWriter buf) { + DirectCodeBuilder.build(methodInfo, cb -> elements.forEach(cb), constantPool, context, null).writeTo(buf); + } + + @Override + public String toString() { + return String.format("CodeModel[id=%s]", Integer.toHexString(System.identityHashCode(this))); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder$Model$1.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder$Model$1.class new file mode 100644 index 00000000..49ddbe60 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder$Model$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder$Model.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder$Model.class new file mode 100644 index 00000000..b96d95d2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder$Model.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder.class new file mode 100644 index 00000000..09310059 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder.java new file mode 100644 index 00000000..2ba7ef14 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BufferedFieldBuilder.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.*; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class BufferedFieldBuilder + implements TerminalFieldBuilder { + private final SplitConstantPool constantPool; + private final ClassFileImpl context; + private final Utf8Entry name; + private final Utf8Entry desc; + private final List elements = new ArrayList<>(); + private AccessFlags flags; + private final FieldModel original; + + public BufferedFieldBuilder(SplitConstantPool constantPool, + ClassFileImpl context, + Utf8Entry name, + Utf8Entry type, + FieldModel original) { + this.constantPool = constantPool; + this.context = context; + this.name = name; + this.desc = type; + this.flags = AccessFlags.ofField(); + this.original = original; + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + @Override + public Optional original() { + return Optional.ofNullable(original); + } + + @Override + public FieldBuilder with(FieldElement element) { + elements.add(element); + if (element instanceof AccessFlags f) this.flags = f; + return this; + } + + public BufferedFieldBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + public FieldModel toModel() { + return new Model(); + } + + public final class Model + extends AbstractUnboundModel + implements FieldModel { + public Model() { + super(elements); + } + + @Override + public Optional parent() { + FieldModel fm = original().orElse(null); + return fm == null? Optional.empty() : fm.parent(); + } + + @Override + public AccessFlags flags() { + return flags; + } + + @Override + public Utf8Entry fieldName() { + return name; + } + + @Override + public Utf8Entry fieldType() { + return desc; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.withField(name, desc, new Consumer() { + @Override + public void accept(FieldBuilder fieldBuilder) { + elements.forEach(fieldBuilder); + } + }); + } + + @Override + public void writeTo(BufWriter buf) { + DirectFieldBuilder fb = new DirectFieldBuilder(constantPool, context, name, desc, null); + elements.forEach(fb); + fb.writeTo(buf); + } + + @Override + public String toString() { + return String.format("FieldModel[fieldName=%s, fieldType=%s, flags=%d]", name.stringValue(), desc.stringValue(), flags.flagsMask()); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder$Model$1.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder$Model$1.class new file mode 100644 index 00000000..0d00ee4c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder$Model$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder$Model.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder$Model.class new file mode 100644 index 00000000..186b7458 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder$Model.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder.class new file mode 100644 index 00000000..07c2b6a4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder.java new file mode 100644 index 00000000..c6cf9d8e --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BufferedMethodBuilder.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.AccessFlags; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassModel; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.MethodElement; +import java.lang.classfile.MethodModel; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class BufferedMethodBuilder + implements TerminalMethodBuilder, MethodInfo { + private final List elements; + private final SplitConstantPool constantPool; + private final ClassFileImpl context; + private final Utf8Entry name; + private final Utf8Entry desc; + private AccessFlags flags; + private final MethodModel original; + private int[] parameterSlots; + MethodTypeDesc mDesc; + + public BufferedMethodBuilder(SplitConstantPool constantPool, + ClassFileImpl context, + Utf8Entry nameInfo, + Utf8Entry typeInfo, + MethodModel original) { + this.elements = new ArrayList<>(); + this.constantPool = constantPool; + this.context = context; + this.name = nameInfo; + this.desc = typeInfo; + this.flags = AccessFlags.ofMethod(); + this.original = original; + } + + @Override + public MethodBuilder with(MethodElement element) { + elements.add(element); + if (element instanceof AccessFlags f) this.flags = f; + return this; + } + + @Override + public ConstantPoolBuilder constantPool() { + return constantPool; + } + + @Override + public Optional original() { + return Optional.ofNullable(original); + } + + @Override + public Utf8Entry methodName() { + return name; + } + + @Override + public Utf8Entry methodType() { + return desc; + } + + @Override + public MethodTypeDesc methodTypeSymbol() { + if (mDesc == null) { + if (original instanceof MethodInfo mi) { + mDesc = mi.methodTypeSymbol(); + } else { + mDesc = MethodTypeDesc.ofDescriptor(methodType().stringValue()); + } + } + return mDesc; + } + + @Override + public int methodFlags() { + return flags.flagsMask(); + } + + @Override + public int parameterSlot(int paramNo) { + if (parameterSlots == null) + parameterSlots = Util.parseParameterSlots(methodFlags(), methodTypeSymbol()); + return parameterSlots[paramNo]; + } + + @Override + public MethodBuilder withCode(Consumer handler) { + return with(new BufferedCodeBuilder(this, constantPool, context, null) + .run(handler) + .toModel()); + } + + @Override + public MethodBuilder transformCode(CodeModel code, CodeTransform transform) { + BufferedCodeBuilder builder = new BufferedCodeBuilder(this, constantPool, context, code); + builder.transform(code, transform); + return with(builder.toModel()); + } + + @Override + public BufferedCodeBuilder bufferedCodeBuilder(CodeModel original) { + return new BufferedCodeBuilder(this, constantPool, context, original); + } + + public BufferedMethodBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + public MethodModel toModel() { + return new Model(); + } + + public final class Model + extends AbstractUnboundModel + implements MethodModel, MethodInfo { + public Model() { + super(elements); + } + + @Override + public AccessFlags flags() { + return flags; + } + + @Override + public Optional parent() { + return original().flatMap(MethodModel::parent); + } + + @Override + public Utf8Entry methodName() { + return name; + } + + @Override + public Utf8Entry methodType() { + return desc; + } + + @Override + public MethodTypeDesc methodTypeSymbol() { + return BufferedMethodBuilder.this.methodTypeSymbol(); + } + + @Override + public int methodFlags() { + return flags.flagsMask(); + } + + @Override + public int parameterSlot(int paramNo) { + return BufferedMethodBuilder.this.parameterSlot(paramNo); + } + + @Override + public Optional code() { + throw new UnsupportedOperationException("nyi"); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.withMethod(methodName(), methodType(), methodFlags(), new Consumer<>() { + @Override + public void accept(MethodBuilder mb) { + forEachElement(mb); + } + }); + } + + @Override + public void writeTo(BufWriter buf) { + DirectMethodBuilder mb = new DirectMethodBuilder(constantPool, context, name, desc, methodFlags(), null); + elements.forEach(mb); + mb.writeTo(buf); + } + + @Override + public String toString() { + return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]", + name.stringValue(), desc.stringValue(), flags.flagsMask()); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers$1.class b/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers$1.class new file mode 100644 index 00000000..fe9af9b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers.class b/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers.class new file mode 100644 index 00000000..9a243a3e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers.java b/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers.java new file mode 100644 index 00000000..f7aa0902 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/BytecodeHelpers.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandleInfo; +import java.util.ArrayList; +import java.util.List; + +import java.lang.classfile.BootstrapMethodEntry; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantDynamicEntry; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import java.lang.classfile.constantpool.LoadableConstantEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.constantpool.MethodHandleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; + +public class BytecodeHelpers { + + private BytecodeHelpers() { + } + + public static Opcode loadOpcode(TypeKind tk, int slot) { + return switch (tk) { + case IntType, ShortType, ByteType, CharType, BooleanType -> switch (slot) { + case 0 -> Opcode.ILOAD_0; + case 1 -> Opcode.ILOAD_1; + case 2 -> Opcode.ILOAD_2; + case 3 -> Opcode.ILOAD_3; + default -> (slot < 256) ? Opcode.ILOAD : Opcode.ILOAD_W; + }; + case LongType -> switch (slot) { + case 0 -> Opcode.LLOAD_0; + case 1 -> Opcode.LLOAD_1; + case 2 -> Opcode.LLOAD_2; + case 3 -> Opcode.LLOAD_3; + default -> (slot < 256) ? Opcode.LLOAD : Opcode.LLOAD_W; + }; + case DoubleType -> switch (slot) { + case 0 -> Opcode.DLOAD_0; + case 1 -> Opcode.DLOAD_1; + case 2 -> Opcode.DLOAD_2; + case 3 -> Opcode.DLOAD_3; + default -> (slot < 256) ? Opcode.DLOAD : Opcode.DLOAD_W; + }; + case FloatType -> switch (slot) { + case 0 -> Opcode.FLOAD_0; + case 1 -> Opcode.FLOAD_1; + case 2 -> Opcode.FLOAD_2; + case 3 -> Opcode.FLOAD_3; + default -> (slot < 256) ? Opcode.FLOAD : Opcode.FLOAD_W; + }; + case ReferenceType -> switch (slot) { + case 0 -> Opcode.ALOAD_0; + case 1 -> Opcode.ALOAD_1; + case 2 -> Opcode.ALOAD_2; + case 3 -> Opcode.ALOAD_3; + default -> (slot < 256) ? Opcode.ALOAD : Opcode.ALOAD_W; + }; + case VoidType -> throw new IllegalArgumentException("void"); + }; + } + + public static Opcode storeOpcode(TypeKind tk, int slot) { + return switch (tk) { + case IntType, ShortType, ByteType, CharType, BooleanType -> switch (slot) { + case 0 -> Opcode.ISTORE_0; + case 1 -> Opcode.ISTORE_1; + case 2 -> Opcode.ISTORE_2; + case 3 -> Opcode.ISTORE_3; + default -> (slot < 256) ? Opcode.ISTORE : Opcode.ISTORE_W; + }; + case LongType -> switch (slot) { + case 0 -> Opcode.LSTORE_0; + case 1 -> Opcode.LSTORE_1; + case 2 -> Opcode.LSTORE_2; + case 3 -> Opcode.LSTORE_3; + default -> (slot < 256) ? Opcode.LSTORE : Opcode.LSTORE_W; + }; + case DoubleType -> switch (slot) { + case 0 -> Opcode.DSTORE_0; + case 1 -> Opcode.DSTORE_1; + case 2 -> Opcode.DSTORE_2; + case 3 -> Opcode.DSTORE_3; + default -> (slot < 256) ? Opcode.DSTORE : Opcode.DSTORE_W; + }; + case FloatType -> switch (slot) { + case 0 -> Opcode.FSTORE_0; + case 1 -> Opcode.FSTORE_1; + case 2 -> Opcode.FSTORE_2; + case 3 -> Opcode.FSTORE_3; + default -> (slot < 256) ? Opcode.FSTORE : Opcode.FSTORE_W; + }; + case ReferenceType -> switch (slot) { + case 0 -> Opcode.ASTORE_0; + case 1 -> Opcode.ASTORE_1; + case 2 -> Opcode.ASTORE_2; + case 3 -> Opcode.ASTORE_3; + default -> (slot < 256) ? Opcode.ASTORE : Opcode.ASTORE_W; + }; + case VoidType -> throw new IllegalArgumentException("void"); + }; + } + + public static Opcode returnOpcode(TypeKind tk) { + return switch (tk) { + case ByteType, ShortType, IntType, CharType, BooleanType -> Opcode.IRETURN; + case FloatType -> Opcode.FRETURN; + case LongType -> Opcode.LRETURN; + case DoubleType -> Opcode.DRETURN; + case ReferenceType -> Opcode.ARETURN; + case VoidType -> Opcode.RETURN; + }; + } + + public static Opcode arrayLoadOpcode(TypeKind tk) { + return switch (tk) { + case ByteType, BooleanType -> Opcode.BALOAD; + case ShortType -> Opcode.SALOAD; + case IntType -> Opcode.IALOAD; + case FloatType -> Opcode.FALOAD; + case LongType -> Opcode.LALOAD; + case DoubleType -> Opcode.DALOAD; + case ReferenceType -> Opcode.AALOAD; + case CharType -> Opcode.CALOAD; + case VoidType -> throw new IllegalArgumentException("void not an allowable array type"); + }; + } + + public static Opcode arrayStoreOpcode(TypeKind tk) { + return switch (tk) { + case ByteType, BooleanType -> Opcode.BASTORE; + case ShortType -> Opcode.SASTORE; + case IntType -> Opcode.IASTORE; + case FloatType -> Opcode.FASTORE; + case LongType -> Opcode.LASTORE; + case DoubleType -> Opcode.DASTORE; + case ReferenceType -> Opcode.AASTORE; + case CharType -> Opcode.CASTORE; + case VoidType -> throw new IllegalArgumentException("void not an allowable array type"); + }; + } + + public static Opcode reverseBranchOpcode(Opcode op) { + return switch (op) { + case IFEQ -> Opcode.IFNE; + case IFNE -> Opcode.IFEQ; + case IFLT -> Opcode.IFGE; + case IFGE -> Opcode.IFLT; + case IFGT -> Opcode.IFLE; + case IFLE -> Opcode.IFGT; + case IF_ICMPEQ -> Opcode.IF_ICMPNE; + case IF_ICMPNE -> Opcode.IF_ICMPEQ; + case IF_ICMPLT -> Opcode.IF_ICMPGE; + case IF_ICMPGE -> Opcode.IF_ICMPLT; + case IF_ICMPGT -> Opcode.IF_ICMPLE; + case IF_ICMPLE -> Opcode.IF_ICMPGT; + case IF_ACMPEQ -> Opcode.IF_ACMPNE; + case IF_ACMPNE -> Opcode.IF_ACMPEQ; + case IFNULL -> Opcode.IFNONNULL; + case IFNONNULL -> Opcode.IFNULL; + default -> throw new IllegalArgumentException("Unknown branch instruction: " + op); + }; + } + + public static Opcode convertOpcode(TypeKind from, TypeKind to) { + return switch (from) { + case IntType -> + switch (to) { + case LongType -> Opcode.I2L; + case FloatType -> Opcode.I2F; + case DoubleType -> Opcode.I2D; + case ByteType -> Opcode.I2B; + case CharType -> Opcode.I2C; + case ShortType -> Opcode.I2S; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + case LongType -> + switch (to) { + case FloatType -> Opcode.L2F; + case DoubleType -> Opcode.L2D; + case IntType -> Opcode.L2I; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + case DoubleType -> + switch (to) { + case FloatType -> Opcode.D2F; + case LongType -> Opcode.D2L; + case IntType -> Opcode.D2I; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + case FloatType -> + switch (to) { + case LongType -> Opcode.F2L; + case DoubleType -> Opcode.F2D; + case IntType -> Opcode.F2I; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + default -> throw new IllegalArgumentException(String.format("convert %s -> %s", from, to)); + }; + } + + static void validateSIPUSH(ConstantDesc d) { + if (d instanceof Integer iVal && Short.MIN_VALUE <= iVal && iVal <= Short.MAX_VALUE) + return; + + if (d instanceof Long lVal && Short.MIN_VALUE <= lVal && Short.MAX_VALUE <= lVal) + return; + + throw new IllegalArgumentException("SIPUSH: value must be within: Short.MIN_VALUE <= value <= Short.MAX_VALUE" + + ", found: " + d); + } + + static void validateBIPUSH(ConstantDesc d) { + if (d instanceof Integer iVal && Byte.MIN_VALUE <= iVal && iVal <= Byte.MAX_VALUE) + return; + + if (d instanceof Long lVal && Byte.MIN_VALUE <= lVal && Byte.MAX_VALUE <= lVal) + return; + + throw new IllegalArgumentException("BIPUSH: value must be within: Byte.MIN_VALUE <= value <= Byte.MAX_VALUE" + + ", found: " + d); + } + + public static MethodHandleEntry handleDescToHandleInfo(ConstantPoolBuilder constantPool, DirectMethodHandleDesc bootstrapMethod) { + ClassEntry bsOwner = constantPool.classEntry(bootstrapMethod.owner()); + NameAndTypeEntry bsNameAndType = constantPool.nameAndTypeEntry(constantPool.utf8Entry(bootstrapMethod.methodName()), + constantPool.utf8Entry(bootstrapMethod.lookupDescriptor())); + int bsRefKind = bootstrapMethod.refKind(); + MemberRefEntry bsReference = toBootstrapMemberRef(constantPool, bsRefKind, bsOwner, bsNameAndType, bootstrapMethod.isOwnerInterface()); + + return constantPool.methodHandleEntry(bsRefKind, bsReference); + } + + static MemberRefEntry toBootstrapMemberRef(ConstantPoolBuilder constantPool, int bsRefKind, ClassEntry owner, NameAndTypeEntry nat, boolean isOwnerInterface) { + return isOwnerInterface + ? constantPool.interfaceMethodRefEntry(owner, nat) + : bsRefKind <= MethodHandleInfo.REF_putStatic + ? constantPool.fieldRefEntry(owner, nat) + : constantPool.methodRefEntry(owner, nat); + } + + static ConstantDynamicEntry handleConstantDescToHandleInfo(ConstantPoolBuilder constantPool, DynamicConstantDesc desc) { + ConstantDesc[] bootstrapArgs = desc.bootstrapArgs(); + List staticArgs = new ArrayList<>(bootstrapArgs.length); + for (ConstantDesc bootstrapArg : bootstrapArgs) + staticArgs.add(constantPool.loadableConstantEntry(bootstrapArg)); + + var bootstrapDesc = desc.bootstrapMethod(); + ClassEntry bsOwner = constantPool.classEntry(bootstrapDesc.owner()); + NameAndTypeEntry bsNameAndType = constantPool.nameAndTypeEntry(bootstrapDesc.methodName(), + bootstrapDesc.invocationType()); + int bsRefKind = bootstrapDesc.refKind(); + + MemberRefEntry memberRefEntry = toBootstrapMemberRef(constantPool, bsRefKind, bsOwner, bsNameAndType, bootstrapDesc.isOwnerInterface()); + MethodHandleEntry methodHandleEntry = constantPool.methodHandleEntry(bsRefKind, memberRefEntry); + BootstrapMethodEntry bme = constantPool.bsmEntry(methodHandleEntry, staticArgs); + return constantPool.constantDynamicEntry(bme, + constantPool.nameAndTypeEntry(desc.constantName(), + desc.constantType())); + } + + public static void validateValue(Opcode opcode, ConstantDesc v) { + switch (opcode) { + case ACONST_NULL -> { + if (v != null && v != ConstantDescs.NULL) + throw new IllegalArgumentException("value must be null or ConstantDescs.NULL with opcode ACONST_NULL"); + } + case SIPUSH -> + validateSIPUSH(v); + case BIPUSH -> + validateBIPUSH(v); + case LDC, LDC_W, LDC2_W -> { + if (v == null) + throw new IllegalArgumentException("`null` must use ACONST_NULL"); + } + default -> { + var exp = opcode.constantValue(); + if (exp == null) + throw new IllegalArgumentException("Can not use Opcode: " + opcode + " with constant()"); + if (v == null || !(v.equals(exp) || (exp instanceof Long l && v.equals(l.intValue())))) { + var t = (exp instanceof Long) ? "L" : (exp instanceof Float) ? "f" : (exp instanceof Double) ? "d" : ""; + throw new IllegalArgumentException("value must be " + exp + t + " with opcode " + opcode.name()); + } + } + } + } + + public static LoadableConstantEntry constantEntry(ConstantPoolBuilder constantPool, + ConstantDesc constantValue) { + // this method is invoked during JVM bootstrap - cannot use pattern switch + if (constantValue instanceof Integer value) { + return constantPool.intEntry(value); + } + if (constantValue instanceof String value) { + return constantPool.stringEntry(value); + } + if (constantValue instanceof ClassDesc value && !value.isPrimitive()) { + return constantPool.classEntry(value); + } + if (constantValue instanceof Long value) { + return constantPool.longEntry(value); + } + if (constantValue instanceof Float value) { + return constantPool.floatEntry(value); + } + if (constantValue instanceof Double value) { + return constantPool.doubleEntry(value); + } + if (constantValue instanceof MethodTypeDesc value) { + return constantPool.methodTypeEntry(value); + } + if (constantValue instanceof DirectMethodHandleDesc value) { + return handleDescToHandleInfo(constantPool, value); + } if (constantValue instanceof DynamicConstantDesc value) { + return handleConstantDescToHandleInfo(constantPool, value); + } + throw new UnsupportedOperationException("not yet: " + constantValue); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CatchBuilderImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/CatchBuilderImpl.class new file mode 100644 index 00000000..5ab534db Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CatchBuilderImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CatchBuilderImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/CatchBuilderImpl.java new file mode 100644 index 00000000..5d262c39 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/CatchBuilderImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.Label; +import java.lang.classfile.Opcode; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +public final class CatchBuilderImpl implements CodeBuilder.CatchBuilder { + final CodeBuilder b; + final BlockCodeBuilderImpl tryBlock; + final Label tryCatchEnd; + final Set catchTypes; + BlockCodeBuilderImpl catchBlock; + + public CatchBuilderImpl(CodeBuilder b, BlockCodeBuilderImpl tryBlock, Label tryCatchEnd) { + this.b = b; + this.tryBlock = tryBlock; + this.tryCatchEnd = tryCatchEnd; + this.catchTypes = new HashSet<>(); + } + + @Override + public CodeBuilder.CatchBuilder catching(ClassDesc exceptionType, Consumer catchHandler) { + return catchingMulti(exceptionType == null ? List.of() : List.of(exceptionType), catchHandler); + } + + @Override + public CodeBuilder.CatchBuilder catchingMulti(List exceptionTypes, Consumer catchHandler) { + Objects.requireNonNull(exceptionTypes); + Objects.requireNonNull(catchHandler); + + if (catchBlock == null) { + if (tryBlock.reachable()) { + b.branch(Opcode.GOTO, tryCatchEnd); + } + } + + for (var exceptionType : exceptionTypes) { + if (!catchTypes.add(exceptionType)) { + throw new IllegalArgumentException("Existing catch block catches exception of type: " + exceptionType); + } + } + + // Finish prior catch block + if (catchBlock != null) { + catchBlock.end(); + if (catchBlock.reachable()) { + b.branch(Opcode.GOTO, tryCatchEnd); + } + } + + catchBlock = new BlockCodeBuilderImpl(b, tryCatchEnd); + Label tryStart = tryBlock.startLabel(); + Label tryEnd = tryBlock.endLabel(); + if (exceptionTypes.isEmpty()) { + catchBlock.exceptionCatchAll(tryStart, tryEnd, catchBlock.startLabel()); + } + else { + for (var exceptionType : exceptionTypes) { + catchBlock.exceptionCatch(tryStart, tryEnd, catchBlock.startLabel(), exceptionType); + } + } + catchBlock.start(); + catchHandler.accept(catchBlock); + + return this; + } + + @Override + public void catchingAll(Consumer catchAllHandler) { + catchingMulti(List.of(), catchAllHandler); + } + + public void finish() { + if (catchBlock != null) { + catchBlock.end(); + } + b.labelBinding(tryCatchEnd); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedClassBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/ChainedClassBuilder.class new file mode 100644 index 00000000..04977791 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ChainedClassBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedClassBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/ChainedClassBuilder.java new file mode 100644 index 00000000..b33d192f --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ChainedClassBuilder.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.*; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class ChainedClassBuilder + implements ClassBuilder, Consumer { + private final DirectClassBuilder terminal; + private final Consumer consumer; + + public ChainedClassBuilder(ClassBuilder downstream, + Consumer consumer) { + this.consumer = consumer; + this.terminal = switch (downstream) { + case ChainedClassBuilder cb -> cb.terminal; + case DirectClassBuilder db -> db; + }; + } + + @Override + public ClassBuilder with(ClassElement element) { + consumer.accept(element); + return this; + } + + @Override + public Optional original() { + return terminal.original(); + } + + @Override + public ClassBuilder withField(Utf8Entry name, Utf8Entry descriptor, Consumer handler) { + consumer.accept(new BufferedFieldBuilder(terminal.constantPool, terminal.context, + name, descriptor, null) + .run(handler) + .toModel()); + return this; + } + + @Override + public ClassBuilder transformField(FieldModel field, FieldTransform transform) { + BufferedFieldBuilder builder = new BufferedFieldBuilder(terminal.constantPool, terminal.context, + field.fieldName(), field.fieldType(), + field); + builder.transform(field, transform); + consumer.accept(builder.toModel()); + return this; + } + + @Override + public ClassBuilder withMethod(Utf8Entry name, Utf8Entry descriptor, int flags, + Consumer handler) { + consumer.accept(new BufferedMethodBuilder(terminal.constantPool, terminal.context, + name, descriptor, null) + .run(handler) + .toModel()); + return this; + } + + @Override + public ClassBuilder transformMethod(MethodModel method, MethodTransform transform) { + BufferedMethodBuilder builder = new BufferedMethodBuilder(terminal.constantPool, terminal.context, + method.methodName(), method.methodType(), method); + builder.transform(method, transform); + consumer.accept(builder.toModel()); + return this; + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedCodeBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/ChainedCodeBuilder.class new file mode 100644 index 00000000..bd507a6f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ChainedCodeBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedCodeBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/ChainedCodeBuilder.java new file mode 100644 index 00000000..8c6c80b3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ChainedCodeBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.TypeKind; +import java.lang.classfile.Label; + +import java.util.function.Consumer; + +public final class ChainedCodeBuilder + extends NonterminalCodeBuilder + implements CodeBuilder { + private final Consumer consumer; + + public ChainedCodeBuilder(CodeBuilder downstream, + Consumer consumer) { + super(downstream); + this.consumer = consumer; + } + + @Override + public Label startLabel() { + return terminal.startLabel(); + } + + @Override + public Label endLabel() { + return terminal.endLabel(); + } + + @Override + public int allocateLocal(TypeKind typeKind) { + return parent.allocateLocal(typeKind); + } + + @Override + public CodeBuilder with(CodeElement element) { + consumer.accept(element); + return this; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedFieldBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/ChainedFieldBuilder.class new file mode 100644 index 00000000..8e5b7517 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ChainedFieldBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedFieldBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/ChainedFieldBuilder.java new file mode 100644 index 00000000..3c892324 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ChainedFieldBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.FieldBuilder; +import java.lang.classfile.FieldElement; +import java.lang.classfile.FieldModel; +import java.lang.classfile.constantpool.ConstantPoolBuilder; + +public final class ChainedFieldBuilder implements FieldBuilder { + private final TerminalFieldBuilder terminal; + private final Consumer consumer; + + public ChainedFieldBuilder(FieldBuilder downstream, + Consumer consumer) { + this.consumer = consumer; + this.terminal = switch (downstream) { + case ChainedFieldBuilder cb -> cb.terminal; + case TerminalFieldBuilder tb -> tb; + }; + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + + @Override + public Optional original() { + return terminal.original(); + } + + @Override + public FieldBuilder with(FieldElement element) { + consumer.accept(element); + return this; + } + +} + diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedMethodBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/ChainedMethodBuilder.class new file mode 100644 index 00000000..59ed8d7f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ChainedMethodBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ChainedMethodBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/ChainedMethodBuilder.java new file mode 100644 index 00000000..866dda2c --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ChainedMethodBuilder.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.MethodElement; +import java.lang.classfile.MethodModel; +import java.lang.classfile.constantpool.ConstantPoolBuilder; + +public final class ChainedMethodBuilder implements MethodBuilder { + final TerminalMethodBuilder terminal; + final Consumer consumer; + + public ChainedMethodBuilder(MethodBuilder downstream, + Consumer consumer) { + this.consumer = consumer; + this.terminal = switch (downstream) { + case ChainedMethodBuilder cb -> cb.terminal; + case TerminalMethodBuilder tb -> tb; + }; + } + + @Override + public MethodBuilder with(MethodElement element) { + consumer.accept(element); + return this; + } + + @Override + public MethodBuilder withCode(Consumer handler) { + consumer.accept(terminal.bufferedCodeBuilder(null) + .run(handler) + .toModel()); + return this; + } + + @Override + public MethodBuilder transformCode(CodeModel code, CodeTransform transform) { + BufferedCodeBuilder builder = terminal.bufferedCodeBuilder(code); + builder.transform(code, transform); + consumer.accept(builder.toModel()); + return this; + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + + @Override + public Optional original() { + return terminal.original(); + } + +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$1.class new file mode 100644 index 00000000..5f091b1b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$2.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$2.class new file mode 100644 index 00000000..1dc655e5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$2.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$AttributeMapperOptionImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$AttributeMapperOptionImpl.class new file mode 100644 index 00000000..b2511a21 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$AttributeMapperOptionImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$ClassHierarchyResolverOptionImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$ClassHierarchyResolverOptionImpl.class new file mode 100644 index 00000000..4d793b67 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl$ClassHierarchyResolverOptionImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl.class new file mode 100644 index 00000000..48f59590 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl.java new file mode 100644 index 00000000..8bffbd6d --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Consumer; + +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassFile.*; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassHierarchyResolver; +import java.lang.classfile.ClassModel; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.Utf8Entry; +import jdk.internal.classfile.impl.verifier.VerifierImpl; + +public record ClassFileImpl(StackMapsOption stackMapsOption, + DebugElementsOption debugElementsOption, + LineNumbersOption lineNumbersOption, + AttributesProcessingOption attributesProcessingOption, + ConstantPoolSharingOption constantPoolSharingOption, + ShortJumpsOption shortJumpsOption, + DeadCodeOption deadCodeOption, + DeadLabelsOption deadLabelsOption, + ClassHierarchyResolverOption classHierarchyResolverOption, + AttributeMapperOption attributeMapperOption) implements ClassFile { + + public static final ClassFileImpl DEFAULT_CONTEXT = new ClassFileImpl( + StackMapsOption.STACK_MAPS_WHEN_REQUIRED, + DebugElementsOption.PASS_DEBUG, + LineNumbersOption.PASS_LINE_NUMBERS, + AttributesProcessingOption.PASS_ALL_ATTRIBUTES, + ConstantPoolSharingOption.SHARED_POOL, + ShortJumpsOption.FIX_SHORT_JUMPS, + DeadCodeOption.PATCH_DEAD_CODE, + DeadLabelsOption.FAIL_ON_DEAD_LABELS, + new ClassHierarchyResolverOptionImpl(ClassHierarchyResolver.defaultResolver()), + new AttributeMapperOptionImpl(new Function<>() { + @Override + public AttributeMapper apply(Utf8Entry k) { + return null; + } + })); + + @SuppressWarnings("unchecked") + @Override + public ClassFileImpl withOptions(Option... options) { + var smo = stackMapsOption; + var deo = debugElementsOption; + var lno = lineNumbersOption; + var apo = attributesProcessingOption; + var cpso = constantPoolSharingOption; + var sjo = shortJumpsOption; + var dco = deadCodeOption; + var dlo = deadLabelsOption; + var chro = classHierarchyResolverOption; + var amo = attributeMapperOption; + for (var o : options) { + switch (o) { + case StackMapsOption oo -> smo = oo; + case DebugElementsOption oo -> deo = oo; + case LineNumbersOption oo -> lno = oo; + case AttributesProcessingOption oo -> apo = oo; + case ConstantPoolSharingOption oo -> cpso = oo; + case ShortJumpsOption oo -> sjo = oo; + case DeadCodeOption oo -> dco = oo; + case DeadLabelsOption oo -> dlo = oo; + case ClassHierarchyResolverOption oo -> chro = oo; + case AttributeMapperOption oo -> amo = oo; + } + } + return new ClassFileImpl(smo, deo, lno, apo, cpso, sjo, dco, dlo, chro, amo); + } + + @Override + public ClassModel parse(byte[] bytes) { + return new ClassImpl(bytes, this); + } + + @Override + public byte[] build(ClassEntry thisClassEntry, + ConstantPoolBuilder constantPool, + Consumer handler) { + thisClassEntry = AbstractPoolEntry.maybeClone(constantPool, thisClassEntry); + DirectClassBuilder builder = new DirectClassBuilder((SplitConstantPool)constantPool, this, thisClassEntry); + handler.accept(builder); + return builder.build(); + } + + @Override + public byte[] transform(ClassModel model, ClassEntry newClassName, ClassTransform transform) { + ConstantPoolBuilder constantPool = constantPoolSharingOption() == ConstantPoolSharingOption.SHARED_POOL + ? ConstantPoolBuilder.of(model) + : ConstantPoolBuilder.of(); + return build(newClassName, constantPool, + new Consumer() { + @Override + public void accept(ClassBuilder builder) { + ((DirectClassBuilder) builder).setOriginal((ClassImpl)model); + ((DirectClassBuilder) builder).setSizeHint(((ClassImpl)model).classfileLength()); + builder.transform((ClassImpl)model, transform); + } + }); + } + + @Override + public List verify(ClassModel model) { + return VerifierImpl.verify(model, classHierarchyResolverOption().classHierarchyResolver(), null); + } + + @Override + public List verify(byte[] bytes) { + try { + return verify(parse(bytes)); + } catch (IllegalArgumentException parsingError) { + return List.of(new VerifyError(parsingError.getMessage())); + } + } + + public record AttributeMapperOptionImpl(Function> attributeMapper) + implements AttributeMapperOption { + } + + public record ClassHierarchyResolverOptionImpl(ClassHierarchyResolver classHierarchyResolver) + implements ClassHierarchyResolverOption { + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileVersionImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileVersionImpl.class new file mode 100644 index 00000000..87994c33 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileVersionImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassFileVersionImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileVersionImpl.java new file mode 100644 index 00000000..ba260529 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassFileVersionImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.ClassFileVersion; + +public final class ClassFileVersionImpl + extends AbstractElement + implements ClassFileVersion { + private final int majorVersion, minorVersion; + + public ClassFileVersionImpl(int majorVersion, int minorVersion) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + } + + @Override + public int majorVersion() { + return majorVersion; + } + + @Override + public int minorVersion() { + return minorVersion; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setVersion(majorVersion, minorVersion); + } + + @Override + public String toString() { + return String.format("ClassFileVersion[majorVersion=%d, minorVersion=%d]", majorVersion, minorVersion); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver$1.class new file mode 100644 index 00000000..e426b05e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver.class new file mode 100644 index 00000000..94016b01 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassHierarchyInfoImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassHierarchyInfoImpl.class new file mode 100644 index 00000000..132bcdda Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassHierarchyInfoImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver$1.class new file mode 100644 index 00000000..01872468 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver.class new file mode 100644 index 00000000..87758c31 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ResourceParsingClassHierarchyResolver$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ResourceParsingClassHierarchyResolver$1.class new file mode 100644 index 00000000..84e21cd9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ResourceParsingClassHierarchyResolver$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ResourceParsingClassHierarchyResolver.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ResourceParsingClassHierarchyResolver.class new file mode 100644 index 00000000..0bacb281 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$ResourceParsingClassHierarchyResolver.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$StaticClassHierarchyResolver.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$StaticClassHierarchyResolver.class new file mode 100644 index 00000000..4635f25e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl$StaticClassHierarchyResolver.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl.class new file mode 100644 index 00000000..83ba4cfd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl.java new file mode 100644 index 00000000..1154b038 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassHierarchyImpl.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.classfile.impl; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.constant.ClassDesc; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import java.lang.classfile.ClassHierarchyResolver; + +import static java.lang.constant.ConstantDescs.CD_Object; +import static java.lang.classfile.ClassFile.*; +import static java.util.Objects.requireNonNull; + +/** + * Class hierarchy resolution framework is answering questions about classes assignability, common classes ancestor and whether the class represents an interface. + * All the requests are handled without class loading nor full verification, optionally with incomplete dependencies and with focus on maximum performance. + * + */ +public final class ClassHierarchyImpl { + + public record ClassHierarchyInfoImpl(ClassDesc superClass, boolean isInterface) implements ClassHierarchyResolver.ClassHierarchyInfo { + static final ClassHierarchyResolver.ClassHierarchyInfo OBJECT_INFO = new ClassHierarchyInfoImpl(null, false); + } + + public static final ClassHierarchyResolver DEFAULT_RESOLVER = + new ClassLoadingClassHierarchyResolver(ClassLoadingClassHierarchyResolver.SYSTEM_CLASS_PROVIDER); + + private final ClassHierarchyResolver resolver; + + /** + * Public constructor of ClassHierarchyImpl accepting instances of ClassHierarchyInfoResolver to resolve individual class streams. + * @param classHierarchyResolver ClassHierarchyInfoResolver instance + */ + public ClassHierarchyImpl(ClassHierarchyResolver classHierarchyResolver) { + requireNonNull(classHierarchyResolver); + this.resolver = classHierarchyResolver instanceof CachedClassHierarchyResolver + ? classHierarchyResolver + : classHierarchyResolver.cached(); + } + + private ClassHierarchyInfoImpl resolve(ClassDesc classDesc) { + var res = resolver.getClassInfo(classDesc); + if (res != null) return (ClassHierarchyInfoImpl) res; + throw new IllegalArgumentException("Could not resolve class " + classDesc.displayName()); + } + + /** + * Method answering question whether given class is an interface, + * responding without the class stream resolution and parsing is preferred in case the interface status is known from previous activities. + * @param classDesc class path in form of <package>/<class_name>.class + * @return true if the given class name represents an interface + */ + public boolean isInterface(ClassDesc classDesc) { + return resolve(classDesc).isInterface(); + } + + /** + * Method resolving common ancestor of two classes + * @param symbol1 first class descriptor + * @param symbol2 second class descriptor + * @return common ancestor class name or null if it could not be identified + */ + public ClassDesc commonAncestor(ClassDesc symbol1, ClassDesc symbol2) { + //calculation of common ancestor is a robust (yet fast) way to decide about assignability in incompletely resolved class hierarchy + //exact order of symbol loops is critical for performance of the above isAssignableFrom method, so standard situations are resolved in linear time + //this method returns null if common ancestor could not be identified + if (isInterface(symbol1) || isInterface(symbol2)) return CD_Object; + for (var s1 = symbol1; s1 != null; s1 = resolve(s1).superClass()) { + for (var s2 = symbol2; s2 != null; s2 = resolve(s2).superClass()) { + if (s1.equals(s2)) return s1; + } + } + return null; + } + + public boolean isAssignableFrom(ClassDesc thisClass, ClassDesc fromClass) { + //extra check if fromClass is an interface is necessary to handle situation when thisClass might not been fully resolved and so it is potentially an unidentified interface + //this special corner-case handling has been added based on better success rate of constructing stack maps with simulated broken resolution of classes and interfaces + if (isInterface(fromClass)) return resolve(thisClass).superClass() == null; + //regular calculation of assignability is based on common ancestor calculation + var anc = commonAncestor(thisClass, fromClass); + //if common ancestor does not exist (as the class hierarchy could not be fully resolved) we optimistically assume the classes might be accessible + //if common ancestor is equal to thisClass then the classes are clearly accessible + //if other common ancestor is calculated (which works even when their grandparents could not be resolved) then it is clear that thisClass could not be assigned from fromClass + return anc == null || thisClass.equals(anc); + } + + public static final class CachedClassHierarchyResolver implements ClassHierarchyResolver { + // this instance should not leak out, appears only in cache in order to utilize Map.computeIfAbsent + // is already an invalid combination, so it can be compared with equals or as value class safely + private static final ClassHierarchyResolver.ClassHierarchyInfo NOPE = + new ClassHierarchyInfoImpl(null, true); + + private final Map resolvedCache; + private final Function delegateFunction; + + public CachedClassHierarchyResolver(ClassHierarchyResolver delegate, Map resolvedCache) { + this.resolvedCache = resolvedCache; + this.delegateFunction = new Function<>() { + @Override + public ClassHierarchyInfo apply(ClassDesc classDesc) { + var ret = delegate.getClassInfo(classDesc); + return ret == null ? NOPE : ret; + } + }; + } + + @Override + public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + var ret = resolvedCache.computeIfAbsent(classDesc, delegateFunction); + return ret == NOPE ? null : ret; + } + } + + public static final class ResourceParsingClassHierarchyResolver implements ClassHierarchyResolver { + public static final Function SYSTEM_STREAM_PROVIDER = new Function<>() { + @Override + public InputStream apply(ClassDesc cd) { + return ClassLoader.getSystemClassLoader().getResourceAsStream(Util.toInternalName(cd) + ".class"); + } + }; + private final Function streamProvider; + + public ResourceParsingClassHierarchyResolver(Function classStreamProvider) { + this.streamProvider = classStreamProvider; + } + + // resolve method looks for the class file using ClassStreamResolver instance and tries to briefly scan it just for minimal information necessary + // minimal information includes: identification of the class as interface, obtaining its superclass name and identification of all potential interfaces (to avoid unnecessary future resolutions of them) + // empty ClInfo is stored in case of an exception to avoid repeated scanning failures + @Override + public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + var ci = streamProvider.apply(classDesc); + if (ci == null) return null; + try (var in = new DataInputStream(new BufferedInputStream(ci))) { + in.skipBytes(8); + int cpLength = in.readUnsignedShort(); + String[] cpStrings = new String[cpLength]; + int[] cpClasses = new int[cpLength]; + for (int i = 1; i < cpLength; i++) { + int tag; + switch (tag = in.readUnsignedByte()) { + case TAG_UTF8 -> cpStrings[i] = in.readUTF(); + case TAG_CLASS -> cpClasses[i] = in.readUnsignedShort(); + case TAG_STRING, TAG_METHODTYPE, TAG_MODULE, TAG_PACKAGE -> in.skipBytes(2); + case TAG_METHODHANDLE -> in.skipBytes(3); + case TAG_INTEGER, TAG_FLOAT, TAG_FIELDREF, TAG_METHODREF, TAG_INTERFACEMETHODREF, + TAG_NAMEANDTYPE, TAG_CONSTANTDYNAMIC, TAG_INVOKEDYNAMIC -> in.skipBytes(4); + case TAG_LONG, TAG_DOUBLE -> { + in.skipBytes(8); + i++; + } + default -> throw new IllegalStateException("Bad tag (" + tag + ") at index (" + i + ")"); + } + } + boolean isInterface = (in.readUnsignedShort() & ACC_INTERFACE) != 0; + in.skipBytes(2); + int superIndex = in.readUnsignedShort(); + var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null; + return new ClassHierarchyInfoImpl(superClass, isInterface); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + } + + public static final class StaticClassHierarchyResolver implements ClassHierarchyResolver { + + private final Map map; + + public StaticClassHierarchyResolver(Collection interfaceNames, Map classToSuperClass) { + map = HashMap.newHashMap(interfaceNames.size() + classToSuperClass.size() + 1); + map.put(CD_Object, ClassHierarchyInfoImpl.OBJECT_INFO); + for (var e : classToSuperClass.entrySet()) + map.put(e.getKey(), ClassHierarchyInfo.ofClass(e.getValue())); + for (var i : interfaceNames) + map.put(i, ClassHierarchyInfo.ofInterface()); + } + + @Override + public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { + return map.get(classDesc); + } + } + + public static final class ClassLoadingClassHierarchyResolver implements ClassHierarchyResolver { + public static final Function> SYSTEM_CLASS_PROVIDER = new Function<>() { + @Override + public Class apply(ClassDesc cd) { + try { + return Class.forName(Util.toBinaryName(cd), false, ClassLoader.getSystemClassLoader()); + } catch (ClassNotFoundException ex) { + return null; + } + } + }; + private final Function> classProvider; + + public ClassLoadingClassHierarchyResolver(Function> classProvider) { + this.classProvider = classProvider; + } + + @Override + public ClassHierarchyInfo getClassInfo(ClassDesc cd) { + if (!cd.isClassOrInterface()) + return null; + + if (cd.equals(CD_Object)) + return ClassHierarchyInfo.ofClass(null); + + var cl = classProvider.apply(cd); + if (cl == null) { + return null; + } + + return cl.isInterface() ? ClassHierarchyInfo.ofInterface() + : ClassHierarchyInfo.ofClass(cl.getSuperclass().describeConstable().orElseThrow()); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl$1.class new file mode 100644 index 00000000..cea07d69 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl.class new file mode 100644 index 00000000..debc845b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl.java new file mode 100644 index 00000000..35075cb7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassImpl.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.reflect.AccessFlag; +import java.lang.classfile.AccessFlags; +import java.lang.classfile.Attribute; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassModel; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassFileVersion; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.FieldModel; +import java.lang.classfile.Interfaces; +import java.lang.classfile.MethodModel; +import java.lang.classfile.Superclass; +import java.lang.classfile.attribute.InnerClassesAttribute; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModuleHashesAttribute; +import java.lang.classfile.attribute.ModuleMainClassAttribute; +import java.lang.classfile.attribute.ModulePackagesAttribute; +import java.lang.classfile.attribute.ModuleResolutionAttribute; +import java.lang.classfile.attribute.ModuleTargetAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.SourceDebugExtensionAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import jdk.internal.access.SharedSecrets; + +public final class ClassImpl + extends AbstractElement + implements ClassModel { + + final ClassReaderImpl reader; + private final int attributesPos; + private final List methods; + private final List fields; + private List> attributes; + private List interfaces; + + public ClassImpl(byte[] cfbytes, ClassFileImpl context) { + this.reader = new ClassReaderImpl(cfbytes, context); + int p = reader.interfacesPos; + int icnt = reader.readU2(p); + p += 2 + icnt * 2; + int fcnt = reader.readU2(p); + FieldImpl[] fields = new FieldImpl[fcnt]; + p += 2; + for (int i = 0; i < fcnt; ++i) { + int startPos = p; + int attrStart = p + 6; + p = reader.skipAttributeHolder(attrStart); + fields[i] = new FieldImpl(reader, startPos, p, attrStart); + } + this.fields = List.of(fields); + int mcnt = reader.readU2(p); + MethodImpl[] methods = new MethodImpl[mcnt]; + p += 2; + for (int i = 0; i < mcnt; ++i) { + int startPos = p; + int attrStart = p + 6; + p = reader.skipAttributeHolder(attrStart); + methods[i] = new MethodImpl(reader, startPos, p, attrStart); + } + this.methods = List.of(methods); + this.attributesPos = p; + reader.setContainedClass(this); + } + + public int classfileLength() { + return reader.classfileLength(); + } + + @Override + public AccessFlags flags() { + return AccessFlags.ofClass(reader.flags()); + } + + @Override + public int majorVersion() { + return reader.readU2(6); + } + + @Override + public int minorVersion() { + return reader.readU2(4); + } + + @Override + public ConstantPool constantPool() { + return reader; + } + + @Override + public ClassEntry thisClass() { + return reader.thisClassEntry(); + } + + @Override + public Optional superclass() { + return reader.superclassEntry(); + } + + @Override + public List interfaces() { + if (interfaces == null) { + int pos = reader.thisClassPos() + 4; + int cnt = reader.readU2(pos); + pos += 2; + var arr = new Object[cnt]; + for (int i = 0; i < cnt; ++i) { + arr[i] = reader.readClassEntry(pos); + pos += 2; + } + this.interfaces = SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(arr); + } + return interfaces; + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } + + // ClassModel + + @Override + public void forEachElement(Consumer consumer) { + consumer.accept(flags()); + consumer.accept(ClassFileVersion.of(majorVersion(), minorVersion())); + superclass().ifPresent(new Consumer() { + @Override + public void accept(ClassEntry entry) { + consumer.accept(Superclass.of(entry)); + } + }); + consumer.accept(Interfaces.of(interfaces())); + fields().forEach(consumer); + methods().forEach(consumer); + for (Attribute attr : attributes()) { + if (attr instanceof ClassElement e) + consumer.accept(e); + } + } + + @Override + public List fields() { + return fields; + } + + @Override + public List methods() { + return methods; + } + + @Override + public boolean isModuleInfo() { + AccessFlags flags = flags(); + // move to where? + return flags.has(AccessFlag.MODULE) + && majorVersion() >= ClassFile.JAVA_9_VERSION + && thisClass().asInternalName().equals("module-info") + && (superclass().isEmpty()) + && interfaces().isEmpty() + && fields().isEmpty() + && methods().isEmpty() + && verifyModuleAttributes(); + } + + @Override + public String toString() { + return String.format("ClassModel[thisClass=%s, flags=%d]", thisClass().name().stringValue(), flags().flagsMask()); + } + + private boolean verifyModuleAttributes() { + if (findAttribute(Attributes.module()).isEmpty()) + return false; + + return attributes().stream().allMatch(a -> + a instanceof ModuleAttribute + || a instanceof ModulePackagesAttribute + || a instanceof ModuleHashesAttribute + || a instanceof ModuleMainClassAttribute + || a instanceof ModuleResolutionAttribute + || a instanceof ModuleTargetAttribute + || a instanceof InnerClassesAttribute + || a instanceof SourceFileAttribute + || a instanceof SourceDebugExtensionAttribute + || a instanceof RuntimeVisibleAnnotationsAttribute + || a instanceof RuntimeInvisibleAnnotationsAttribute + || a instanceof CustomAttribute); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$1.class new file mode 100644 index 00000000..d09d9e1b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$ExceptionHandler.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$ExceptionHandler.class new file mode 100644 index 00000000..457420d6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$ExceptionHandler.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$LeafNodeImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$LeafNodeImpl.class new file mode 100644 index 00000000..2c569886 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$LeafNodeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$ListNodeImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$ListNodeImpl.class new file mode 100644 index 00000000..8edb9892 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$ListNodeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$MapNodeImpl$PrivateListNodeImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$MapNodeImpl$PrivateListNodeImpl.class new file mode 100644 index 00000000..4a8e233d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$MapNodeImpl$PrivateListNodeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$MapNodeImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$MapNodeImpl.class new file mode 100644 index 00000000..8954cb8f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$MapNodeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$Style.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$Style.class new file mode 100644 index 00000000..c33b67ce Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl$Style.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl.class new file mode 100644 index 00000000..c8552508 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl.java new file mode 100644 index 00000000..fac2eba9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassPrinterImpl.java @@ -0,0 +1,1084 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.reflect.AccessFlag; +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import java.lang.classfile.Annotation; + +import java.lang.classfile.AnnotationElement; +import java.lang.classfile.AnnotationValue; +import java.lang.classfile.AnnotationValue.*; +import java.lang.classfile.Attribute; +import java.lang.classfile.ClassModel; +import java.lang.classfile.components.ClassPrinter.*; +import java.lang.classfile.CodeModel; +import java.lang.classfile.Instruction; +import java.lang.classfile.MethodModel; +import java.lang.classfile.TypeAnnotation; +import java.lang.classfile.attribute.*; +import java.lang.classfile.attribute.StackMapFrameInfo.*; +import java.lang.classfile.constantpool.*; +import java.lang.classfile.instruction.*; + +import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.CompoundElement; +import java.lang.classfile.FieldModel; +import static jdk.internal.classfile.impl.ClassPrinterImpl.Style.*; + +public final class ClassPrinterImpl { + + public enum Style { BLOCK, FLOW } + + public record LeafNodeImpl(ConstantDesc name, ConstantDesc value) implements LeafNode { + + @Override + public Stream walk() { + return Stream.of(this); + } + } + + public static sealed class ListNodeImpl extends AbstractList implements ListNode { + + private final Style style; + private final ConstantDesc name; + protected final List nodes; + + public ListNodeImpl(Style style, ConstantDesc name, Stream nodes) { + this.style = style; + this.name = name; + this.nodes = nodes.toList(); + } + + protected ListNodeImpl(Style style, ConstantDesc name, List nodes) { + this.style = style; + this.name = name; + this.nodes = nodes; + } + + @Override + public ConstantDesc name() { + return name; + } + + @Override + public Stream walk() { + return Stream.concat(Stream.of(this), stream().flatMap(Node::walk)); + } + + public Style style() { + return style; + } + + @Override + public Node get(int index) { + return nodes.get(index); + } + + @Override + public int size() { + return nodes.size(); + } + } + + public static final class MapNodeImpl implements MapNode { + + private static final class PrivateListNodeImpl extends ListNodeImpl { + PrivateListNodeImpl(Style style, ConstantDesc name, Node... n) { + super(style, name, new ArrayList<>(List.of(n))); + } + } + + private final Style style; + private final ConstantDesc name; + private final Map map; + + public MapNodeImpl(Style style, ConstantDesc name) { + this.style = style; + this.name = name; + this.map = new LinkedHashMap<>(); + } + + @Override + public ConstantDesc name() { + return name; + } + + @Override + public Stream walk() { + return Stream.concat(Stream.of(this), values().stream().flatMap(Node::walk)); + } + + public Style style() { + return style; + } + + @Override + public int size() { + return map.size(); + } + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public Node get(Object key) { + return map.get(key); + } + + @Override + public Node put(ConstantDesc key, Node value) { + throw new UnsupportedOperationException(); + } + + @Override + public Node remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Set keySet() { + return Collections.unmodifiableSet(map.keySet()); + } + + @Override + public Collection values() { + return Collections.unmodifiableCollection(map.values()); + } + + @Override + public Set> entrySet() { + return Collections.unmodifiableSet(map.entrySet()); + } + + + MapNodeImpl with(Node... nodes) { + for (var n : nodes) { + if (n != null) { + var prev = map.putIfAbsent(n.name(), n); + if (prev != null) { + //nodes with duplicite keys are joined into a list + if (prev instanceof PrivateListNodeImpl list) { + list.nodes.add(n); + } else { + map.put(n.name(), new PrivateListNodeImpl(style, n.name(), prev, n)); + } + } + } + } + return this; + } + } + + private static Node leaf(ConstantDesc name, ConstantDesc value) { + return new LeafNodeImpl(name, value); + } + + private static Node[] leafs(ConstantDesc... namesAndValues) { + if ((namesAndValues.length & 1) > 0) + throw new AssertionError("Odd number of arguments: " + Arrays.toString(namesAndValues)); + var nodes = new Node[namesAndValues.length >> 1]; + for (int i = 0, j = 0; i < nodes.length; i ++) { + nodes[i] = leaf(namesAndValues[j++], namesAndValues[j++]); + } + return nodes; + } + + private static Node list(ConstantDesc listName, ConstantDesc itemsName, Stream values) { + return new ListNodeImpl(FLOW, listName, values.map(v -> leaf(itemsName, v))); + } + + private static Node map(ConstantDesc mapName, ConstantDesc... keysAndValues) { + return new MapNodeImpl(FLOW, mapName).with(leafs(keysAndValues)); + } + + private static final String NL = System.lineSeparator(); + + private static final char[] DIGITS = "0123456789ABCDEF".toCharArray(); + + private static void escape(int c, StringBuilder sb) { + switch (c) { + case '\\' -> sb.append('\\').append('\\'); + case '"' -> sb.append('\\').append('"'); + case '\b' -> sb.append('\\').append('b'); + case '\n' -> sb.append('\\').append('n'); + case '\t' -> sb.append('\\').append('t'); + case '\f' -> sb.append('\\').append('f'); + case '\r' -> sb.append('\\').append('r'); + default -> { + if (c >= 0x20 && c < 0x7f) { + sb.append((char)c); + } else { + sb.append('\\').append('u').append(DIGITS[(c >> 12) & 0xf]) + .append(DIGITS[(c >> 8) & 0xf]).append(DIGITS[(c >> 4) & 0xf]).append(DIGITS[(c) & 0xf]); + } + } + } + } + + public static void toYaml(Node node, Consumer out) { + toYaml(0, false, new ListNodeImpl(BLOCK, null, Stream.of(node)), out); + out.accept(NL); + } + + private static void toYaml(int indent, boolean skipFirstIndent, Node node, Consumer out) { + switch (node) { + case LeafNode leaf -> { + out.accept(quoteAndEscapeYaml(leaf.value())); + } + case ListNodeImpl list -> { + switch (list.style()) { + case FLOW -> { + out.accept("["); + boolean first = true; + for (var n : list) { + if (first) first = false; + else out.accept(", "); + toYaml(0, false, n, out); + } + out.accept("]"); + } + case BLOCK -> { + for (var n : list) { + out.accept(NL + " ".repeat(indent) + " - "); + toYaml(indent + 1, true, n, out); + } + } + } + } + case MapNodeImpl map -> { + switch (map.style()) { + case FLOW -> { + out.accept("{"); + boolean first = true; + for (var n : map.values()) { + if (first) first = false; + else out.accept(", "); + out.accept(quoteAndEscapeYaml(n.name()) + ": "); + toYaml(0, false, n, out); + } + out.accept("}"); + } + case BLOCK -> { + for (var n : map.values()) { + if (skipFirstIndent) { + skipFirstIndent = false; + } else { + out.accept(NL + " ".repeat(indent)); + } + out.accept(quoteAndEscapeYaml(n.name()) + ": "); + toYaml(n instanceof ListNodeImpl pl && pl.style() == BLOCK ? indent : indent + 1, false, n, out); + } + } + } + } + } + } + + private static String quoteAndEscapeYaml(ConstantDesc value) { + String s = String.valueOf(value); + if (value instanceof Number) return s; + if (s.length() == 0) return "''"; + var sb = new StringBuilder(s.length() << 1); + s.chars().forEach(c -> { + switch (c) { + case '\'' -> sb.append("''"); + default -> escape(c, sb); + }}); + String esc = sb.toString(); + if (esc.length() != s.length()) return "'" + esc + "'"; + switch (esc.charAt(0)) { + case '-', '?', ':', ',', '[', ']', '{', '}', '#', '&', '*', '!', '|', '>', '\'', '"', '%', '@', '`': + return "'" + esc + "'"; + } + for (int i = 1; i < esc.length(); i++) { + switch (esc.charAt(i)) { + case ',', '[', ']', '{', '}': + return "'" + esc + "'"; + } + } + return esc; + } + + public static void toJson(Node node, Consumer out) { + toJson(1, true, node, out); + out.accept(NL); + } + + private static void toJson(int indent, boolean skipFirstIndent, Node node, Consumer out) { + switch (node) { + case LeafNode leaf -> { + out.accept(quoteAndEscapeJson(leaf.value())); + } + case ListNodeImpl list -> { + out.accept("["); + boolean first = true; + switch (list.style()) { + case FLOW -> { + for (var n : list) { + if (first) first = false; + else out.accept(", "); + toJson(0, false, n, out); + } + } + case BLOCK -> { + for (var n : list) { + if (first) first = false; + else out.accept(","); + out.accept(NL + " ".repeat(indent)); + toJson(indent + 1, true, n, out); + } + } + } + out.accept("]"); + } + case MapNodeImpl map -> { + switch (map.style()) { + case FLOW -> { + out.accept("{"); + boolean first = true; + for (var n : map.values()) { + if (first) first = false; + else out.accept(", "); + out.accept(quoteAndEscapeJson(n.name().toString()) + ": "); + toJson(0, false, n, out); + } + } + case BLOCK -> { + if (skipFirstIndent) out.accept(" { "); + else out.accept("{"); + boolean first = true; + for (var n : map.values()) { + if (first) first = false; + else out.accept(","); + if (skipFirstIndent) skipFirstIndent = false; + else out.accept(NL + " ".repeat(indent)); + out.accept(quoteAndEscapeJson(n.name().toString()) + ": "); + toJson(indent + 1, false, n, out); + } + } + } + out.accept("}"); + } + } + } + + private static String quoteAndEscapeJson(ConstantDesc value) { + String s = String.valueOf(value); + if (value instanceof Number) return s; + var sb = new StringBuilder(s.length() << 1); + sb.append('"'); + s.chars().forEach(c -> escape(c, sb)); + sb.append('"'); + return sb.toString(); + } + + public static void toXml(Node node, Consumer out) { + out.accept(""); + toXml(0, false, node, out); + out.accept(NL); + } + + private static void toXml(int indent, boolean skipFirstIndent, Node node, Consumer out) { + var name = toXmlName(node.name().toString()); + switch (node) { + case LeafNode leaf -> { + out.accept("<" + name + ">"); + out.accept(xmlEscape(leaf.value())); + } + case ListNodeImpl list -> { + switch (list.style()) { + case FLOW -> { + out.accept("<" + name + ">"); + for (var n : list) { + toXml(0, false, n, out); + } + } + case BLOCK -> { + if (!skipFirstIndent) + out.accept(NL + " ".repeat(indent)); + out.accept("<" + name + ">"); + for (var n : list) { + out.accept(NL + " ".repeat(indent + 1)); + toXml(indent + 1, true, n, out); + } + } + } + } + case MapNodeImpl map -> { + switch (map.style()) { + case FLOW -> { + out.accept("<" + name + ">"); + for (var n : map.values()) { + toXml(0, false, n, out); + } + } + case BLOCK -> { + if (!skipFirstIndent) + out.accept(NL + " ".repeat(indent)); + out.accept("<" + name + ">"); + for (var n : map.values()) { + out.accept(NL + " ".repeat(indent + 1)); + toXml(indent + 1, true, n, out); + } + } + } + } + } + out.accept(""); + } + + private static String xmlEscape(ConstantDesc value) { + var s = String.valueOf(value); + var sb = new StringBuilder(s.length() << 1); + s.chars().forEach(c -> { + switch (c) { + case '<' -> sb.append("<"); + case '>' -> sb.append(">"); + case '"' -> sb.append("""); + case '&' -> sb.append("&"); + case '\'' -> sb.append("'"); + default -> escape(c, sb); + }}); + return sb.toString(); + } + + private static String toXmlName(String name) { + if (Character.isDigit(name.charAt(0))) + name = "_" + name; + return name.replaceAll("[^A-Za-z_0-9]", "_"); + } + + private static Node[] elementValueToTree(AnnotationValue v) { + return switch (v) { + case OfString cv -> leafs("string", String.valueOf(cv.constantValue())); + case OfDouble cv -> leafs("double", String.valueOf(cv.constantValue())); + case OfFloat cv -> leafs("float", String.valueOf(cv.constantValue())); + case OfLong cv -> leafs("long", String.valueOf(cv.constantValue())); + case OfInteger cv -> leafs("int", String.valueOf(cv.constantValue())); + case OfShort cv -> leafs("short", String.valueOf(cv.constantValue())); + case OfCharacter cv -> leafs("char", String.valueOf(cv.constantValue())); + case OfByte cv -> leafs("byte", String.valueOf(cv.constantValue())); + case OfBoolean cv -> leafs("boolean", String.valueOf((int)cv.constantValue() != 0)); + case OfClass clv -> leafs("class", clv.className().stringValue()); + case OfEnum ev -> leafs("enum class", ev.className().stringValue(), + "constant name", ev.constantName().stringValue()); + case OfAnnotation av -> leafs("annotation class", av.annotation().className().stringValue()); + case OfArray av -> new Node[]{new ListNodeImpl(FLOW, "array", av.values().stream().map( + ev -> new MapNodeImpl(FLOW, "value").with(elementValueToTree(ev))))}; + }; + } + + private static Node elementValuePairsToTree(List evps) { + return new ListNodeImpl(FLOW, "values", evps.stream().map(evp -> new MapNodeImpl(FLOW, "pair").with( + leaf("name", evp.name().stringValue()), + new MapNodeImpl(FLOW, "value").with(elementValueToTree(evp.value()))))); + } + + private static Stream convertVTIs(CodeAttribute lr, List vtis) { + return vtis.stream().mapMulti((vti, ret) -> { + switch (vti) { + case SimpleVerificationTypeInfo s -> { + switch (s) { + case ITEM_DOUBLE -> { + ret.accept("double"); + ret.accept("double2"); + } + case ITEM_FLOAT -> + ret.accept("float"); + case ITEM_INTEGER -> + ret.accept("int"); + case ITEM_LONG -> { + ret.accept("long"); + ret.accept("long2"); + } + case ITEM_NULL -> ret.accept("null"); + case ITEM_TOP -> ret.accept("?"); + case ITEM_UNINITIALIZED_THIS -> ret.accept("THIS"); + } + } + case ObjectVerificationTypeInfo o -> + ret.accept(o.className().name().stringValue()); + case UninitializedVerificationTypeInfo u -> + ret.accept("UNINITIALIZED @" + lr.labelToBci(u.newTarget())); + } + }); + } + + private record ExceptionHandler(int start, int end, int handler, String catchType) {} + + public static MapNode modelToTree(CompoundElement model, Verbosity verbosity) { + return switch(model) { + case ClassModel cm -> classToTree(cm, verbosity); + case FieldModel fm -> fieldToTree(fm, verbosity); + case MethodModel mm -> methodToTree(mm, verbosity); + case CodeModel com -> codeToTree((CodeAttribute)com, verbosity); + }; + } + + private static MapNode classToTree(ClassModel clm, Verbosity verbosity) { + return new MapNodeImpl(BLOCK, "class") + .with(leaf("class name", clm.thisClass().asInternalName()), + leaf("version", clm.majorVersion() + "." + clm.minorVersion()), + list("flags", "flag", clm.flags().flags().stream().map(AccessFlag::name)), + leaf("superclass", clm.superclass().map(ClassEntry::asInternalName).orElse("")), + list("interfaces", "interface", clm.interfaces().stream().map(ClassEntry::asInternalName)), + list("attributes", "attribute", clm.attributes().stream().map(Attribute::attributeName))) + .with(constantPoolToTree(clm.constantPool(), verbosity)) + .with(attributesToTree(clm.attributes(), verbosity)) + .with(new ListNodeImpl(BLOCK, "fields", clm.fields().stream().map(f -> + fieldToTree(f, verbosity)))) + .with(new ListNodeImpl(BLOCK, "methods", clm.methods().stream().map(mm -> + methodToTree(mm, verbosity)))); + } + + private static Node[] constantPoolToTree(ConstantPool cp, Verbosity verbosity) { + if (verbosity == Verbosity.TRACE_ALL) { + var cpNode = new MapNodeImpl(BLOCK, "constant pool"); + for (PoolEntry e : cp) { + cpNode.with(new MapNodeImpl(FLOW, e.index()) + .with(leaf("tag", switch (e.tag()) { + case TAG_UTF8 -> "Utf8"; + case TAG_INTEGER -> "Integer"; + case TAG_FLOAT -> "Float"; + case TAG_LONG -> "Long"; + case TAG_DOUBLE -> "Double"; + case TAG_CLASS -> "Class"; + case TAG_STRING -> "String"; + case TAG_FIELDREF -> "Fieldref"; + case TAG_METHODREF -> "Methodref"; + case TAG_INTERFACEMETHODREF -> "InterfaceMethodref"; + case TAG_NAMEANDTYPE -> "NameAndType"; + case TAG_METHODHANDLE -> "MethodHandle"; + case TAG_METHODTYPE -> "MethodType"; + case TAG_CONSTANTDYNAMIC -> "Dynamic"; + case TAG_INVOKEDYNAMIC -> "InvokeDynamic"; + case TAG_MODULE -> "Module"; + case TAG_PACKAGE -> "Package"; + default -> throw new AssertionError("Unknown CP tag: " + e.tag()); + })) + .with(switch (e) { + case ClassEntry ce -> leafs( + "class name index", ce.name().index(), + "class internal name", ce.asInternalName()); + case ModuleEntry me -> leafs( + "module name index", me.name().index(), + "module name", me.name().stringValue()); + case PackageEntry pe -> leafs( + "package name index", pe.name().index(), + "package name", pe.name().stringValue()); + case StringEntry se -> leafs( + "value index", se.utf8().index(), + "value", se.stringValue()); + case MemberRefEntry mre -> leafs( + "owner index", mre.owner().index(), + "name and type index", mre.nameAndType().index(), + "owner", mre.owner().name().stringValue(), + "name", mre.name().stringValue(), + "type", mre.type().stringValue()); + case NameAndTypeEntry nte -> leafs( + "name index", nte.name().index(), + "type index", nte.type().index(), + "name", nte.name().stringValue(), + "type", nte.type().stringValue()); + case MethodHandleEntry mhe -> leafs( + "reference kind", DirectMethodHandleDesc.Kind.valueOf(mhe.kind()).name(), + "reference index", mhe.reference().index(), + "owner", mhe.reference().owner().asInternalName(), + "name", mhe.reference().name().stringValue(), + "type", mhe.reference().type().stringValue()); + case MethodTypeEntry mte -> leafs( + "descriptor index", mte.descriptor().index(), + "descriptor", mte.descriptor().stringValue()); + case DynamicConstantPoolEntry dcpe -> new Node[] { + leaf("bootstrap method handle index", dcpe.bootstrap().bootstrapMethod().index()), + list("bootstrap method arguments indexes", + "index", dcpe.bootstrap().arguments().stream().map(en -> en.index())), + leaf("name and type index", dcpe.nameAndType().index()), + leaf("name", dcpe.name().stringValue()), + leaf("type", dcpe.type().stringValue())}; + case AnnotationConstantValueEntry ve -> leafs( + "value", String.valueOf(ve.constantValue()) + ); + })); + } + return new Node[]{cpNode}; + } else { + return new Node[0]; + } + } + + private static Node frameToTree(ConstantDesc name, CodeAttribute lr, StackMapFrameInfo f) { + return new MapNodeImpl(FLOW, name).with( + list("locals", "item", convertVTIs(lr, f.locals())), + list("stack", "item", convertVTIs(lr, f.stack()))); + } + + private static MapNode fieldToTree(FieldModel f, Verbosity verbosity) { + return new MapNodeImpl(BLOCK, "field") + .with(leaf("field name", f.fieldName().stringValue()), + list("flags", + "flag", f.flags().flags().stream().map(AccessFlag::name)), + leaf("field type", f.fieldType().stringValue()), + list("attributes", + "attribute", f.attributes().stream().map(Attribute::attributeName))) + .with(attributesToTree(f.attributes(), verbosity)); + } + + public static MapNode methodToTree(MethodModel m, Verbosity verbosity) { + return new MapNodeImpl(BLOCK, "method") + .with(leaf("method name", m.methodName().stringValue()), + list("flags", + "flag", m.flags().flags().stream().map(AccessFlag::name)), + leaf("method type", m.methodType().stringValue()), + list("attributes", + "attribute", m.attributes().stream().map(Attribute::attributeName))) + .with(attributesToTree(m.attributes(), verbosity)) + .with(codeToTree((CodeAttribute)m.code().orElse(null), verbosity)); + } + + private static MapNode codeToTree(CodeAttribute com, Verbosity verbosity) { + if (verbosity != Verbosity.MEMBERS_ONLY && com != null) { + var codeNode = new MapNodeImpl(BLOCK, "code"); + codeNode.with(leaf("max stack", com.maxStack())); + codeNode.with(leaf("max locals", com.maxLocals())); + codeNode.with(list("attributes", + "attribute", com.attributes().stream().map(Attribute::attributeName))); + var stackMap = new MapNodeImpl(BLOCK, "stack map frames"); + var visibleTypeAnnos = new LinkedHashMap>(); + var invisibleTypeAnnos = new LinkedHashMap>(); + List locals = List.of(); + for (var attr : com.attributes()) { + if (attr instanceof StackMapTableAttribute smta) { + codeNode.with(stackMap); + for (var smf : smta.entries()) { + stackMap.with(frameToTree(com.labelToBci(smf.target()), com, smf)); + } + } else if (verbosity == Verbosity.TRACE_ALL && attr != null) switch (attr) { + case LocalVariableTableAttribute lvta -> { + locals = lvta.localVariables(); + codeNode.with(new ListNodeImpl(BLOCK, "local variables", + IntStream.range(0, locals.size()).mapToObj(i -> { + var lv = lvta.localVariables().get(i); + return map(i + 1, + "start", lv.startPc(), + "end", lv.startPc() + lv.length(), + "slot", lv.slot(), + "name", lv.name().stringValue(), + "type", lv.type().stringValue()); + }))); + } + case LocalVariableTypeTableAttribute lvtta -> { + codeNode.with(new ListNodeImpl(BLOCK, "local variable types", + IntStream.range(0, lvtta.localVariableTypes().size()).mapToObj(i -> { + var lvt = lvtta.localVariableTypes().get(i); + return map(i + 1, + "start", lvt.startPc(), + "end", lvt.startPc() + lvt.length(), + "slot", lvt.slot(), + "name", lvt.name().stringValue(), + "signature", lvt.signature().stringValue()); + }))); + } + case LineNumberTableAttribute lnta -> { + codeNode.with(new ListNodeImpl(BLOCK, "line numbers", + IntStream.range(0, lnta.lineNumbers().size()).mapToObj(i -> { + var ln = lnta.lineNumbers().get(i); + return map(i + 1, + "start", ln.startPc(), + "line number", ln.lineNumber()); + }))); + } + case CharacterRangeTableAttribute crta -> { + codeNode.with(new ListNodeImpl(BLOCK, "character ranges", + IntStream.range(0, crta.characterRangeTable().size()).mapToObj(i -> { + var cr = crta.characterRangeTable().get(i); + return map(i + 1, + "start", cr.startPc(), + "end", cr.endPc(), + "range start", cr.characterRangeStart(), + "range end", cr.characterRangeEnd(), + "flags", cr.flags()); + }))); + } + case RuntimeVisibleTypeAnnotationsAttribute rvtaa -> + rvtaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) -> + visibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an))); + case RuntimeInvisibleTypeAnnotationsAttribute ritaa -> + ritaa.annotations().forEach(a -> forEachOffset(a, com, (off, an) -> + invisibleTypeAnnos.computeIfAbsent(off, o -> new LinkedList<>()).add(an))); + case Object o -> {} + } + } + codeNode.with(attributesToTree(com.attributes(), verbosity)); + if (!stackMap.containsKey(0)) { + codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @0").with( + list("locals", "item", convertVTIs(com, StackMapDecoder.initFrameLocals(com.parent().get()))), + list("stack", "item", Stream.of()))); + } + var excHandlers = com.exceptionHandlers().stream().map(exc -> new ExceptionHandler( + com.labelToBci(exc.tryStart()), + com.labelToBci(exc.tryEnd()), + com.labelToBci(exc.handler()), + exc.catchType().map(ct -> ct.name().stringValue()).orElse(null))).toList(); + int bci = 0; + for (var coe : com) { + if (coe instanceof Instruction ins) { + var frame = stackMap.get(bci); + if (frame != null) { + codeNode.with(new MapNodeImpl(FLOW, "//stack map frame @" + bci) + .with(((MapNodeImpl)frame).values().toArray(new Node[2]))); + } + var annos = invisibleTypeAnnos.get(bci); + if (annos != null) { + codeNode.with(typeAnnotationsToTree(FLOW, "//invisible type annotations @" + bci, annos)); + } + annos = visibleTypeAnnos.get(bci); + if (annos != null) { + codeNode.with(typeAnnotationsToTree(FLOW, "//visible type annotations @" + bci, annos)); + } + for (int i = 0; i < excHandlers.size(); i++) { + var exc = excHandlers.get(i); + if (exc.start() == bci) { + codeNode.with(map("//try block " + (i + 1) + " start", + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "catch type", exc.catchType())); + } + if (exc.end() == bci) { + codeNode.with(map("//try block " + (i + 1) + " end", + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "catch type", exc.catchType())); + } + if (exc.handler() == bci) { + codeNode.with(map("//exception handler " + (i + 1) + " start", + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "catch type", exc.catchType())); + } + } + var in = new MapNodeImpl(FLOW, bci).with(leaf("opcode", ins.opcode().name())); + codeNode.with(in); + switch (coe) { + case IncrementInstruction inc -> in.with(leafs( + "slot", inc.slot(), + "const", inc.constant())) + .with(localInfoToTree(locals, inc.slot(), bci)); + case LoadInstruction lv -> in.with(leaf( + "slot", lv.slot())) + .with(localInfoToTree(locals, lv.slot(), bci)); + case StoreInstruction lv -> in.with(leaf( + "slot", lv.slot())) + .with(localInfoToTree(locals, lv.slot(), bci)); + case FieldInstruction fa -> in.with(leafs( + "owner", fa.owner().name().stringValue(), + "field name", fa.name().stringValue(), + "field type", fa.type().stringValue())); + case InvokeInstruction inv -> in.with(leafs( + "owner", inv.owner().name().stringValue(), + "method name", inv.name().stringValue(), + "method type", inv.type().stringValue())); + case InvokeDynamicInstruction invd -> { + in.with(leafs( + "name", invd.name().stringValue(), + "descriptor", invd.type().stringValue(), + "bootstrap method", invd.bootstrapMethod().kind().name() + + " " + Util.toInternalName(invd.bootstrapMethod().owner()) + + "::" + invd.bootstrapMethod().methodName())); + in.with(list("arguments", "arg", invd.bootstrapArgs().stream())); + } + case NewObjectInstruction newo -> in.with(leaf( + "type", newo.className().name().stringValue())); + case NewPrimitiveArrayInstruction newa -> in.with(leafs( + "dimensions", 1, + "descriptor", newa.typeKind().typeName())); + case NewReferenceArrayInstruction newa -> in.with(leafs( + "dimensions", 1, + "descriptor", newa.componentType().name().stringValue())); + case NewMultiArrayInstruction newa -> in.with(leafs( + "dimensions", newa.dimensions(), + "descriptor", newa.arrayType().name().stringValue())); + case TypeCheckInstruction tch -> in.with(leaf( + "type", tch.type().name().stringValue())); + case ConstantInstruction cons -> in.with(leaf( + "constant value", cons.constantValue())); + case BranchInstruction br -> in.with(leaf( + "target", com.labelToBci(br.target()))); + case LookupSwitchInstruction si -> in.with(list( + "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) + .map(com::labelToBci), si.cases().stream() + .map(sc -> com.labelToBci(sc.target()))))); + case TableSwitchInstruction si -> in.with(list( + "targets", "target", Stream.concat(Stream.of(si.defaultTarget()) + .map(com::labelToBci), si.cases().stream() + .map(sc -> com.labelToBci(sc.target()))))); + case DiscontinuedInstruction.JsrInstruction jsr -> in.with(leaf( + "target", com.labelToBci(jsr.target()))); + case DiscontinuedInstruction.RetInstruction ret -> in.with(leaf( + "slot", ret.slot())); + default -> {} + } + bci += ins.sizeInBytes(); + } + } + if (!excHandlers.isEmpty()) { + var handlersNode = new MapNodeImpl(BLOCK, "exception handlers"); + codeNode.with(handlersNode); + for (int i = 0; i < excHandlers.size(); i++) { + var exc = excHandlers.get(i); + handlersNode.with(map("handler " + (i + 1), + "start", exc.start(), + "end", exc.end(), + "handler", exc.handler(), + "type", exc.catchType())); + } + } + return codeNode; + } + return null; + } + + private static Node[] attributesToTree(List> attributes, Verbosity verbosity) { + var nodes = new LinkedList(); + if (verbosity != Verbosity.MEMBERS_ONLY) for (var attr : attributes) { + switch (attr) { + case BootstrapMethodsAttribute bma -> + nodes.add(new ListNodeImpl(BLOCK, "bootstrap methods", bma.bootstrapMethods().stream().map( + bm -> { + var mh = bm.bootstrapMethod(); + var mref = mh.reference(); + var bmNode = new MapNodeImpl(FLOW, "bm"); + bmNode.with(leafs( + "index", bm.bsmIndex(), + "kind", DirectMethodHandleDesc.Kind.valueOf(mh.kind(), + mref instanceof InterfaceMethodRefEntry).name(), + "owner", mref.owner().asInternalName(), + "name", mref.nameAndType().name().stringValue())); + bmNode.with(list("args", "arg", bm.arguments().stream().map(LoadableConstantEntry::constantValue))); + return bmNode; + }))); + case ConstantValueAttribute cva -> + nodes.add(leaf("constant value", cva.constant().constantValue())); + case NestHostAttribute nha -> + nodes.add(leaf("nest host", nha.nestHost().name().stringValue())); + case NestMembersAttribute nma -> + nodes.add(list("nest members", "member", nma.nestMembers().stream() + .map(mp -> mp.name().stringValue()))); + case PermittedSubclassesAttribute psa -> + nodes.add(list("permitted subclasses", "subclass", psa.permittedSubclasses().stream() + .map(e -> e.name().stringValue()))); + default -> {} + } + if (verbosity == Verbosity.TRACE_ALL) switch (attr) { + case EnclosingMethodAttribute ema -> + nodes.add(map("enclosing method", + "class", ema.enclosingClass().name().stringValue(), + "method name", ema.enclosingMethodName() + .map(Utf8Entry::stringValue).orElse("null"), + "method type", ema.enclosingMethodType() + .map(Utf8Entry::stringValue).orElse("null"))); + case ExceptionsAttribute exa -> + nodes.add(list("exceptions", "exc", exa.exceptions().stream() + .map(e -> e.name().stringValue()))); + case InnerClassesAttribute ica -> + nodes.add(new ListNodeImpl(BLOCK, "inner classes", ica.classes().stream() + .map(ic -> new MapNodeImpl(FLOW, "cls").with( + leaf("inner class", ic.innerClass().name().stringValue()), + leaf("outer class", ic.outerClass() + .map(cle -> cle.name().stringValue()).orElse("null")), + leaf("inner name", ic.innerName().map(Utf8Entry::stringValue).orElse("null")), + list("flags", "flag", ic.flags().stream().map(AccessFlag::name)))))); + case MethodParametersAttribute mpa -> { + var n = new MapNodeImpl(BLOCK, "method parameters"); + for (int i = 0; i < mpa.parameters().size(); i++) { + var p = mpa.parameters().get(i); + n.with(new MapNodeImpl(FLOW, i + 1).with( + leaf("name", p.name().map(Utf8Entry::stringValue).orElse("null")), + list("flags", "flag", p.flags().stream().map(AccessFlag::name)))); + } + } + case ModuleAttribute ma -> + nodes.add(new MapNodeImpl(BLOCK, "module") + .with(leaf("name", ma.moduleName().name().stringValue()), + list("flags","flag", ma.moduleFlags().stream().map(AccessFlag::name)), + leaf("version", ma.moduleVersion().map(Utf8Entry::stringValue).orElse("null")), + list("uses", "class", ma.uses().stream().map(ce -> ce.name().stringValue())), + new ListNodeImpl(BLOCK, "requires", ma.requires().stream().map(req -> + new MapNodeImpl(FLOW, "req").with( + leaf("name", req.requires().name().stringValue()), + list("flags", "flag", req.requiresFlags().stream() + .map(AccessFlag::name)), + leaf("version", req.requiresVersion() + .map(Utf8Entry::stringValue).orElse(null))))), + new ListNodeImpl(BLOCK, "exports", ma.exports().stream().map(exp -> + new MapNodeImpl(FLOW, "exp").with( + leaf("package", exp.exportedPackage().asSymbol().name()), + list("flags", "flag", exp.exportsFlags().stream() + .map(AccessFlag::name)), + list("to", "module", exp.exportsTo().stream() + .map(me -> me.name().stringValue()))))), + new ListNodeImpl(BLOCK, "opens", ma.opens().stream().map(opn -> + new MapNodeImpl(FLOW, "opn").with( + leaf("package", opn.openedPackage().asSymbol().name()), + list("flags", "flag", opn.opensFlags().stream() + .map(AccessFlag::name)), + list("to", "module", opn.opensTo().stream() + .map(me -> me.name().stringValue()))))), + new ListNodeImpl(BLOCK, "provides", ma.provides().stream() + .map(prov -> new MapNodeImpl(FLOW, "prov").with( + leaf("class", prov.provides().name().stringValue()), + list("with", "cls", prov.providesWith().stream() + .map(ce -> ce.name().stringValue()))))))); + case ModulePackagesAttribute mopa -> + nodes.add(list("module packages", "subclass", mopa.packages().stream() + .map(mp -> mp.asSymbol().name()))); + case ModuleMainClassAttribute mmca -> + nodes.add(leaf("module main class", mmca.mainClass().name().stringValue())); + case RecordAttribute ra -> + nodes.add(new ListNodeImpl(BLOCK, "record components", ra.components().stream() + .map(rc -> new MapNodeImpl(BLOCK, "component") + .with(leafs( + "name", rc.name().stringValue(), + "type", rc.descriptor().stringValue())) + .with(list("attributes", "attribute", rc.attributes().stream() + .map(Attribute::attributeName))) + .with(attributesToTree(rc.attributes(), verbosity))))); + case AnnotationDefaultAttribute ada -> + nodes.add(new MapNodeImpl(FLOW, "annotation default").with(elementValueToTree(ada.defaultValue()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + nodes.add(annotationsToTree("invisible annotations", aa.annotations())); + case RuntimeVisibleAnnotationsAttribute aa -> + nodes.add(annotationsToTree("visible annotations", aa.annotations())); + case RuntimeInvisibleParameterAnnotationsAttribute aa -> + nodes.add(parameterAnnotationsToTree("invisible parameter annotations", aa.parameterAnnotations())); + case RuntimeVisibleParameterAnnotationsAttribute aa -> + nodes.add(parameterAnnotationsToTree("visible parameter annotations", aa.parameterAnnotations())); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + nodes.add(typeAnnotationsToTree(BLOCK, "invisible type annotations", aa.annotations())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + nodes.add(typeAnnotationsToTree(BLOCK, "visible type annotations", aa.annotations())); + case SignatureAttribute sa -> + nodes.add(leaf("signature", sa.signature().stringValue())); + case SourceFileAttribute sfa -> + nodes.add(leaf("source file", sfa.sourceFile().stringValue())); + default -> {} + } + } + return nodes.toArray(Node[]::new); + } + + private static Node annotationsToTree(String name, List annos) { + return new ListNodeImpl(BLOCK, name, annos.stream().map(a -> + new MapNodeImpl(FLOW, "anno") + .with(leaf("annotation class", a.className().stringValue())) + .with(elementValuePairsToTree(a.elements())))); + + } + + private static Node typeAnnotationsToTree(Style style, String name, List annos) { + return new ListNodeImpl(style, name, annos.stream().map(a -> + new MapNodeImpl(FLOW, "anno") + .with(leaf("annotation class", a.className().stringValue()), + leaf("target info", a.targetInfo().targetType().name())) + .with(elementValuePairsToTree(a.elements())))); + + } + + private static MapNodeImpl parameterAnnotationsToTree(String name, List> paramAnnotations) { + var node = new MapNodeImpl(BLOCK, name); + for (int i = 0; i < paramAnnotations.size(); i++) { + var annos = paramAnnotations.get(i); + if (!annos.isEmpty()) { + node.with(new ListNodeImpl(FLOW, "parameter " + (i + 1), annos.stream().map(a -> + new MapNodeImpl(FLOW, "anno") + .with(leaf("annotation class", a.className().stringValue())) + .with(elementValuePairsToTree(a.elements()))))); + } + } + return node; + } + + private static Node[] localInfoToTree(List locals, int slot, int bci) { + if (locals != null) { + for (var l : locals) { + if (l.slot() == slot && l.startPc() <= bci && l.length() + l.startPc() >= bci) { + return leafs("type", l.type().stringValue(), + "variable name", l.name().stringValue()); + } + } + } + return new Node[0]; + } + + private static void forEachOffset(TypeAnnotation ta, CodeAttribute lr, BiConsumer consumer) { + switch (ta.targetInfo()) { + case TypeAnnotation.OffsetTarget ot -> + consumer.accept(lr.labelToBci(ot.target()), ta); + case TypeAnnotation.TypeArgumentTarget tat -> + consumer.accept(lr.labelToBci(tat.target()), ta); + case TypeAnnotation.LocalVarTarget lvt -> + lvt.table().forEach(lvti -> consumer.accept(lr.labelToBci(lvti.startLabel()), ta)); + default -> {} + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassReaderImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassReaderImpl.class new file mode 100644 index 00000000..60ca47d0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassReaderImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassReaderImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassReaderImpl.java new file mode 100644 index 00000000..b8a262ce --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassReaderImpl.java @@ -0,0 +1,516 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import java.lang.classfile.*; +import java.lang.classfile.attribute.BootstrapMethodsAttribute; +import java.lang.classfile.constantpool.*; + +import static java.lang.classfile.ClassFile.TAG_CLASS; +import static java.lang.classfile.ClassFile.TAG_CONSTANTDYNAMIC; +import static java.lang.classfile.ClassFile.TAG_DOUBLE; +import static java.lang.classfile.ClassFile.TAG_FIELDREF; +import static java.lang.classfile.ClassFile.TAG_FLOAT; +import static java.lang.classfile.ClassFile.TAG_INTEGER; +import static java.lang.classfile.ClassFile.TAG_INTERFACEMETHODREF; +import static java.lang.classfile.ClassFile.TAG_INVOKEDYNAMIC; +import static java.lang.classfile.ClassFile.TAG_LONG; +import static java.lang.classfile.ClassFile.TAG_METHODHANDLE; +import static java.lang.classfile.ClassFile.TAG_METHODREF; +import static java.lang.classfile.ClassFile.TAG_METHODTYPE; +import static java.lang.classfile.ClassFile.TAG_MODULE; +import static java.lang.classfile.ClassFile.TAG_NAMEANDTYPE; +import static java.lang.classfile.ClassFile.TAG_PACKAGE; +import static java.lang.classfile.ClassFile.TAG_STRING; +import static java.lang.classfile.ClassFile.TAG_UTF8; + +public final class ClassReaderImpl + implements ClassReader { + static final int CP_ITEM_START = 10; + + private final byte[] buffer; + private final int metadataStart; + private final int classfileLength; + private final Function> attributeMapper; + private final int flags; + private final int thisClassPos; + private ClassEntry thisClass; + private Optional superclass; + private final int constantPoolCount; + private final int[] cpOffset; + + final ClassFileImpl context; + final int interfacesPos; + final PoolEntry[] cp; + + private ClassModel containedClass; + private List bsmEntries; + private BootstrapMethodsAttribute bootstrapMethodsAttribute; + + ClassReaderImpl(byte[] classfileBytes, + ClassFileImpl context) { + this.buffer = classfileBytes; + this.classfileLength = classfileBytes.length; + this.context = context; + this.attributeMapper = this.context.attributeMapperOption().attributeMapper(); + if (classfileLength < 4 || readInt(0) != 0xCAFEBABE) { + throw new IllegalArgumentException("Bad magic number"); + } + if (readU2(6) > ClassFile.latestMajorVersion()) { + throw new IllegalArgumentException("Unsupported class file version: " + readU2(6)); + } + int constantPoolCount = readU2(8); + int[] cpOffset = new int[constantPoolCount]; + int p = CP_ITEM_START; + for (int i = 1; i < cpOffset.length; ++i) { + cpOffset[i] = p; + int tag = readU1(p); + ++p; + switch (tag) { + // 2 + case TAG_CLASS, TAG_METHODTYPE, TAG_MODULE, TAG_STRING, TAG_PACKAGE -> p += 2; + + // 3 + case TAG_METHODHANDLE -> p += 3; + + // 4 + case TAG_CONSTANTDYNAMIC, TAG_FIELDREF, TAG_FLOAT, TAG_INTEGER, + TAG_INTERFACEMETHODREF, TAG_INVOKEDYNAMIC, TAG_METHODREF, + TAG_NAMEANDTYPE -> p += 4; + + // 8 + case TAG_DOUBLE, TAG_LONG -> { + p += 8; + ++i; + } + case TAG_UTF8 -> p += 2 + readU2(p); + default -> throw new ConstantPoolException( + "Bad tag (" + tag + ") at index (" + i + ") position (" + p + ")"); + } + } + this.metadataStart = p; + this.cpOffset = cpOffset; + this.constantPoolCount = constantPoolCount; + this.cp = new PoolEntry[constantPoolCount]; + + this.flags = readU2(p); + this.thisClassPos = p + 2; + p += 6; + this.interfacesPos = p; + } + + public ClassFileImpl context() { + return context; + } + + @Override + public Function> customAttributes() { + return attributeMapper; + } + + @Override + public int size() { + return constantPoolCount; + } + + @Override + public int flags() { + return flags; + } + + @Override + public ClassEntry thisClassEntry() { + if (thisClass == null) { + thisClass = readClassEntry(thisClassPos); + } + return thisClass; + } + + @Override + public Optional superclassEntry() { + if (superclass == null) { + superclass = Optional.ofNullable(readEntryOrNull(thisClassPos + 2, ClassEntry.class)); + } + return superclass; + } + + public int thisClassPos() { + return thisClassPos; + } + + @Override + public int classfileLength() { + return classfileLength; + } + + //------ Bootstrap Method Table handling + + @Override + public int bootstrapMethodCount() { + return bootstrapMethodsAttribute().bootstrapMethodsSize(); + } + + @Override + public BootstrapMethodEntryImpl bootstrapMethodEntry(int index) { + if (index < 0 || index >= bootstrapMethodCount()) { + throw new ConstantPoolException("Bad BSM index: " + index); + } + return bsmEntries().get(index); + } + + private static IllegalArgumentException outOfBoundsError(IndexOutOfBoundsException cause) { + return new IllegalArgumentException("Reading beyond classfile bounds", cause); + } + + @Override + public int readU1(int p) { + try { + return buffer[p] & 0xFF; + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public int readU2(int p) { + try { + int b1 = buffer[p] & 0xFF; + int b2 = buffer[p + 1] & 0xFF; + return (b1 << 8) + b2; + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public int readS1(int p) { + try { + return buffer[p]; + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public int readS2(int p) { + try { + int b1 = buffer[p]; + int b2 = buffer[p + 1] & 0xFF; + return (b1 << 8) + b2; + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public int readInt(int p) { + try { + int ch1 = buffer[p] & 0xFF; + int ch2 = buffer[p + 1] & 0xFF; + int ch3 = buffer[p + 2] & 0xFF; + int ch4 = buffer[p + 3] & 0xFF; + return (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4; + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public long readLong(int p) { + try { + return ((long) buffer[p + 0] << 56) + ((long) (buffer[p + 1] & 255) << 48) + + ((long) (buffer[p + 2] & 255) << 40) + ((long) (buffer[p + 3] & 255) << 32) + + ((long) (buffer[p + 4] & 255) << 24) + ((buffer[p + 5] & 255) << 16) + ((buffer[p + 6] & 255) << 8) + + (buffer[p + 7] & 255); + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public float readFloat(int p) { + return Float.intBitsToFloat(readInt(p)); + } + + @Override + public double readDouble(int p) { + return Double.longBitsToDouble(readLong(p)); + } + + @Override + public byte[] readBytes(int p, int len) { + try { + return Arrays.copyOfRange(buffer, p, p + len); + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + @Override + public void copyBytesTo(BufWriter buf, int p, int len) { + try { + buf.writeBytes(buffer, p, len); + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } + + BootstrapMethodsAttribute bootstrapMethodsAttribute() { + + if (bootstrapMethodsAttribute == null) { + bootstrapMethodsAttribute + = containedClass.findAttribute(Attributes.bootstrapMethods()) + .orElse(new UnboundAttribute.EmptyBootstrapAttribute()); + } + + return bootstrapMethodsAttribute; + } + + List bsmEntries() { + if (bsmEntries == null) { + bsmEntries = new ArrayList<>(); + BootstrapMethodsAttribute attr = bootstrapMethodsAttribute(); + List list = attr.bootstrapMethods(); + if (!list.isEmpty()) { + for (BootstrapMethodEntry bm : list) { + AbstractPoolEntry.MethodHandleEntryImpl handle = (AbstractPoolEntry.MethodHandleEntryImpl) bm.bootstrapMethod(); + List args = bm.arguments(); + int hash = BootstrapMethodEntryImpl.computeHashCode(handle, args); + bsmEntries.add(new BootstrapMethodEntryImpl(this, bsmEntries.size(), hash, handle, args)); + } + } + } + return bsmEntries; + } + + void setContainedClass(ClassModel containedClass) { + this.containedClass = containedClass; + } + + ClassModel getContainedClass() { + return containedClass; + } + + boolean writeBootstrapMethods(BufWriter buf) { + Optional a + = containedClass.findAttribute(Attributes.bootstrapMethods()); + if (a.isEmpty()) + return false; + a.get().writeTo(buf); + return true; + } + + void writeConstantPoolEntries(BufWriter buf) { + copyBytesTo(buf, ClassReaderImpl.CP_ITEM_START, + metadataStart - ClassReaderImpl.CP_ITEM_START); + } + + // Constantpool + @Override + public PoolEntry entryByIndex(int index) { + return entryByIndex(index, PoolEntry.class); + } + + private static boolean checkTag(int tag, Class cls) { + var type = switch (tag) { + // JVMS Table 4.4-B. Constant pool tags + case TAG_UTF8 -> AbstractPoolEntry.Utf8EntryImpl.class; + case TAG_INTEGER -> AbstractPoolEntry.IntegerEntryImpl.class; + case TAG_FLOAT -> AbstractPoolEntry.FloatEntryImpl.class; + case TAG_LONG -> AbstractPoolEntry.LongEntryImpl.class; + case TAG_DOUBLE -> AbstractPoolEntry.DoubleEntryImpl.class; + case TAG_CLASS -> AbstractPoolEntry.ClassEntryImpl.class; + case TAG_STRING -> AbstractPoolEntry.StringEntryImpl.class; + case TAG_FIELDREF -> AbstractPoolEntry.FieldRefEntryImpl.class; + case TAG_METHODREF -> AbstractPoolEntry.MethodRefEntryImpl.class; + case TAG_INTERFACEMETHODREF -> AbstractPoolEntry.InterfaceMethodRefEntryImpl.class; + case TAG_NAMEANDTYPE -> AbstractPoolEntry.NameAndTypeEntryImpl.class; + case TAG_METHODHANDLE -> AbstractPoolEntry.MethodHandleEntryImpl.class; + case TAG_METHODTYPE -> AbstractPoolEntry.MethodTypeEntryImpl.class; + case TAG_CONSTANTDYNAMIC -> AbstractPoolEntry.ConstantDynamicEntryImpl.class; + case TAG_INVOKEDYNAMIC -> AbstractPoolEntry.InvokeDynamicEntryImpl.class; + case TAG_MODULE -> AbstractPoolEntry.ModuleEntryImpl.class; + case TAG_PACKAGE -> AbstractPoolEntry.PackageEntryImpl.class; + default -> null; + }; + return type != null && cls.isAssignableFrom(type); + } + + static T checkType(PoolEntry e, int index, Class cls) { + if (cls.isInstance(e)) return cls.cast(e); + throw new ConstantPoolException("Not a " + cls.getSimpleName() + " at index: " + index); + } + + @Override + public T entryByIndex(int index, Class cls) { + Objects.requireNonNull(cls); + if (index <= 0 || index >= constantPoolCount) { + throw new ConstantPoolException("Bad CP index: " + index); + } + PoolEntry info = cp[index]; + if (info == null) { + int offset = cpOffset[index]; + if (offset == 0) { + throw new ConstantPoolException("Unusable CP index: " + index); + } + int tag = readU1(offset); + if (!checkTag(tag, cls)) { + throw new ConstantPoolException( + "Bad tag (" + tag + ") at index (" + index + ") position (" + offset + "), expected " + cls.getSimpleName()); + } + final int q = offset + 1; + info = switch (tag) { + case TAG_UTF8 -> new AbstractPoolEntry.Utf8EntryImpl(this, index, buffer, q + 2, readU2(q)); + case TAG_INTEGER -> new AbstractPoolEntry.IntegerEntryImpl(this, index, readInt(q)); + case TAG_FLOAT -> new AbstractPoolEntry.FloatEntryImpl(this, index, readFloat(q)); + case TAG_LONG -> new AbstractPoolEntry.LongEntryImpl(this, index, readLong(q)); + case TAG_DOUBLE -> new AbstractPoolEntry.DoubleEntryImpl(this, index, readDouble(q)); + case TAG_CLASS -> new AbstractPoolEntry.ClassEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_STRING -> new AbstractPoolEntry.StringEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_FIELDREF -> new AbstractPoolEntry.FieldRefEntryImpl(this, index, (AbstractPoolEntry.ClassEntryImpl) readClassEntry(q), + (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_METHODREF -> new AbstractPoolEntry.MethodRefEntryImpl(this, index, (AbstractPoolEntry.ClassEntryImpl) readClassEntry(q), + (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_INTERFACEMETHODREF -> new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, index, (AbstractPoolEntry.ClassEntryImpl) readClassEntry(q), + (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_NAMEANDTYPE -> new AbstractPoolEntry.NameAndTypeEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q), + (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q + 2)); + case TAG_METHODHANDLE -> new AbstractPoolEntry.MethodHandleEntryImpl(this, index, readU1(q), + readEntry(q + 1, AbstractPoolEntry.AbstractMemberRefEntry.class)); + case TAG_METHODTYPE -> new AbstractPoolEntry.MethodTypeEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_CONSTANTDYNAMIC -> new AbstractPoolEntry.ConstantDynamicEntryImpl(this, index, readU2(q), (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_INVOKEDYNAMIC -> new AbstractPoolEntry.InvokeDynamicEntryImpl(this, index, readU2(q), (AbstractPoolEntry.NameAndTypeEntryImpl) readNameAndTypeEntry(q + 2)); + case TAG_MODULE -> new AbstractPoolEntry.ModuleEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + case TAG_PACKAGE -> new AbstractPoolEntry.PackageEntryImpl(this, index, (AbstractPoolEntry.Utf8EntryImpl) readUtf8Entry(q)); + default -> throw new ConstantPoolException( + "Bad tag (" + tag + ") at index (" + index + ") position (" + offset + ")"); + }; + cp[index] = info; + } + return checkType(info, index, cls); + } + + public int skipAttributeHolder(int offset) { + int p = offset; + int cnt = readU2(p); + p += 2; + for (int i = 0; i < cnt; ++i) { + int len = readInt(p + 2); + p += 6; + if (len < 0 || len > classfileLength - p) { + throw new IllegalArgumentException("attribute " + readUtf8Entry(p - 6).stringValue() + " too big to handle"); + } + p += len; + } + return p; + } + + @Override + public PoolEntry readEntry(int pos) { + return entryByIndex(readU2(pos)); + } + + @Override + public T readEntry(int pos, Class cls) { + Objects.requireNonNull(cls); + return entryByIndex(readU2(pos), cls); + } + + @Override + public PoolEntry readEntryOrNull(int pos) { + int index = readU2(pos); + if (index == 0) { + return null; + } + return entryByIndex(index); + } + + @Override + public T readEntryOrNull(int offset, Class cls) { + Objects.requireNonNull(cls); + int index = readU2(offset); + if (index == 0) { + return null; + } + return entryByIndex(index, cls); + } + + @Override + public Utf8Entry readUtf8Entry(int pos) { + return readEntry(pos, Utf8Entry.class); + } + + @Override + public Utf8Entry readUtf8EntryOrNull(int pos) { + return readEntryOrNull(pos, Utf8Entry.class); + } + + @Override + public ModuleEntry readModuleEntry(int pos) { + return readEntry(pos, ModuleEntry.class); + } + + @Override + public PackageEntry readPackageEntry(int pos) { + return readEntry(pos, PackageEntry.class); + } + + @Override + public ClassEntry readClassEntry(int pos) { + return readEntry(pos, ClassEntry.class); + } + + @Override + public NameAndTypeEntry readNameAndTypeEntry(int pos) { + return readEntry(pos, NameAndTypeEntry.class); + } + + @Override + public MethodHandleEntry readMethodHandleEntry(int pos) { + return readEntry(pos, MethodHandleEntry.class); + } + + @Override + public boolean compare(BufWriter bufWriter, + int bufWriterOffset, + int classReaderOffset, + int length) { + try { + return Arrays.equals(((BufWriterImpl) bufWriter).elems, + bufWriterOffset, bufWriterOffset + length, + buffer, classReaderOffset, classReaderOffset + length); + } catch (IndexOutOfBoundsException e) { + throw outOfBoundsError(e); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl$1.class new file mode 100644 index 00000000..728fbf3a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl.class new file mode 100644 index 00000000..c8f064ec Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl.java new file mode 100644 index 00000000..d1a02d9a --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ClassRemapperImpl.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.Annotation; +import java.lang.classfile.AnnotationElement; +import java.lang.classfile.AnnotationValue; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassSignature; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.FieldBuilder; +import java.lang.classfile.FieldElement; +import java.lang.classfile.FieldModel; +import java.lang.classfile.FieldTransform; +import java.lang.classfile.Interfaces; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.MethodElement; +import java.lang.classfile.MethodModel; +import java.lang.classfile.MethodSignature; +import java.lang.classfile.MethodTransform; +import java.lang.classfile.Signature; +import java.lang.classfile.Superclass; +import java.lang.classfile.TypeAnnotation; +import java.lang.classfile.attribute.AnnotationDefaultAttribute; +import java.lang.classfile.attribute.EnclosingMethodAttribute; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.attribute.InnerClassInfo; +import java.lang.classfile.attribute.InnerClassesAttribute; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModuleProvideInfo; +import java.lang.classfile.attribute.NestHostAttribute; +import java.lang.classfile.attribute.NestMembersAttribute; +import java.lang.classfile.attribute.PermittedSubclassesAttribute; +import java.lang.classfile.attribute.RecordAttribute; +import java.lang.classfile.attribute.RecordComponentInfo; +import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.SignatureAttribute; +import java.lang.classfile.components.ClassRemapper; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.classfile.instruction.ConstantInstruction.LoadConstantInstruction; +import java.lang.classfile.instruction.ExceptionCatch; +import java.lang.classfile.instruction.FieldInstruction; +import java.lang.classfile.instruction.InvokeDynamicInstruction; +import java.lang.classfile.instruction.InvokeInstruction; +import java.lang.classfile.instruction.LocalVariable; +import java.lang.classfile.instruction.LocalVariableType; +import java.lang.classfile.instruction.NewMultiArrayInstruction; +import java.lang.classfile.instruction.NewObjectInstruction; +import java.lang.classfile.instruction.NewReferenceArrayInstruction; +import java.lang.classfile.instruction.TypeCheckInstruction; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.DynamicCallSiteDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.List; +import java.util.function.Function; + +public record ClassRemapperImpl(Function mapFunction) implements ClassRemapper { + + @Override + public void accept(ClassBuilder clb, ClassElement cle) { + switch (cle) { + case FieldModel fm -> + clb.withField(fm.fieldName().stringValue(), map( + fm.fieldTypeSymbol()), fb -> + fm.forEachElement(asFieldTransform().resolve(fb).consumer())); + case MethodModel mm -> + clb.withMethod(mm.methodName().stringValue(), mapMethodDesc( + mm.methodTypeSymbol()), mm.flags().flagsMask(), mb -> + mm.forEachElement(asMethodTransform().resolve(mb).consumer())); + case Superclass sc -> + clb.withSuperclass(map(sc.superclassEntry().asSymbol())); + case Interfaces ins -> + clb.withInterfaceSymbols(Util.mappedList(ins.interfaces(), in -> + map(in.asSymbol()))); + case SignatureAttribute sa -> + clb.with(SignatureAttribute.of(mapClassSignature(sa.asClassSignature()))); + case InnerClassesAttribute ica -> + clb.with(InnerClassesAttribute.of(ica.classes().stream().map(ici -> + InnerClassInfo.of(map(ici.innerClass().asSymbol()), + ici.outerClass().map(oc -> map(oc.asSymbol())), + ici.innerName().map(Utf8Entry::stringValue), + ici.flagsMask())).toList())); + case EnclosingMethodAttribute ema -> + clb.with(EnclosingMethodAttribute.of(map(ema.enclosingClass().asSymbol()), + ema.enclosingMethodName().map(Utf8Entry::stringValue), + ema.enclosingMethodTypeSymbol().map(this::mapMethodDesc))); + case RecordAttribute ra -> + clb.with(RecordAttribute.of(ra.components().stream() + .map(this::mapRecordComponent).toList())); + case ModuleAttribute ma -> + clb.with(ModuleAttribute.of(ma.moduleName(), ma.moduleFlagsMask(), + ma.moduleVersion().orElse(null), + ma.requires(), ma.exports(), ma.opens(), + ma.uses().stream().map(ce -> + clb.constantPool().classEntry(map(ce.asSymbol()))).toList(), + ma.provides().stream().map(mp -> + ModuleProvideInfo.of(map(mp.provides().asSymbol()), + mp.providesWith().stream().map(pw -> + map(pw.asSymbol())).toList())).toList())); + case NestHostAttribute nha -> + clb.with(NestHostAttribute.of(map(nha.nestHost().asSymbol()))); + case NestMembersAttribute nma -> + clb.with(NestMembersAttribute.ofSymbols(nma.nestMembers().stream() + .map(nm -> map(nm.asSymbol())).toList())); + case PermittedSubclassesAttribute psa -> + clb.with(PermittedSubclassesAttribute.ofSymbols( + psa.permittedSubclasses().stream().map(ps -> + map(ps.asSymbol())).toList())); + case RuntimeVisibleAnnotationsAttribute aa -> + clb.with(RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + clb.with(RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + clb.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + clb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + clb.with(cle); + } + } + + @Override + public FieldTransform asFieldTransform() { + return (FieldBuilder fb, FieldElement fe) -> { + switch (fe) { + case SignatureAttribute sa -> + fb.with(SignatureAttribute.of( + mapSignature(sa.asTypeSignature()))); + case RuntimeVisibleAnnotationsAttribute aa -> + fb.with(RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + fb.with(RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + fb.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + fb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + fb.with(fe); + } + }; + } + + @Override + public MethodTransform asMethodTransform() { + return (MethodBuilder mb, MethodElement me) -> { + switch (me) { + case AnnotationDefaultAttribute ada -> + mb.with(AnnotationDefaultAttribute.of( + mapAnnotationValue(ada.defaultValue()))); + case CodeModel com -> + mb.transformCode(com, asCodeTransform()); + case ExceptionsAttribute ea -> + mb.with(ExceptionsAttribute.ofSymbols( + ea.exceptions().stream().map(ce -> + map(ce.asSymbol())).toList())); + case SignatureAttribute sa -> + mb.with(SignatureAttribute.of( + mapMethodSignature(sa.asMethodSignature()))); + case RuntimeVisibleAnnotationsAttribute aa -> + mb.with(RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeInvisibleAnnotationsAttribute aa -> + mb.with(RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations()))); + case RuntimeVisibleParameterAnnotationsAttribute paa -> + mb.with(RuntimeVisibleParameterAnnotationsAttribute.of( + paa.parameterAnnotations().stream() + .map(this::mapAnnotations).toList())); + case RuntimeInvisibleParameterAnnotationsAttribute paa -> + mb.with(RuntimeInvisibleParameterAnnotationsAttribute.of( + paa.parameterAnnotations().stream() + .map(this::mapAnnotations).toList())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + mb.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + mb.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + mb.with(me); + } + }; + } + + @Override + public CodeTransform asCodeTransform() { + return (CodeBuilder cob, CodeElement coe) -> { + switch (coe) { + case FieldInstruction fai -> + cob.fieldAccess(fai.opcode(), map(fai.owner().asSymbol()), + fai.name().stringValue(), map(fai.typeSymbol())); + case InvokeInstruction ii -> + cob.invoke(ii.opcode(), map(ii.owner().asSymbol()), + ii.name().stringValue(), mapMethodDesc(ii.typeSymbol()), + ii.isInterface()); + case InvokeDynamicInstruction idi -> + cob.invokedynamic(DynamicCallSiteDesc.of( + mapDirectMethodHandle(idi.bootstrapMethod()), idi.name().stringValue(), + mapMethodDesc(idi.typeSymbol()), + idi.bootstrapArgs().stream().map(this::mapConstantValue).toArray(ConstantDesc[]::new))); + case NewObjectInstruction c -> + cob.new_(map(c.className().asSymbol())); + case NewReferenceArrayInstruction c -> + cob.anewarray(map(c.componentType().asSymbol())); + case NewMultiArrayInstruction c -> + cob.multianewarray(map(c.arrayType().asSymbol()), c.dimensions()); + case TypeCheckInstruction c -> + cob.with(TypeCheckInstruction.of(c.opcode(), map(c.type().asSymbol()))); + case ExceptionCatch c -> + cob.exceptionCatch(c.tryStart(), c.tryEnd(), c.handler(),c.catchType() + .map(d -> TemporaryConstantPool.INSTANCE.classEntry(map(d.asSymbol())))); + case LocalVariable c -> + cob.localVariable(c.slot(), c.name().stringValue(), map(c.typeSymbol()), + c.startScope(), c.endScope()); + case LocalVariableType c -> + cob.localVariableType(c.slot(), c.name().stringValue(), + mapSignature(c.signatureSymbol()), c.startScope(), c.endScope()); + case LoadConstantInstruction ldc -> + cob.loadConstant(ldc.opcode(), + mapConstantValue(ldc.constantValue())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + cob.with(RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + cob.with(RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations()))); + default -> + cob.with(coe); + } + }; + } + + @Override + public ClassDesc map(ClassDesc desc) { + if (desc == null) return null; + if (desc.isArray()) return map(desc.componentType()).arrayType(); + if (desc.isPrimitive()) return desc; + return mapFunction.apply(desc); + } + + MethodTypeDesc mapMethodDesc(MethodTypeDesc desc) { + return MethodTypeDesc.of(map(desc.returnType()), + desc.parameterList().stream().map(this::map).toArray(ClassDesc[]::new)); + } + + ClassSignature mapClassSignature(ClassSignature signature) { + return ClassSignature.of(mapTypeParams(signature.typeParameters()), + mapSignature(signature.superclassSignature()), + signature.superinterfaceSignatures().stream() + .map(this::mapSignature).toArray(Signature.ClassTypeSig[]::new)); + } + + MethodSignature mapMethodSignature(MethodSignature signature) { + return MethodSignature.of(mapTypeParams(signature.typeParameters()), + signature.throwableSignatures().stream().map(this::mapSignature).toList(), + mapSignature(signature.result()), + signature.arguments().stream() + .map(this::mapSignature).toArray(Signature[]::new)); + } + + RecordComponentInfo mapRecordComponent(RecordComponentInfo component) { + return RecordComponentInfo.of(component.name().stringValue(), + map(component.descriptorSymbol()), + component.attributes().stream().map(atr -> + switch (atr) { + case SignatureAttribute sa -> + SignatureAttribute.of( + mapSignature(sa.asTypeSignature())); + case RuntimeVisibleAnnotationsAttribute aa -> + RuntimeVisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations())); + case RuntimeInvisibleAnnotationsAttribute aa -> + RuntimeInvisibleAnnotationsAttribute.of( + mapAnnotations(aa.annotations())); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + RuntimeVisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations())); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + RuntimeInvisibleTypeAnnotationsAttribute.of( + mapTypeAnnotations(aa.annotations())); + default -> atr; + }).toList()); + } + + DirectMethodHandleDesc mapDirectMethodHandle(DirectMethodHandleDesc dmhd) { + return switch (dmhd.kind()) { + case GETTER, SETTER, STATIC_GETTER, STATIC_SETTER -> + MethodHandleDesc.ofField(dmhd.kind(), map(dmhd.owner()), + dmhd.methodName(), + map(ClassDesc.ofDescriptor(dmhd.lookupDescriptor()))); + default -> + MethodHandleDesc.ofMethod(dmhd.kind(), map(dmhd.owner()), + dmhd.methodName(), + mapMethodDesc(MethodTypeDesc.ofDescriptor(dmhd.lookupDescriptor()))); + }; + } + + ConstantDesc mapConstantValue(ConstantDesc value) { + return switch (value) { + case ClassDesc cd -> + map(cd); + case DynamicConstantDesc dcd -> + mapDynamicConstant(dcd); + case DirectMethodHandleDesc dmhd -> + mapDirectMethodHandle(dmhd); + case MethodTypeDesc mtd -> + mapMethodDesc(mtd); + default -> value; + }; + } + + DynamicConstantDesc mapDynamicConstant(DynamicConstantDesc dcd) { + return DynamicConstantDesc.ofNamed(mapDirectMethodHandle(dcd.bootstrapMethod()), + dcd.constantName(), + map(dcd.constantType()), + dcd.bootstrapArgsList().stream().map(this::mapConstantValue).toArray(ConstantDesc[]::new)); + } + + @SuppressWarnings("unchecked") + S mapSignature(S signature) { + return (S) switch (signature) { + case Signature.ArrayTypeSig ats -> + Signature.ArrayTypeSig.of(mapSignature(ats.componentSignature())); + case Signature.ClassTypeSig cts -> + Signature.ClassTypeSig.of( + cts.outerType().map(this::mapSignature).orElse(null), + map(cts.classDesc()), + cts.typeArgs().stream().map(ta -> switch (ta) { + case Signature.TypeArg.Unbounded u -> u; + case Signature.TypeArg.Bounded bta -> Signature.TypeArg.bounded( + bta.wildcardIndicator(), mapSignature(bta.boundType())); + }).toArray(Signature.TypeArg[]::new)); + default -> signature; + }; + } + + List mapAnnotations(List annotations) { + return annotations.stream().map(this::mapAnnotation).toList(); + } + + Annotation mapAnnotation(Annotation a) { + return Annotation.of(map(a.classSymbol()), a.elements().stream().map(el -> + AnnotationElement.of(el.name(), mapAnnotationValue(el.value()))).toList()); + } + + AnnotationValue mapAnnotationValue(AnnotationValue val) { + return switch (val) { + case AnnotationValue.OfAnnotation oa -> + AnnotationValue.ofAnnotation(mapAnnotation(oa.annotation())); + case AnnotationValue.OfArray oa -> + AnnotationValue.ofArray(oa.values().stream().map(this::mapAnnotationValue).toList()); + case AnnotationValue.OfConstant oc -> oc; + case AnnotationValue.OfClass oc -> + AnnotationValue.ofClass(map(oc.classSymbol())); + case AnnotationValue.OfEnum oe -> + AnnotationValue.ofEnum(map(oe.classSymbol()), oe.constantName().stringValue()); + }; + } + + List mapTypeAnnotations(List typeAnnotations) { + return typeAnnotations.stream().map(a -> TypeAnnotation.of(a.targetInfo(), + a.targetPath(), map(a.classSymbol()), + a.elements().stream().map(el -> AnnotationElement.of(el.name(), + mapAnnotationValue(el.value()))).toList())).toList(); + } + + List mapTypeParams(List typeParams) { + return typeParams.stream().map(tp -> Signature.TypeParam.of(tp.identifier(), + tp.classBound().map(this::mapSignature), + tp.interfaceBounds().stream() + .map(this::mapSignature).toArray(Signature.RefTypeSig[]::new))).toList(); + } + +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$1.class new file mode 100644 index 00000000..c892bd51 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$2.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$2.class new file mode 100644 index 00000000..808bf565 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$2.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$3.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$3.class new file mode 100644 index 00000000..b39e04b6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$3.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$4.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$4.class new file mode 100644 index 00000000..ab174f6f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$4.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$ExceptionHandlerAction.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$ExceptionHandlerAction.class new file mode 100644 index 00000000..6f888912 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl$ExceptionHandlerAction.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl.class new file mode 100644 index 00000000..28252f61 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl.java new file mode 100644 index 00000000..06058bba --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/CodeImpl.java @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.*; +import java.lang.classfile.attribute.CodeAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.instruction.*; + +import static java.lang.classfile.ClassFile.*; + +public final class CodeImpl + extends BoundAttribute.BoundCodeAttribute + implements CodeModel, LabelContext { + + static final Instruction[] SINGLETON_INSTRUCTIONS = new Instruction[256]; + + static { + for (var o : Opcode.values()) { + if (o.sizeIfFixed() == 1) { + SINGLETON_INSTRUCTIONS[o.bytecode()] = switch (o.kind()) { + case ARRAY_LOAD -> ArrayLoadInstruction.of(o); + case ARRAY_STORE -> ArrayStoreInstruction.of(o); + case CONSTANT -> ConstantInstruction.ofIntrinsic(o); + case CONVERT -> ConvertInstruction.of(o); + case LOAD -> LoadInstruction.of(o, o.slot()); + case MONITOR -> MonitorInstruction.of(o); + case NOP -> NopInstruction.of(); + case OPERATOR -> OperatorInstruction.of(o); + case RETURN -> ReturnInstruction.of(o); + case STACK -> StackInstruction.of(o); + case STORE -> StoreInstruction.of(o, o.slot()); + case THROW_EXCEPTION -> ThrowInstruction.of(); + default -> throw new AssertionError("invalid opcode: " + o); + }; + } + } + } + + List exceptionTable; + List> attributes; + + // Inflated for iteration + LabelImpl[] labels; + int[] lineNumbers; + boolean inflated; + + public CodeImpl(AttributedElement enclosing, + ClassReader reader, + AttributeMapper mapper, + int payloadStart) { + super(enclosing, reader, mapper, payloadStart); + } + + // LabelContext + + @Override + public Label newLabel() { + throw new UnsupportedOperationException("CodeAttribute only supports fixed labels"); + } + + @Override + public void setLabelTarget(Label label, int bci) { + throw new UnsupportedOperationException("CodeAttribute only supports fixed labels"); + } + + @Override + public Label getLabel(int bci) { + if (bci < 0 || bci > codeLength) + throw new IllegalArgumentException(String.format("Bytecode offset out of range; bci=%d, codeLength=%d", + bci, codeLength)); + if (labels == null) + labels = new LabelImpl[codeLength + 1]; + LabelImpl l = labels[bci]; + if (l == null) + l = labels[bci] = new LabelImpl(this, bci); + return l; + } + + @Override + public int labelToBci(Label label) { + LabelImpl lab = (LabelImpl) label; + if (lab.labelContext() != this) + throw new IllegalArgumentException(String.format("Illegal label reuse; context=%s, label=%s", + this, lab.labelContext())); + return lab.getBCI(); + } + + private void inflateMetadata() { + if (!inflated) { + if (labels == null) + labels = new LabelImpl[codeLength + 1]; + if (classReader.context().lineNumbersOption() == ClassFile.LineNumbersOption.PASS_LINE_NUMBERS) + inflateLineNumbers(); + inflateJumpTargets(); + inflateTypeAnnotations(); + inflated = true; + } + } + + // CodeAttribute + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, classReader, attributePos, classReader.customAttributes()); + } + return attributes; + } + + @Override + public void writeTo(BufWriter buf) { + if (buf.canWriteDirect(classReader)) { + super.writeTo(buf); + } + else { + DirectCodeBuilder.build((MethodInfo) enclosingMethod, + new Consumer() { + @Override + public void accept(CodeBuilder cb) { + forEachElement(cb); + } + }, + (SplitConstantPool)buf.constantPool(), + ((BufWriterImpl)buf).context(), + null).writeTo(buf); + } + } + + // CodeModel + + @Override + public Optional parent() { + return Optional.of(enclosingMethod); + } + + @Override + public void forEachElement(Consumer consumer) { + inflateMetadata(); + boolean doLineNumbers = (lineNumbers != null); + generateCatchTargets(consumer); + if (classReader.context().debugElementsOption() == ClassFile.DebugElementsOption.PASS_DEBUG) + generateDebugElements(consumer); + for (int pos=codeStart; pos exceptionHandlers() { + if (exceptionTable == null) { + inflateMetadata(); + exceptionTable = new ArrayList<>(exceptionHandlerCnt); + iterateExceptionHandlers(new ExceptionHandlerAction() { + @Override + public void accept(int s, int e, int h, int c) { + ClassEntry catchTypeEntry = c == 0 + ? null + : constantPool().entryByIndex(c, ClassEntry.class); + exceptionTable.add(new AbstractPseudoInstruction.ExceptionCatchImpl(getLabel(h), getLabel(s), getLabel(e), catchTypeEntry)); + } + }); + exceptionTable = Collections.unmodifiableList(exceptionTable); + } + return exceptionTable; + } + + public boolean compareCodeBytes(BufWriter buf, int offset, int len) { + return codeLength == len + && classReader.compare(buf, offset, codeStart, codeLength); + } + + private int adjustForObjectOrUninitialized(int bci) { + int vt = classReader.readU1(bci); + //inflate newTarget labels from Uninitialized VTIs + if (vt == 8) inflateLabel(classReader.readU2(bci + 1)); + return (vt == 7 || vt == 8) ? bci + 3 : bci + 1; + } + + private void inflateLabel(int bci) { + if (bci < 0 || bci > codeLength) + throw new IllegalArgumentException(String.format("Bytecode offset out of range; bci=%d, codeLength=%d", + bci, codeLength)); + if (labels[bci] == null) + labels[bci] = new LabelImpl(this, bci); + } + + private void inflateLineNumbers() { + for (Attribute a : attributes()) { + if (a.attributeMapper() == Attributes.lineNumberTable()) { + BoundLineNumberTableAttribute attr = (BoundLineNumberTableAttribute) a; + if (lineNumbers == null) + lineNumbers = new int[codeLength + 1]; + + int nLn = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (nLn * 4); + for (; p < pEnd; p += 4) { + int startPc = classReader.readU2(p); + if (startPc > codeLength) { + throw new IllegalArgumentException(String.format( + "Line number start_pc out of range; start_pc=%d, codeLength=%d", startPc, codeLength)); + } + int lineNumber = classReader.readU2(p + 2); + lineNumbers[startPc] = lineNumber; + } + } + } + } + + private void inflateJumpTargets() { + Optional a = findAttribute(Attributes.stackMapTable()); + if (a.isEmpty()) { + if (classReader.readU2(6) <= ClassFile.JAVA_6_VERSION) { + //fallback to jump targets inflation without StackMapTableAttribute + for (int pos=codeStart; pos br.target(); + case DiscontinuedInstruction.JsrInstruction jsr -> jsr.target(); + default -> {} + } + pos += i.sizeInBytes(); + } + } + return; + } + @SuppressWarnings("unchecked") + int stackMapPos = ((BoundAttribute) a.get()).payloadStart; + + int bci = -1; //compensate for offsetDelta + 1 + int nEntries = classReader.readU2(stackMapPos); + int p = stackMapPos + 2; + for (int i = 0; i < nEntries; ++i) { + int frameType = classReader.readU1(p); + int offsetDelta; + if (frameType < 64) { + offsetDelta = frameType; + ++p; + } + else if (frameType < 128) { + offsetDelta = frameType & 0x3f; + p = adjustForObjectOrUninitialized(p + 1); + } + else + switch (frameType) { + case 247 -> { + offsetDelta = classReader.readU2(p + 1); + p = adjustForObjectOrUninitialized(p + 3); + } + case 248, 249, 250, 251 -> { + offsetDelta = classReader.readU2(p + 1); + p += 3; + } + case 252, 253, 254 -> { + offsetDelta = classReader.readU2(p + 1); + int k = frameType - 251; + p += 3; + for (int c = 0; c < k; ++c) { + p = adjustForObjectOrUninitialized(p); + } + } + case 255 -> { + offsetDelta = classReader.readU2(p + 1); + p += 3; + int k = classReader.readU2(p); + p += 2; + for (int c = 0; c < k; ++c) { + p = adjustForObjectOrUninitialized(p); + } + k = classReader.readU2(p); + p += 2; + for (int c = 0; c < k; ++c) { + p = adjustForObjectOrUninitialized(p); + } + } + default -> throw new IllegalArgumentException("Bad frame type: " + frameType); + } + bci += offsetDelta + 1; + inflateLabel(bci); + } + } + + private void inflateTypeAnnotations() { + findAttribute(Attributes.runtimeVisibleTypeAnnotations()).ifPresent(RuntimeVisibleTypeAnnotationsAttribute::annotations); + findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).ifPresent(RuntimeInvisibleTypeAnnotationsAttribute::annotations); + } + + private void generateCatchTargets(Consumer consumer) { + // We attach all catch targets to bci zero, because trying to attach them + // to their range could subtly affect the order of exception processing + iterateExceptionHandlers(new ExceptionHandlerAction() { + @Override + public void accept(int s, int e, int h, int c) { + ClassEntry catchType = c == 0 + ? null + : classReader.entryByIndex(c, ClassEntry.class); + consumer.accept(new AbstractPseudoInstruction.ExceptionCatchImpl(getLabel(h), getLabel(s), getLabel(e), catchType)); + } + }); + } + + private void generateDebugElements(Consumer consumer) { + for (Attribute a : attributes()) { + if (a.attributeMapper() == Attributes.characterRangeTable()) { + var attr = (BoundCharacterRangeTableAttribute) a; + int cnt = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (cnt * 14); + for (; p < pEnd; p += 14) { + var instruction = new BoundCharacterRange(this, p); + inflateLabel(instruction.startPc()); + inflateLabel(instruction.endPc() + 1); + consumer.accept(instruction); + } + } + else if (a.attributeMapper() == Attributes.localVariableTable()) { + var attr = (BoundLocalVariableTableAttribute) a; + int cnt = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (cnt * 10); + for (; p < pEnd; p += 10) { + BoundLocalVariable instruction = new BoundLocalVariable(this, p); + inflateLabel(instruction.startPc()); + inflateLabel(instruction.startPc() + instruction.length()); + consumer.accept(instruction); + } + } + else if (a.attributeMapper() == Attributes.localVariableTypeTable()) { + var attr = (BoundLocalVariableTypeTableAttribute) a; + int cnt = classReader.readU2(attr.payloadStart); + int p = attr.payloadStart + 2; + int pEnd = p + (cnt * 10); + for (; p < pEnd; p += 10) { + BoundLocalVariableType instruction = new BoundLocalVariableType(this, p); + inflateLabel(instruction.startPc()); + inflateLabel(instruction.startPc() + instruction.length()); + consumer.accept(instruction); + } + } + else if (a.attributeMapper() == Attributes.runtimeVisibleTypeAnnotations()) { + consumer.accept((BoundRuntimeVisibleTypeAnnotationsAttribute) a); + } + else if (a.attributeMapper() == Attributes.runtimeInvisibleTypeAnnotations()) { + consumer.accept((BoundRuntimeInvisibleTypeAnnotationsAttribute) a); + } + } + } + + public interface ExceptionHandlerAction { + void accept(int start, int end, int handler, int catchTypeIndex); + } + + public void iterateExceptionHandlers(ExceptionHandlerAction a) { + int p = exceptionHandlerPos + 2; + for (int i = 0; i < exceptionHandlerCnt; ++i) { + a.accept(classReader.readU2(p), classReader.readU2(p + 2), classReader.readU2(p + 4), classReader.readU2(p + 6)); + p += 8; + } + } + + private Instruction bcToInstruction(int bc, int pos) { + return switch (bc) { + case BIPUSH -> new AbstractInstruction.BoundArgumentConstantInstruction(Opcode.BIPUSH, CodeImpl.this, pos); + case SIPUSH -> new AbstractInstruction.BoundArgumentConstantInstruction(Opcode.SIPUSH, CodeImpl.this, pos); + case LDC -> new AbstractInstruction.BoundLoadConstantInstruction(Opcode.LDC, CodeImpl.this, pos); + case LDC_W -> new AbstractInstruction.BoundLoadConstantInstruction(Opcode.LDC_W, CodeImpl.this, pos); + case LDC2_W -> new AbstractInstruction.BoundLoadConstantInstruction(Opcode.LDC2_W, CodeImpl.this, pos); + case ILOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ILOAD, CodeImpl.this, pos); + case LLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.LLOAD, CodeImpl.this, pos); + case FLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.FLOAD, CodeImpl.this, pos); + case DLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.DLOAD, CodeImpl.this, pos); + case ALOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ALOAD, CodeImpl.this, pos); + case ISTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ISTORE, CodeImpl.this, pos); + case LSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.LSTORE, CodeImpl.this, pos); + case FSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.FSTORE, CodeImpl.this, pos); + case DSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.DSTORE, CodeImpl.this, pos); + case ASTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ASTORE, CodeImpl.this, pos); + case IINC -> new AbstractInstruction.BoundIncrementInstruction(Opcode.IINC, CodeImpl.this, pos); + case IFEQ -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFEQ, CodeImpl.this, pos); + case IFNE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNE, CodeImpl.this, pos); + case IFLT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFLT, CodeImpl.this, pos); + case IFGE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFGE, CodeImpl.this, pos); + case IFGT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFGT, CodeImpl.this, pos); + case IFLE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFLE, CodeImpl.this, pos); + case IF_ICMPEQ -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPEQ, CodeImpl.this, pos); + case IF_ICMPNE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPNE, CodeImpl.this, pos); + case IF_ICMPLT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPLT, CodeImpl.this, pos); + case IF_ICMPGE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPGE, CodeImpl.this, pos); + case IF_ICMPGT -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPGT, CodeImpl.this, pos); + case IF_ICMPLE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ICMPLE, CodeImpl.this, pos); + case IF_ACMPEQ -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ACMPEQ, CodeImpl.this, pos); + case IF_ACMPNE -> new AbstractInstruction.BoundBranchInstruction(Opcode.IF_ACMPNE, CodeImpl.this, pos); + case GOTO -> new AbstractInstruction.BoundBranchInstruction(Opcode.GOTO, CodeImpl.this, pos); + case TABLESWITCH -> new AbstractInstruction.BoundTableSwitchInstruction(Opcode.TABLESWITCH, CodeImpl.this, pos); + case LOOKUPSWITCH -> new AbstractInstruction.BoundLookupSwitchInstruction(Opcode.LOOKUPSWITCH, CodeImpl.this, pos); + case GETSTATIC -> new AbstractInstruction.BoundFieldInstruction(Opcode.GETSTATIC, CodeImpl.this, pos); + case PUTSTATIC -> new AbstractInstruction.BoundFieldInstruction(Opcode.PUTSTATIC, CodeImpl.this, pos); + case GETFIELD -> new AbstractInstruction.BoundFieldInstruction(Opcode.GETFIELD, CodeImpl.this, pos); + case PUTFIELD -> new AbstractInstruction.BoundFieldInstruction(Opcode.PUTFIELD, CodeImpl.this, pos); + case INVOKEVIRTUAL -> new AbstractInstruction.BoundInvokeInstruction(Opcode.INVOKEVIRTUAL, CodeImpl.this, pos); + case INVOKESPECIAL -> new AbstractInstruction.BoundInvokeInstruction(Opcode.INVOKESPECIAL, CodeImpl.this, pos); + case INVOKESTATIC -> new AbstractInstruction.BoundInvokeInstruction(Opcode.INVOKESTATIC, CodeImpl.this, pos); + case INVOKEINTERFACE -> new AbstractInstruction.BoundInvokeInterfaceInstruction(Opcode.INVOKEINTERFACE, CodeImpl.this, pos); + case INVOKEDYNAMIC -> new AbstractInstruction.BoundInvokeDynamicInstruction(Opcode.INVOKEDYNAMIC, CodeImpl.this, pos); + case NEW -> new AbstractInstruction.BoundNewObjectInstruction(CodeImpl.this, pos); + case NEWARRAY -> new AbstractInstruction.BoundNewPrimitiveArrayInstruction(Opcode.NEWARRAY, CodeImpl.this, pos); + case ANEWARRAY -> new AbstractInstruction.BoundNewReferenceArrayInstruction(Opcode.ANEWARRAY, CodeImpl.this, pos); + case CHECKCAST -> new AbstractInstruction.BoundTypeCheckInstruction(Opcode.CHECKCAST, CodeImpl.this, pos); + case INSTANCEOF -> new AbstractInstruction.BoundTypeCheckInstruction(Opcode.INSTANCEOF, CodeImpl.this, pos); + + case WIDE -> { + int bclow = classReader.readU1(pos + 1); + yield switch (bclow) { + case ILOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ILOAD_W, this, pos); + case LLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.LLOAD_W, this, pos); + case FLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.FLOAD_W, this, pos); + case DLOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.DLOAD_W, this, pos); + case ALOAD -> new AbstractInstruction.BoundLoadInstruction(Opcode.ALOAD_W, this, pos); + case ISTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ISTORE_W, this, pos); + case LSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.LSTORE_W, this, pos); + case FSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.FSTORE_W, this, pos); + case DSTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.DSTORE_W, this, pos); + case ASTORE -> new AbstractInstruction.BoundStoreInstruction(Opcode.ASTORE_W, this, pos); + case IINC -> new AbstractInstruction.BoundIncrementInstruction(Opcode.IINC_W, this, pos); + case RET -> new AbstractInstruction.BoundRetInstruction(Opcode.RET_W, this, pos); + default -> throw new IllegalArgumentException("unknown wide instruction: " + bclow); + }; + } + + case MULTIANEWARRAY -> new AbstractInstruction.BoundNewMultidimensionalArrayInstruction(Opcode.MULTIANEWARRAY, CodeImpl.this, pos); + case IFNULL -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNULL, CodeImpl.this, pos); + case IFNONNULL -> new AbstractInstruction.BoundBranchInstruction(Opcode.IFNONNULL, CodeImpl.this, pos); + case GOTO_W -> new AbstractInstruction.BoundBranchInstruction(Opcode.GOTO_W, CodeImpl.this, pos); + + case JSR -> new AbstractInstruction.BoundJsrInstruction(Opcode.JSR, CodeImpl.this, pos); + case RET -> new AbstractInstruction.BoundRetInstruction(Opcode.RET, this, pos); + case JSR_W -> new AbstractInstruction.BoundJsrInstruction(Opcode.JSR_W, CodeImpl.this, pos); + default -> { + Instruction instr = SINGLETON_INSTRUCTIONS[bc]; + if (instr == null) + throw new IllegalArgumentException("unknown instruction: " + bc); + yield instr; + } + }; + } + + @Override + public String toString() { + return String.format("CodeModel[id=%d]", System.identityHashCode(this)); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeLocalsShifterImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeLocalsShifterImpl.class new file mode 100644 index 00000000..6e23d14b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeLocalsShifterImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeLocalsShifterImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/CodeLocalsShifterImpl.java new file mode 100644 index 00000000..a68225fb --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/CodeLocalsShifterImpl.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.Signature; +import java.lang.classfile.TypeKind; +import java.lang.classfile.components.CodeLocalsShifter; +import java.lang.classfile.instruction.IncrementInstruction; +import java.lang.classfile.instruction.LoadInstruction; +import java.lang.classfile.instruction.LocalVariable; +import java.lang.classfile.instruction.LocalVariableType; +import java.lang.classfile.instruction.StoreInstruction; + +import java.util.Arrays; + +public final class CodeLocalsShifterImpl implements CodeLocalsShifter { + + private int[] locals = new int[0]; + private final int fixed; + + public CodeLocalsShifterImpl(int fixed) { + this.fixed = fixed; + } + + @Override + public void accept(CodeBuilder cob, CodeElement coe) { + switch (coe) { + case LoadInstruction li -> + cob.loadLocal( + li.typeKind(), + shift(cob, li.slot(), li.typeKind())); + case StoreInstruction si -> + cob.storeLocal( + si.typeKind(), + shift(cob, si.slot(), si.typeKind())); + case IncrementInstruction ii -> + cob.iinc( + shift(cob, ii.slot(), TypeKind.IntType), + ii.constant()); + case LocalVariable lv -> + cob.localVariable( + shift(cob, lv.slot(), TypeKind.fromDescriptor(lv.type().stringValue())), + lv.name(), + lv.type(), + lv.startScope(), + lv.endScope()); + case LocalVariableType lvt -> + cob.localVariableType( + shift(cob, lvt.slot(), + (lvt.signatureSymbol() instanceof Signature.BaseTypeSig bsig) + ? TypeKind.fromDescriptor(bsig.signatureString()) + : TypeKind.ReferenceType), + lvt.name(), + lvt.signature(), + lvt.startScope(), + lvt.endScope()); + default -> cob.with(coe); + } + } + + private int shift(CodeBuilder cob, int slot, TypeKind tk) { + if (tk == TypeKind.VoidType) throw new IllegalArgumentException("Illegal local void type"); + if (slot >= fixed) { + int key = 2*slot - fixed + tk.slotSize() - 1; + if (key >= locals.length) locals = Arrays.copyOf(locals, key + 20); + slot = locals[key] - 1; + if (slot < 0) { + slot = cob.allocateLocal(tk); + locals[key] = slot + 1; + if (tk.slotSize() == 2) locals[key - 1] = slot + 1; + } + } + return slot; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeRelabelerImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeRelabelerImpl.class new file mode 100644 index 00000000..4a20e102 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeRelabelerImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeRelabelerImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/CodeRelabelerImpl.java new file mode 100644 index 00000000..f191cbf3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/CodeRelabelerImpl.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.Label; +import java.lang.classfile.components.CodeRelabeler; +import java.lang.classfile.instruction.BranchInstruction; +import java.lang.classfile.instruction.CharacterRange; +import java.lang.classfile.instruction.ExceptionCatch; +import java.lang.classfile.instruction.LabelTarget; +import java.lang.classfile.instruction.LocalVariable; +import java.lang.classfile.instruction.LocalVariableType; +import java.lang.classfile.instruction.LookupSwitchInstruction; +import java.lang.classfile.instruction.SwitchCase; +import java.lang.classfile.instruction.TableSwitchInstruction; + +import java.util.function.BiFunction; + +public record CodeRelabelerImpl(BiFunction mapFunction) implements CodeRelabeler { + + @Override + public Label relabel(Label label, CodeBuilder cob) { + return mapFunction.apply(label, cob); + } + + @Override + public void accept(CodeBuilder cob, CodeElement coe) { + switch (coe) { + case BranchInstruction bi -> + cob.branch( + bi.opcode(), + relabel(bi.target(), cob)); + case LookupSwitchInstruction lsi -> + cob.lookupswitch( + relabel(lsi.defaultTarget(), cob), + lsi.cases().stream().map(c -> + SwitchCase.of( + c.caseValue(), + relabel(c.target(), cob))).toList()); + case TableSwitchInstruction tsi -> + cob.tableswitch( + tsi.lowValue(), + tsi.highValue(), + relabel(tsi.defaultTarget(), cob), + tsi.cases().stream().map(c -> + SwitchCase.of( + c.caseValue(), + relabel(c.target(), cob))).toList()); + case LabelTarget lt -> + cob.labelBinding( + relabel(lt.label(), cob)); + case ExceptionCatch ec -> + cob.exceptionCatch( + relabel(ec.tryStart(), cob), + relabel(ec.tryEnd(), cob), + relabel(ec.handler(), cob), + ec.catchType()); + case LocalVariable lv -> + cob.localVariable( + lv.slot(), + lv.name().stringValue(), + lv.typeSymbol(), + relabel(lv.startScope(), cob), + relabel(lv.endScope(), cob)); + case LocalVariableType lvt -> + cob.localVariableType( + lvt.slot(), + lvt.name().stringValue(), + lvt.signatureSymbol(), + relabel(lvt.startScope(), cob), + relabel(lvt.endScope(), cob)); + case CharacterRange chr -> + cob.characterRange( + relabel(chr.startScope(), cob), + relabel(chr.endScope(), cob), + chr.characterRangeStart(), + chr.characterRangeEnd(), + chr.flags()); + default -> + cob.with(coe); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$1.class new file mode 100644 index 00000000..0329f251 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Item.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Item.class new file mode 100644 index 00000000..38d49c51 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Item.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Stack$1.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Stack$1.class new file mode 100644 index 00000000..22cd7af5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Stack$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Stack.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Stack.class new file mode 100644 index 00000000..f0a1b08b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl$Stack.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl.class new file mode 100644 index 00000000..2d1b7ebe Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl.java new file mode 100644 index 00000000..2b87821b --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/CodeStackTrackerImpl.java @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.Label; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import java.lang.classfile.components.CodeStackTracker; +import java.lang.classfile.instruction.ArrayLoadInstruction; +import java.lang.classfile.instruction.ArrayStoreInstruction; +import java.lang.classfile.instruction.BranchInstruction; +import java.lang.classfile.instruction.ConstantInstruction; +import java.lang.classfile.instruction.ConvertInstruction; +import java.lang.classfile.instruction.ExceptionCatch; +import java.lang.classfile.instruction.FieldInstruction; +import java.lang.classfile.instruction.InvokeDynamicInstruction; +import java.lang.classfile.instruction.InvokeInstruction; +import java.lang.classfile.instruction.LabelTarget; +import java.lang.classfile.instruction.LoadInstruction; +import java.lang.classfile.instruction.LookupSwitchInstruction; +import java.lang.classfile.instruction.MonitorInstruction; +import java.lang.classfile.instruction.NewMultiArrayInstruction; +import java.lang.classfile.instruction.NewObjectInstruction; +import java.lang.classfile.instruction.NewPrimitiveArrayInstruction; +import java.lang.classfile.instruction.NewReferenceArrayInstruction; +import java.lang.classfile.instruction.NopInstruction; +import java.lang.classfile.instruction.OperatorInstruction; +import java.lang.classfile.instruction.ReturnInstruction; +import java.lang.classfile.instruction.StackInstruction; +import java.lang.classfile.instruction.StoreInstruction; +import java.lang.classfile.instruction.TableSwitchInstruction; +import java.lang.classfile.instruction.ThrowInstruction; +import java.lang.classfile.instruction.TypeCheckInstruction; + +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Consumer; + +public final class CodeStackTrackerImpl implements CodeStackTracker { + + private static record Item(TypeKind type, Item next) { + } + + private final class Stack extends AbstractCollection { + + private Item top; + private int count, realSize; + + Stack(Item top, int count, int realSize) { + this.top = top; + this.count = count; + this.realSize = realSize; + } + + @Override + public Iterator iterator() { + return new Iterator() { + Item i = top; + + @Override + public boolean hasNext() { + return i != null; + } + + @Override + public TypeKind next() { + if (i == null) { + throw new NoSuchElementException(); + } + var t = i.type; + i = i.next; + return t; + } + }; + } + + @Override + public int size() { + return count; + } + + private void push(TypeKind type) { + top = new Item(type, top); + realSize += type.slotSize(); + count++; + if (maxSize != null && realSize > maxSize) maxSize = realSize; + } + + private TypeKind pop() { + var t = top.type; + realSize -= t.slotSize(); + count--; + top = top.next; + return t; + } + } + + private Stack stack = new Stack(null, 0, 0); + private Integer maxSize = 0; + + public CodeStackTrackerImpl(TypeKind... initialStack) { + for (int i = initialStack.length - 1; i >= 0; i--) + push(initialStack[i]); + } + + @Override + public Optional> stack() { + return Optional.ofNullable(fork()); + } + + @Override + public Optional maxStackSize() { + return Optional.ofNullable(maxSize); + } + + private final Map map = new HashMap<>(); + + private void push(TypeKind type) { + if (stack != null) { + if (type != TypeKind.VoidType) stack.push(type); + } else { + maxSize = null; + } + } + + private void pop(int i) { + if (stack != null) { + while (i-- > 0) stack.pop(); + } else { + maxSize = null; + } + } + + private Stack fork() { + return stack == null ? null : new Stack(stack.top, stack.count, stack.realSize); + } + + private void withStack(Consumer c) { + if (stack != null) c.accept(stack); + else maxSize = null; + } + + @Override + public void accept(CodeBuilder cb, CodeElement el) { + cb.with(el); + switch (el) { + case ArrayLoadInstruction i -> { + pop(2);push(i.typeKind()); + } + case ArrayStoreInstruction i -> + pop(3); + case BranchInstruction i -> { + if (i.opcode() == Opcode.GOTO || i.opcode() == Opcode.GOTO_W) { + map.put(i.target(), stack); + stack = null; + } else { + pop(1); + map.put(i.target(), fork()); + } + } + case ConstantInstruction i -> + push(i.typeKind()); + case ConvertInstruction i -> { + pop(1);push(i.toType()); + } + case FieldInstruction i -> { + switch (i.opcode()) { + case GETSTATIC -> + push(TypeKind.fromDescriptor(i.type().stringValue())); + case GETFIELD -> { + pop(1);push(TypeKind.fromDescriptor(i.type().stringValue())); + } + case PUTSTATIC -> + pop(1); + case PUTFIELD -> + pop(2); + } + } + case InvokeDynamicInstruction i -> { + var type = i.typeSymbol(); + pop(type.parameterCount()); + push(TypeKind.from(type.returnType())); + } + case InvokeInstruction i -> { + var type = i.typeSymbol(); + pop(type.parameterCount()); + if (i.opcode() != Opcode.INVOKESTATIC) pop(1); + push(TypeKind.from(type.returnType())); + } + case LoadInstruction i -> + push(i.typeKind()); + case StoreInstruction i -> + pop(1); + case LookupSwitchInstruction i -> { + map.put(i.defaultTarget(), stack); + for (var c : i.cases()) map.put(c.target(), fork()); + stack = null; + } + case MonitorInstruction i -> + pop(1); + case NewMultiArrayInstruction i -> { + pop(i.dimensions());push(TypeKind.ReferenceType); + } + case NewObjectInstruction i -> + push(TypeKind.ReferenceType); + case NewPrimitiveArrayInstruction i -> { + pop(1);push(TypeKind.ReferenceType); + } + case NewReferenceArrayInstruction i -> { + pop(1);push(TypeKind.ReferenceType); + } + case NopInstruction i -> {} + case OperatorInstruction i -> { + switch (i.opcode()) { + case ARRAYLENGTH, INEG, LNEG, FNEG, DNEG -> pop(1); + default -> pop(2); + } + push(i.typeKind()); + } + case ReturnInstruction i -> + stack = null; + case StackInstruction i -> { + switch (i.opcode()) { + case POP -> pop(1); + case POP2 -> withStack(s -> { + if (s.pop().slotSize() == 1) s.pop(); + }); + case DUP -> withStack(s -> { + var v = s.pop();s.push(v);s.push(v); + }); + case DUP2 -> withStack(s -> { + var v1 = s.pop(); + if (v1.slotSize() == 1) { + var v2 = s.pop(); + s.push(v2);s.push(v1); + s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v1); + } + }); + case DUP_X1 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + s.push(v1);s.push(v2);s.push(v1); + }); + case DUP_X2 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + if (v2.slotSize() == 1) { + var v3 = s.pop(); + s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v2);s.push(v1); + } + }); + case DUP2_X1 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + if (v1.slotSize() == 1) { + var v3 = s.pop(); + s.push(v2);s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v2);s.push(v1); + } + }); + case DUP2_X2 -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + if (v1.slotSize() == 1) { + var v3 = s.pop(); + if (v3.slotSize() == 1) { + var v4 = s.pop(); + s.push(v2);s.push(v1);s.push(v4);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v2);s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } + } else { + if (v2.slotSize() == 1) { + var v3 = s.pop(); + s.push(v1);s.push(v3);s.push(v2);s.push(v1); + } else { + s.push(v1);s.push(v2);s.push(v1); + } + } + }); + case SWAP -> withStack(s -> { + var v1 = s.pop(); + var v2 = s.pop(); + s.push(v1);s.push(v2); + }); + } + } + case TableSwitchInstruction i -> { + map.put(i.defaultTarget(), stack); + for (var c : i.cases()) map.put(c.target(), fork()); + stack = null; + } + case ThrowInstruction i -> + stack = null; + case TypeCheckInstruction i -> { + switch (i.opcode()) { + case CHECKCAST -> { + pop(1);push(TypeKind.ReferenceType); + } + case INSTANCEOF -> { + pop(1);push(TypeKind.IntType); + } + } + } + case ExceptionCatch i -> + map.put(i.handler(), new Stack(new Item(TypeKind.ReferenceType, null), 1, 1)); + case LabelTarget i -> + stack = map.getOrDefault(i.label(), stack); + default -> {} + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectClassBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectClassBuilder.class new file mode 100644 index 00000000..8761d428 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectClassBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectClassBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/DirectClassBuilder.java new file mode 100644 index 00000000..915c3ad8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/DirectClassBuilder.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDescs; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassModel; +import java.lang.classfile.ClassFile; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.FieldBuilder; +import java.lang.classfile.FieldModel; +import java.lang.classfile.FieldTransform; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.MethodModel; +import java.lang.classfile.MethodTransform; +import java.lang.classfile.WritableElement; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class DirectClassBuilder + extends AbstractDirectBuilder + implements ClassBuilder { + + final ClassEntry thisClassEntry; + private final List> fields = new ArrayList<>(); + private final List> methods = new ArrayList<>(); + private ClassEntry superclassEntry; + private List interfaceEntries; + private int majorVersion; + private int minorVersion; + private int flags; + private int sizeHint; + + public DirectClassBuilder(SplitConstantPool constantPool, + ClassFileImpl context, + ClassEntry thisClass) { + super(constantPool, context); + this.thisClassEntry = AbstractPoolEntry.maybeClone(constantPool, thisClass); + this.flags = ClassFile.DEFAULT_CLASS_FLAGS; + this.superclassEntry = null; + this.interfaceEntries = Collections.emptyList(); + this.majorVersion = ClassFile.latestMajorVersion(); + this.minorVersion = ClassFile.latestMinorVersion(); + } + + @Override + public ClassBuilder with(ClassElement element) { + if (element instanceof AbstractElement ae) { + ae.writeTo(this); + } else { + writeAttribute((CustomAttribute)element); + } + return this; + } + + @Override + public ClassBuilder withField(Utf8Entry name, + Utf8Entry descriptor, + Consumer handler) { + return withField(new DirectFieldBuilder(constantPool, context, name, descriptor, null) + .run(handler)); + } + + @Override + public ClassBuilder transformField(FieldModel field, FieldTransform transform) { + DirectFieldBuilder builder = new DirectFieldBuilder(constantPool, context, field.fieldName(), + field.fieldType(), field); + builder.transform(field, transform); + return withField(builder); + } + + @Override + public ClassBuilder withMethod(Utf8Entry name, + Utf8Entry descriptor, + int flags, + Consumer handler) { + return withMethod(new DirectMethodBuilder(constantPool, context, name, descriptor, flags, null) + .run(handler)); + } + + @Override + public ClassBuilder transformMethod(MethodModel method, MethodTransform transform) { + DirectMethodBuilder builder = new DirectMethodBuilder(constantPool, context, method.methodName(), + method.methodType(), + method.flags().flagsMask(), + method); + builder.transform(method, transform); + return withMethod(builder); + } + + // internal / for use by elements + + public ClassBuilder withField(WritableElement field) { + fields.add(field); + return this; + } + + public ClassBuilder withMethod(WritableElement method) { + methods.add(method); + return this; + } + + void setSuperclass(ClassEntry superclassEntry) { + this.superclassEntry = superclassEntry; + } + + void setInterfaces(List interfaces) { + this.interfaceEntries = interfaces; + } + + void setVersion(int major, int minor) { + this.majorVersion = major; + this.minorVersion = minor; + } + + void setFlags(int flags) { + this.flags = flags; + } + + public void setSizeHint(int sizeHint) { + this.sizeHint = sizeHint; + } + + + public byte[] build() { + + // The logic of this is very carefully ordered. We want to avoid + // repeated buffer copyings, so we accumulate lists of writers which + // all get written later into the same buffer. But, writing can often + // trigger CP / BSM insertions, so we cannot run the CP writer or + // BSM writers until everything else is written. + + // Do this early because it might trigger CP activity + ClassEntry superclass = superclassEntry; + if (superclass != null) + superclass = AbstractPoolEntry.maybeClone(constantPool, superclass); + else if ((flags & ClassFile.ACC_MODULE) == 0 && !"java/lang/Object".equals(thisClassEntry.asInternalName())) + superclass = constantPool.classEntry(ConstantDescs.CD_Object); + List ies = new ArrayList<>(interfaceEntries.size()); + for (ClassEntry ce : interfaceEntries) + ies.add(AbstractPoolEntry.maybeClone(constantPool, ce)); + + // We maintain two writers, and then we join them at the end + int size = sizeHint == 0 ? 256 : sizeHint; + BufWriter head = new BufWriterImpl(constantPool, context, size); + BufWriterImpl tail = new BufWriterImpl(constantPool, context, size, thisClassEntry, majorVersion); + + // The tail consists of fields and methods, and attributes + // This should trigger all the CP/BSM mutation + tail.writeList(fields); + tail.writeList(methods); + int attributesOffset = tail.size(); + attributes.writeTo(tail); + + // Now we have to append the BSM, if there is one + boolean written = constantPool.writeBootstrapMethods(tail); + if (written) { + // Update attributes count + tail.patchInt(attributesOffset, 2, attributes.size() + 1); + } + + // Now we can make the head + head.writeInt(ClassFile.MAGIC_NUMBER); + head.writeU2(minorVersion); + head.writeU2(majorVersion); + constantPool.writeTo(head); + head.writeU2(flags); + head.writeIndex(thisClassEntry); + head.writeIndexOrZero(superclass); + head.writeListIndices(ies); + + // Join head and tail into an exact-size buffer + byte[] result = new byte[head.size() + tail.size()]; + head.copyTo(result, 0); + tail.copyTo(result, head.size()); + return result; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$1.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$1.class new file mode 100644 index 00000000..1a77df0d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$2.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$2.class new file mode 100644 index 00000000..9aaf5062 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$2.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$3.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$3.class new file mode 100644 index 00000000..143e998e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$3.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$4.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$4.class new file mode 100644 index 00000000..20f149ff Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$4.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$5.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$5.class new file mode 100644 index 00000000..091ef3c0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$5.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$6.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$6.class new file mode 100644 index 00000000..dc298d54 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$6.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$7.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$7.class new file mode 100644 index 00000000..e0274935 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$7.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$8.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$8.class new file mode 100644 index 00000000..43fdce95 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$8.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$DedupLineNumberTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$DedupLineNumberTableAttribute.class new file mode 100644 index 00000000..a23731c2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$DedupLineNumberTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$DeferredLabel.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$DeferredLabel.class new file mode 100644 index 00000000..4b1831f8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$DeferredLabel.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$LabelOverflowException.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$LabelOverflowException.class new file mode 100644 index 00000000..4fc13157 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder$LabelOverflowException.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder.class new file mode 100644 index 00000000..57848f90 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder.java new file mode 100644 index 00000000..0b6549a8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/DirectCodeBuilder.java @@ -0,0 +1,786 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import java.lang.classfile.Attribute; +import java.lang.classfile.Attributes; +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.Instruction; +import java.lang.classfile.Label; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import java.lang.classfile.instruction.SwitchCase; +import java.lang.classfile.attribute.CodeAttribute; +import java.lang.classfile.attribute.LineNumberTableAttribute; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.DoubleEntry; +import java.lang.classfile.constantpool.FieldRefEntry; +import java.lang.classfile.constantpool.InterfaceMethodRefEntry; +import java.lang.classfile.constantpool.InvokeDynamicEntry; +import java.lang.classfile.constantpool.LoadableConstantEntry; +import java.lang.classfile.constantpool.LongEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.instruction.CharacterRange; +import java.lang.classfile.instruction.ExceptionCatch; +import java.lang.classfile.instruction.LocalVariable; +import java.lang.classfile.instruction.LocalVariableType; + +import static java.lang.classfile.Opcode.GOTO; +import static java.lang.classfile.Opcode.GOTO_W; +import static java.lang.classfile.Opcode.IINC; +import static java.lang.classfile.Opcode.IINC_W; +import static java.lang.classfile.Opcode.JSR; +import static java.lang.classfile.Opcode.JSR_W; +import static java.lang.classfile.Opcode.LDC2_W; +import static java.lang.classfile.Opcode.LDC_W; + +public final class DirectCodeBuilder + extends AbstractDirectBuilder + implements TerminalCodeBuilder { + private final List characterRanges = new ArrayList<>(); + final List handlers = new ArrayList<>(); + private final List localVariables = new ArrayList<>(); + private final List localVariableTypes = new ArrayList<>(); + private final boolean transformFwdJumps, transformBackJumps; + private final Label startLabel, endLabel; + final MethodInfo methodInfo; + final BufWriter bytecodesBufWriter; + private CodeAttribute mruParent; + private int[] mruParentTable; + private Map parentMap; + private DedupLineNumberTableAttribute lineNumberWriter; + private int topLocal; + + List deferredLabels; + + /* Locals management + lazily computed maxLocal = -1 + first time: derive count from methodType descriptor (for new methods) & ACC_STATIC, + or model maxLocals (for transformation) + block builders inherit parent count + allocLocal(TypeKind) bumps by nSlots + */ + + public static Attribute build(MethodInfo methodInfo, + Consumer handler, + SplitConstantPool constantPool, + ClassFileImpl context, + CodeModel original) { + DirectCodeBuilder cb; + try { + handler.accept(cb = new DirectCodeBuilder(methodInfo, constantPool, context, original, false)); + cb.buildContent(); + } catch (LabelOverflowException loe) { + if (context.shortJumpsOption() == ClassFile.ShortJumpsOption.FIX_SHORT_JUMPS) { + handler.accept(cb = new DirectCodeBuilder(methodInfo, constantPool, context, original, true)); + cb.buildContent(); + } + else + throw loe; + } + return cb.content; + } + + private DirectCodeBuilder(MethodInfo methodInfo, + SplitConstantPool constantPool, + ClassFileImpl context, + CodeModel original, + boolean transformFwdJumps) { + super(constantPool, context); + setOriginal(original); + this.methodInfo = methodInfo; + this.transformFwdJumps = transformFwdJumps; + this.transformBackJumps = context.shortJumpsOption() == ClassFile.ShortJumpsOption.FIX_SHORT_JUMPS; + bytecodesBufWriter = (original instanceof CodeImpl cai) ? new BufWriterImpl(constantPool, context, cai.codeLength()) + : new BufWriterImpl(constantPool, context); + this.startLabel = new LabelImpl(this, 0); + this.endLabel = new LabelImpl(this, -1); + this.topLocal = Util.maxLocals(methodInfo.methodFlags(), methodInfo.methodTypeSymbol()); + if (original != null) + this.topLocal = Math.max(this.topLocal, original.maxLocals()); + } + + @Override + public CodeBuilder with(CodeElement element) { + if (element instanceof AbstractElement ae) { + ae.writeTo(this); + } else { + writeAttribute((CustomAttribute)element); + } + return this; + } + + @Override + public Label newLabel() { + return new LabelImpl(this, -1); + } + + @Override + public Label startLabel() { + return startLabel; + } + + @Override + public Label endLabel() { + return endLabel; + } + + @Override + public int receiverSlot() { + return methodInfo.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return methodInfo.parameterSlot(paramNo); + } + + public int curTopLocal() { + return topLocal; + } + + @Override + public int allocateLocal(TypeKind typeKind) { + int retVal = topLocal; + topLocal += typeKind.slotSize(); + return retVal; + } + + public int curPc() { + return bytecodesBufWriter.size(); + } + + public MethodInfo methodInfo() { + return methodInfo; + } + + private Attribute content = null; + + private void writeExceptionHandlers(BufWriter buf) { + int pos = buf.size(); + int handlersSize = handlers.size(); + buf.writeU2(handlersSize); + for (AbstractPseudoInstruction.ExceptionCatchImpl h : handlers) { + int startPc = labelToBci(h.tryStart()); + int endPc = labelToBci(h.tryEnd()); + int handlerPc = labelToBci(h.handler()); + if (startPc == -1 || endPc == -1 || handlerPc == -1) { + if (context.deadLabelsOption() == ClassFile.DeadLabelsOption.DROP_DEAD_LABELS) { + handlersSize--; + } else { + throw new IllegalArgumentException("Unbound label in exception handler"); + } + } else { + buf.writeU2(startPc); + buf.writeU2(endPc); + buf.writeU2(handlerPc); + buf.writeIndexOrZero(h.catchTypeEntry()); + handlersSize++; + } + } + if (handlersSize < handlers.size()) + buf.patchInt(pos, 2, handlersSize); + } + + private void buildContent() { + if (content != null) return; + setLabelTarget(endLabel); + + // Backfill branches for which Label didn't have position yet + processDeferredLabels(); + + if (context.debugElementsOption() == ClassFile.DebugElementsOption.PASS_DEBUG) { + if (!characterRanges.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.characterRangeTable()) { + + @Override + public void writeBody(BufWriter b) { + int pos = b.size(); + int crSize = characterRanges.size(); + b.writeU2(crSize); + for (CharacterRange cr : characterRanges) { + var start = labelToBci(cr.startScope()); + var end = labelToBci(cr.endScope()); + if (start == -1 || end == -1) { + if (context.deadLabelsOption() == ClassFile.DeadLabelsOption.DROP_DEAD_LABELS) { + crSize--; + } else { + throw new IllegalArgumentException("Unbound label in character range"); + } + } else { + b.writeU2(start); + b.writeU2(end - 1); + b.writeInt(cr.characterRangeStart()); + b.writeInt(cr.characterRangeEnd()); + b.writeU2(cr.flags()); + } + } + if (crSize < characterRanges.size()) + b.patchInt(pos, 2, crSize); + } + }; + attributes.withAttribute(a); + } + + if (!localVariables.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.localVariableTable()) { + @Override + public void writeBody(BufWriter b) { + int pos = b.size(); + int lvSize = localVariables.size(); + b.writeU2(lvSize); + for (LocalVariable l : localVariables) { + if (!l.writeTo(b)) { + if (context.deadLabelsOption() == ClassFile.DeadLabelsOption.DROP_DEAD_LABELS) { + lvSize--; + } else { + throw new IllegalArgumentException("Unbound label in local variable type"); + } + } + } + if (lvSize < localVariables.size()) + b.patchInt(pos, 2, lvSize); + } + }; + attributes.withAttribute(a); + } + + if (!localVariableTypes.isEmpty()) { + Attribute a = new UnboundAttribute.AdHocAttribute<>(Attributes.localVariableTypeTable()) { + @Override + public void writeBody(BufWriter b) { + int pos = b.size(); + int lvtSize = localVariableTypes.size(); + b.writeU2(localVariableTypes.size()); + for (LocalVariableType l : localVariableTypes) { + if (!l.writeTo(b)) { + if (context.deadLabelsOption() == ClassFile.DeadLabelsOption.DROP_DEAD_LABELS) { + lvtSize--; + } else { + throw new IllegalArgumentException("Unbound label in local variable type"); + } + } + } + if (lvtSize < localVariableTypes.size()) + b.patchInt(pos, 2, lvtSize); + } + }; + attributes.withAttribute(a); + } + } + + if (lineNumberWriter != null) { + attributes.withAttribute(lineNumberWriter); + } + + content = new UnboundAttribute.AdHocAttribute<>(Attributes.code()) { + + private void writeCounters(boolean codeMatch, BufWriterImpl buf) { + if (codeMatch) { + buf.writeU2(original.maxStack()); + buf.writeU2(original.maxLocals()); + } else { + StackCounter cntr = StackCounter.of(DirectCodeBuilder.this, buf); + buf.writeU2(cntr.maxStack()); + buf.writeU2(cntr.maxLocals()); + } + } + + private void generateStackMaps(BufWriterImpl buf) throws IllegalArgumentException { + //new instance of generator immediately calculates maxStack, maxLocals, all frames, + // patches dead bytecode blocks and removes them from exception table + StackMapGenerator gen = StackMapGenerator.of(DirectCodeBuilder.this, buf); + attributes.withAttribute(gen.stackMapTableAttribute()); + buf.writeU2(gen.maxStack()); + buf.writeU2(gen.maxLocals()); + } + + private void tryGenerateStackMaps(boolean codeMatch, BufWriterImpl buf) { + if (buf.getMajorVersion() >= ClassFile.JAVA_6_VERSION) { + try { + generateStackMaps(buf); + } catch (IllegalArgumentException e) { + //failover following JVMS-4.10 + if (buf.getMajorVersion() == ClassFile.JAVA_6_VERSION) { + writeCounters(codeMatch, buf); + } else { + throw e; + } + } + } else { + writeCounters(codeMatch, buf); + } + } + + @Override + public void writeBody(BufWriter b) { + BufWriterImpl buf = (BufWriterImpl) b; + buf.setLabelContext(DirectCodeBuilder.this); + + int codeLength = curPc(); + if (codeLength == 0 || codeLength >= 65536) { + throw new IllegalArgumentException(String.format( + "Code length %d is outside the allowed range in %s%s", + codeLength, + methodInfo.methodName().stringValue(), + methodInfo.methodTypeSymbol().displayDescriptor())); + } + + if (codeAndExceptionsMatch(codeLength)) { + switch (context.stackMapsOption()) { + case STACK_MAPS_WHEN_REQUIRED -> { + attributes.withAttribute(original.findAttribute(Attributes.stackMapTable()).orElse(null)); + writeCounters(true, buf); + } + case GENERATE_STACK_MAPS -> + generateStackMaps(buf); + case DROP_STACK_MAPS -> + writeCounters(true, buf); + } + } else { + switch (context.stackMapsOption()) { + case STACK_MAPS_WHEN_REQUIRED -> + tryGenerateStackMaps(false, buf); + case GENERATE_STACK_MAPS -> + generateStackMaps(buf); + case DROP_STACK_MAPS -> + writeCounters(false, buf); + } + } + + buf.writeInt(codeLength); + buf.writeBytes(bytecodesBufWriter); + writeExceptionHandlers(b); + attributes.writeTo(b); + buf.setLabelContext(null); + } + }; + } + + private static class DedupLineNumberTableAttribute extends UnboundAttribute.AdHocAttribute { + private final BufWriterImpl buf; + private int lastPc, lastLine, writtenLine; + + public DedupLineNumberTableAttribute(ConstantPoolBuilder constantPool, ClassFileImpl context) { + super(Attributes.lineNumberTable()); + buf = new BufWriterImpl(constantPool, context); + lastPc = -1; + writtenLine = -1; + } + + private void push() { + //subsequent identical line numbers are skipped + if (lastPc >= 0 && lastLine != writtenLine) { + buf.writeU2(lastPc); + buf.writeU2(lastLine); + writtenLine = lastLine; + } + } + + //writes are expected ordered by pc in ascending sequence + public void writeLineNumber(int pc, int lineNo) { + //for each pc only the latest line number is written + if (lastPc != pc && lastLine != lineNo) { + push(); + lastPc = pc; + } + lastLine = lineNo; + } + + @Override + public void writeBody(BufWriter b) { + throw new UnsupportedOperationException(); + } + + @Override + public void writeTo(BufWriter b) { + b.writeIndex(b.constantPool().utf8Entry(Attributes.NAME_LINE_NUMBER_TABLE)); + push(); + b.writeInt(buf.size() + 2); + b.writeU2(buf.size() / 4); + b.writeBytes(buf); + } + } + + private boolean codeAndExceptionsMatch(int codeLength) { + boolean codeAttributesMatch; + if (original instanceof CodeImpl cai && canWriteDirect(cai.constantPool())) { + codeAttributesMatch = cai.codeLength == curPc() + && cai.compareCodeBytes(bytecodesBufWriter, 0, codeLength); + if (codeAttributesMatch) { + BufWriter bw = new BufWriterImpl(constantPool, context); + writeExceptionHandlers(bw); + codeAttributesMatch = cai.classReader.compare(bw, 0, cai.exceptionHandlerPos, bw.size()); + } + } + else + codeAttributesMatch = false; + return codeAttributesMatch; + } + + // Writing support + + private record DeferredLabel(int labelPc, int size, int instructionPc, Label label) { } + + private void writeLabelOffset(int nBytes, int instructionPc, Label label) { + int targetBci = labelToBci(label); + if (targetBci == -1) { + int pc = curPc(); + bytecodesBufWriter.writeIntBytes(nBytes, 0); + if (deferredLabels == null) + deferredLabels = new ArrayList<>(); + deferredLabels.add(new DeferredLabel(pc, nBytes, instructionPc, label)); + } + else { + int branchOffset = targetBci - instructionPc; + if (nBytes == 2 && (short)branchOffset != branchOffset) throw new LabelOverflowException(); + bytecodesBufWriter.writeIntBytes(nBytes, branchOffset); + } + } + + private void processDeferredLabels() { + if (deferredLabels != null) { + for (DeferredLabel dl : deferredLabels) { + int branchOffset = labelToBci(dl.label) - dl.instructionPc; + if (dl.size == 2 && (short)branchOffset != branchOffset) throw new LabelOverflowException(); + bytecodesBufWriter.patchInt(dl.labelPc, dl.size, branchOffset); + } + } + } + + // Instruction writing + + public void writeBytecode(Opcode opcode) { + if (opcode.isWide()) + bytecodesBufWriter.writeU1(ClassFile.WIDE); + bytecodesBufWriter.writeU1(opcode.bytecode() & 0xFF); + } + + public void writeLocalVar(Opcode opcode, int localVar) { + writeBytecode(opcode); + switch (opcode.sizeIfFixed()) { + case 1 -> { } + case 2 -> bytecodesBufWriter.writeU1(localVar); + case 4 -> bytecodesBufWriter.writeU2(localVar); + default -> throw new IllegalArgumentException("Unexpected instruction size: " + opcode); + } + } + + public void writeIncrement(int slot, int val) { + Opcode opcode = (slot < 256 && val < 128 && val > -127) + ? IINC + : IINC_W; + writeBytecode(opcode); + if (opcode.isWide()) { + bytecodesBufWriter.writeU2(slot); + bytecodesBufWriter.writeU2(val); + } else { + bytecodesBufWriter.writeU1(slot); + bytecodesBufWriter.writeU1(val); + } + } + + public void writeBranch(Opcode op, Label target) { + int instructionPc = curPc(); + int targetBci = labelToBci(target); + //transform short-opcode forward jumps if enforced, and backward jumps if enabled and overflowing + if (op.sizeIfFixed() == 3 && (targetBci == -1 + ? transformFwdJumps + : (transformBackJumps + && targetBci - instructionPc < Short.MIN_VALUE))) { + if (op == GOTO) { + writeBytecode(GOTO_W); + writeLabelOffset(4, instructionPc, target); + } else if (op == JSR) { + writeBytecode(JSR_W); + writeLabelOffset(4, instructionPc, target); + } else { + writeBytecode(BytecodeHelpers.reverseBranchOpcode(op)); + Label bypassJump = newLabel(); + writeLabelOffset(2, instructionPc, bypassJump); + writeBytecode(GOTO_W); + writeLabelOffset(4, instructionPc + 3, target); + labelBinding(bypassJump); + } + } else { + writeBytecode(op); + writeLabelOffset(op.sizeIfFixed() == 3 ? 2 : 4, instructionPc, target); + } + } + + public void writeLookupSwitch(Label defaultTarget, List cases) { + int instructionPc = curPc(); + writeBytecode(Opcode.LOOKUPSWITCH); + int pad = 4 - (curPc() % 4); + if (pad != 4) + bytecodesBufWriter.writeIntBytes(pad, 0); + writeLabelOffset(4, instructionPc, defaultTarget); + bytecodesBufWriter.writeInt(cases.size()); + cases = new ArrayList<>(cases); + cases.sort(new Comparator() { + @Override + public int compare(SwitchCase c1, SwitchCase c2) { + return Integer.compare(c1.caseValue(), c2.caseValue()); + } + }); + for (var c : cases) { + bytecodesBufWriter.writeInt(c.caseValue()); + writeLabelOffset(4, instructionPc, c.target()); + } + } + + public void writeTableSwitch(int low, int high, Label defaultTarget, List cases) { + int instructionPc = curPc(); + writeBytecode(Opcode.TABLESWITCH); + int pad = 4 - (curPc() % 4); + if (pad != 4) + bytecodesBufWriter.writeIntBytes(pad, 0); + writeLabelOffset(4, instructionPc, defaultTarget); + bytecodesBufWriter.writeInt(low); + bytecodesBufWriter.writeInt(high); + var caseMap = new HashMap(cases.size()); + for (var c : cases) { + caseMap.put(c.caseValue(), c.target()); + } + for (long l = low; l<=high; l++) { + writeLabelOffset(4, instructionPc, caseMap.getOrDefault((int)l, defaultTarget)); + } + } + + public void writeFieldAccess(Opcode opcode, FieldRefEntry ref) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(ref); + } + + public void writeInvokeNormal(Opcode opcode, MemberRefEntry ref) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(ref); + } + + public void writeInvokeInterface(Opcode opcode, + InterfaceMethodRefEntry ref, + int count) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(ref); + bytecodesBufWriter.writeU1(count); + bytecodesBufWriter.writeU1(0); + } + + public void writeInvokeDynamic(InvokeDynamicEntry ref) { + writeBytecode(Opcode.INVOKEDYNAMIC); + bytecodesBufWriter.writeIndex(ref); + bytecodesBufWriter.writeU2(0); + } + + public void writeNewObject(ClassEntry type) { + writeBytecode(Opcode.NEW); + bytecodesBufWriter.writeIndex(type); + } + + public void writeNewPrimitiveArray(int newArrayCode) { + writeBytecode(Opcode.NEWARRAY); + bytecodesBufWriter.writeU1(newArrayCode); + } + + public void writeNewReferenceArray(ClassEntry type) { + writeBytecode(Opcode.ANEWARRAY); + bytecodesBufWriter.writeIndex(type); + } + + public void writeNewMultidimensionalArray(int dimensions, ClassEntry type) { + writeBytecode(Opcode.MULTIANEWARRAY); + bytecodesBufWriter.writeIndex(type); + bytecodesBufWriter.writeU1(dimensions); + } + + public void writeTypeCheck(Opcode opcode, ClassEntry type) { + writeBytecode(opcode); + bytecodesBufWriter.writeIndex(type); + } + + public void writeArgumentConstant(Opcode opcode, int value) { + writeBytecode(opcode); + if (opcode.sizeIfFixed() == 3) { + bytecodesBufWriter.writeU2(value); + } else { + bytecodesBufWriter.writeU1(value); + } + } + + public void writeLoadConstant(Opcode opcode, LoadableConstantEntry value) { + // Make sure Long and Double have LDC2_W and + // rewrite to _W if index is > 256 + int index = AbstractPoolEntry.maybeClone(constantPool, value).index(); + Opcode op = opcode; + if (value instanceof LongEntry || value instanceof DoubleEntry) { + op = LDC2_W; + } else if (index >= 256) + op = LDC_W; + + writeBytecode(op); + if (op.sizeIfFixed() == 3) { + bytecodesBufWriter.writeU2(index); + } else { + bytecodesBufWriter.writeU1(index); + } + } + + @Override + public Label getLabel(int bci) { + throw new UnsupportedOperationException("Lookup by BCI not supported by CodeBuilder"); + } + + @Override + public int labelToBci(Label label) { + LabelImpl lab = (LabelImpl) label; + LabelContext context = lab.labelContext(); + if (context == this) { + return lab.getBCI(); + } + else if (context == mruParent) { + return mruParentTable[lab.getBCI()] - 1; + } + else if (context instanceof CodeAttribute parent) { + if (parentMap == null) + parentMap = new IdentityHashMap<>(); + //critical JDK bootstrap path, cannot use lambda here + int[] table = parentMap.computeIfAbsent(parent, new Function() { + @Override + public int[] apply(CodeAttribute x) { + return new int[parent.codeLength() + 1]; + } + }); + + mruParent = parent; + mruParentTable = table; + return mruParentTable[lab.getBCI()] - 1; + } + else if (context instanceof BufferedCodeBuilder) { + // Hijack the label + return lab.getBCI(); + } + else { + throw new IllegalStateException(String.format("Unexpected label context %s in =%s", context, this)); + } + } + + public void setLineNumber(int lineNo) { + if (lineNumberWriter == null) + lineNumberWriter = new DedupLineNumberTableAttribute(constantPool, context); + lineNumberWriter.writeLineNumber(curPc(), lineNo); + } + + public void setLabelTarget(Label label) { + setLabelTarget(label, curPc()); + } + + @Override + public void setLabelTarget(Label label, int bci) { + LabelImpl lab = (LabelImpl) label; + LabelContext context = lab.labelContext(); + + if (context == this) { + if (lab.getBCI() != -1) + throw new IllegalArgumentException("Setting label target for already-set label"); + lab.setBCI(bci); + } + else if (context == mruParent) { + mruParentTable[lab.getBCI()] = bci + 1; + } + else if (context instanceof CodeAttribute parent) { + if (parentMap == null) + parentMap = new IdentityHashMap<>(); + int[] table = parentMap.computeIfAbsent(parent, new Function() { + @Override + public int[] apply(CodeAttribute x) { + return new int[parent.codeLength() + 1]; + } + }); + + mruParent = parent; + mruParentTable = table; + mruParentTable[lab.getBCI()] = bci + 1; + } + else if (context instanceof BufferedCodeBuilder) { + // Hijack the label + lab.setBCI(bci); + } + else { + throw new IllegalStateException(String.format("Unexpected label context %s in =%s", context, this)); + } + } + + public void addCharacterRange(CharacterRange element) { + characterRanges.add(element); + } + + public void addHandler(ExceptionCatch element) { + AbstractPseudoInstruction.ExceptionCatchImpl el = (AbstractPseudoInstruction.ExceptionCatchImpl) element; + ClassEntry type = el.catchTypeEntry(); + if (type != null && !constantPool.canWriteDirect(type.constantPool())) + el = new AbstractPseudoInstruction.ExceptionCatchImpl(element.handler(), element.tryStart(), element.tryEnd(), AbstractPoolEntry.maybeClone(constantPool, type)); + handlers.add(el); + } + + public void addLocalVariable(LocalVariable element) { + localVariables.add(element); + } + + public void addLocalVariableType(LocalVariableType element) { + localVariableTypes.add(element); + } + + @Override + public String toString() { + return String.format("CodeBuilder[id=%d]", System.identityHashCode(this)); + } + + //ToDo: consolidate and open all exceptions + private static final class LabelOverflowException extends IllegalArgumentException { + + private static final long serialVersionUID = 1L; + + public LabelOverflowException() { + super("Label target offset overflow"); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectFieldBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectFieldBuilder.class new file mode 100644 index 00000000..50da2961 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectFieldBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectFieldBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/DirectFieldBuilder.java new file mode 100644 index 00000000..7bdab531 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/DirectFieldBuilder.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.util.function.Consumer; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.FieldBuilder; +import java.lang.classfile.FieldElement; +import java.lang.classfile.FieldModel; +import java.lang.classfile.WritableElement; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class DirectFieldBuilder + extends AbstractDirectBuilder + implements TerminalFieldBuilder, WritableElement { + private final Utf8Entry name; + private final Utf8Entry desc; + private int flags; + + public DirectFieldBuilder(SplitConstantPool constantPool, + ClassFileImpl context, + Utf8Entry name, + Utf8Entry type, + FieldModel original) { + super(constantPool, context); + setOriginal(original); + this.name = name; + this.desc = type; + this.flags = 0; + } + + @Override + public FieldBuilder with(FieldElement element) { + if (element instanceof AbstractElement ae) { + ae.writeTo(this); + } else { + writeAttribute((CustomAttribute)element); + } + return this; + } + + public DirectFieldBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + void setFlags(int flags) { + this.flags = flags; + } + + @Override + public void writeTo(BufWriter buf) { + buf.writeU2(flags); + buf.writeIndex(name); + buf.writeIndex(desc); + attributes.writeTo(buf); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder$1.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder$1.class new file mode 100644 index 00000000..486bd808 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder.class new file mode 100644 index 00000000..195c7992 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder.java new file mode 100644 index 00000000..50daf243 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/DirectMethodBuilder.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.constant.MethodTypeDesc; +import java.util.function.Consumer; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.MethodElement; +import java.lang.classfile.MethodModel; +import java.lang.classfile.WritableElement; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class DirectMethodBuilder + extends AbstractDirectBuilder + implements TerminalMethodBuilder, WritableElement, MethodInfo { + + final Utf8Entry name; + final Utf8Entry desc; + int flags; + int[] parameterSlots; + MethodTypeDesc mDesc; + + public DirectMethodBuilder(SplitConstantPool constantPool, + ClassFileImpl context, + Utf8Entry nameInfo, + Utf8Entry typeInfo, + int flags, + MethodModel original) { + super(constantPool, context); + setOriginal(original); + this.name = nameInfo; + this.desc = typeInfo; + this.flags = flags; + } + + void setFlags(int flags) { + boolean wasStatic = (this.flags & ClassFile.ACC_STATIC) != 0; + boolean isStatic = (flags & ClassFile.ACC_STATIC) != 0; + if (wasStatic != isStatic) + throw new IllegalArgumentException("Cannot change ACC_STATIC flag of method"); + this.flags = flags; + } + + @Override + public Utf8Entry methodName() { + return name; + } + + @Override + public Utf8Entry methodType() { + return desc; + } + + @Override + public MethodTypeDesc methodTypeSymbol() { + if (mDesc == null) { + if (original instanceof MethodInfo mi) { + mDesc = mi.methodTypeSymbol(); + } else { + mDesc = MethodTypeDesc.ofDescriptor(methodType().stringValue()); + } + } + return mDesc; + } + + @Override + public int methodFlags() { + return flags; + } + + @Override + public int parameterSlot(int paramNo) { + if (parameterSlots == null) + parameterSlots = Util.parseParameterSlots(methodFlags(), methodTypeSymbol()); + return parameterSlots[paramNo]; + } + + @Override + public BufferedCodeBuilder bufferedCodeBuilder(CodeModel original) { + return new BufferedCodeBuilder(this, constantPool, context, original); + } + + @Override + public MethodBuilder with(MethodElement element) { + if (element instanceof AbstractElement ae) { + ae.writeTo(this); + } else { + writeAttribute((CustomAttribute)element); + } + return this; + } + + private MethodBuilder withCode(CodeModel original, + Consumer handler) { + var cb = DirectCodeBuilder.build(this, handler, constantPool, context, original); + writeAttribute(cb); + return this; + } + + @Override + public MethodBuilder withCode(Consumer handler) { + return withCode(null, handler); + } + + @Override + public MethodBuilder transformCode(CodeModel code, CodeTransform transform) { + return withCode(code, new Consumer<>() { + @Override + public void accept(CodeBuilder builder) { + builder.transform(code, transform); + } + }); + } + + public DirectMethodBuilder run(Consumer handler) { + handler.accept(this); + return this; + } + + @Override + public void writeTo(BufWriter b) { + BufWriterImpl buf = (BufWriterImpl) b; + buf.writeU2(flags); + buf.writeIndex(name); + buf.writeIndex(desc); + attributes.writeTo(buf); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/EntryMap.class b/tests/test_data/std/jdk/internal/classfile/impl/EntryMap.class new file mode 100644 index 00000000..b7cbbc45 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/EntryMap.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/EntryMap.java b/tests/test_data/std/jdk/internal/classfile/impl/EntryMap.java new file mode 100644 index 00000000..4270dbf7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/EntryMap.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +/** + * An open-chain multimap used to map nonzero hashes to indexes (of either CP + * elements or BSM entries). Code transformed from public domain implementation + * (http://java-performance.info/). + * + * The internal data structure is an array of 2N int elements, where the first + * element is the hash and the second is the mapped index. To look something up + * in the map, provide a hash value and an index to map it to, and invoke + * firstToken(hash). This returns an opaque token that can be provided to + * nextToken(hash, token) to get the next candidate, or to getElementByToken(token) + * or getIndexByToken to get the mapped element or index. + */ +public abstract class EntryMap { + public static final int NO_VALUE = -1; + + /** + * Keys and values + */ + private int[] data; + + /** + * Fill factor, must be between (0 and 1) + */ + private final float fillFactor; + /** + * We will resize a map once it reaches this size + */ + private int resizeThreshold; + /** + * Current map size + */ + private int size; + + /** + * Mask to calculate the original position + */ + private int mask1; + private int mask2; + + public EntryMap(int size, float fillFactor) { + if (fillFactor <= 0 || fillFactor >= 1) + throw new IllegalArgumentException("FillFactor must be in (0, 1)"); + if (size <= 0) + throw new IllegalArgumentException("Size must be positive!"); + + int capacity = arraySize(size, fillFactor); + this.fillFactor = fillFactor; + this.resizeThreshold = (int) (capacity * fillFactor); + this.mask1 = capacity - 1; + this.mask2 = capacity * 2 - 1; + data = new int[capacity * 2]; + } + + protected abstract T fetchElement(int index); + + public int firstToken(int hash) { + if (hash == 0) + throw new IllegalArgumentException("hash must be nonzero"); + + int ix = (hash & mask1) << 1; + int k = data[ix]; + + if (k == 0) + return NO_VALUE; //end of chain already + else if (k == hash) + return ix; + else + return nextToken(hash, ix); + } + + public int nextToken(int hash, int token) { + int ix = token; + while (true) { + ix = (ix + 2) & mask2; // next index + int k = data[ix]; + if (k == 0) + return NO_VALUE; + else if (k == hash) + return ix; + } + } + + public int getIndexByToken(int token) { + return data[token + 1]; + } + + public T getElementByToken(int token) { + return fetchElement(data[token + 1]); + } + + public void put(int hash, int index) { + if (hash == 0) + throw new IllegalArgumentException("hash must be nonzero"); + + int ptr = (hash & mask1) << 1; + int k = data[ptr]; + if (k == 0) { + data[ptr] = hash; + data[ptr + 1] = index; + if (size >= resizeThreshold) + rehash(data.length * 2); //size is set inside + else + ++size; + return; + } + else if (k == hash && data[ptr + 1] == index) { + return; + } + + while (true) { + ptr = (ptr + 2) & mask2; // next index + k = data[ptr]; + if (k == 0) { + data[ptr] = hash; + data[ptr + 1] = index; + if (size >= resizeThreshold) + rehash(data.length * 2); //size is set inside + else + ++size; + return; + } + else if (k == hash && data[ptr + 1] == index) { + return; + } + } + } + + public int size() { + return size; + } + + private void rehash(final int newCapacity) { + resizeThreshold = (int) (newCapacity / 2 * fillFactor); + mask1 = newCapacity / 2 - 1; + mask2 = newCapacity - 1; + + final int oldCapacity = data.length; + final int[] oldData = data; + + data = new int[newCapacity]; + size = 0; + + for (int i = 0; i < oldCapacity; i += 2) { + final int oldHash = oldData[i]; + if (oldHash != 0) + put(oldHash, oldData[i + 1]); + } + } + + public static long nextPowerOfTwo( long x ) { + return 1L << -Long.numberOfLeadingZeros(x - 1); + } + + public static int arraySize( final int expected, final float f ) { + final long s = Math.max( 2, nextPowerOfTwo( (long)Math.ceil( expected / f ) ) ); + if ( s > (1 << 30) ) + throw new IllegalArgumentException("Too large (" + expected + + " expected elements with load factor " + f + ")" ); + return (int)s; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl$1.class new file mode 100644 index 00000000..cf8dd4d3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl.class new file mode 100644 index 00000000..43a4bf8a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl.java new file mode 100644 index 00000000..6645ddb9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/FieldImpl.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; + +import java.lang.classfile.*; +import java.lang.classfile.constantpool.Utf8Entry; + +public final class FieldImpl + extends AbstractElement + implements FieldModel { + + private final ClassReader reader; + private final int startPos, endPos, attributesPos; + private List> attributes; + + public FieldImpl(ClassReader reader, int startPos, int endPos, int attributesPos) { + this.reader = reader; + this.startPos = startPos; + this.endPos = endPos; + this.attributesPos = attributesPos; + } + + @Override + public AccessFlags flags() { + return AccessFlags.ofField(reader.readU2(startPos)); + } + + @Override + public Optional parent() { + if (reader instanceof ClassReaderImpl cri) + return Optional.of(cri.getContainedClass()); + else + return Optional.empty(); + } + + @Override + public Utf8Entry fieldName() { + return reader.readUtf8Entry(startPos + 2); + } + + @Override + public Utf8Entry fieldType() { + return reader.readUtf8Entry(startPos + 4); + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } + + @Override + public void writeTo(BufWriter buf) { + if (buf.canWriteDirect(reader)) { + reader.copyBytesTo(buf, startPos, endPos - startPos); + } + else { + buf.writeU2(flags().flagsMask()); + buf.writeIndex(fieldName()); + buf.writeIndex(fieldType()); + buf.writeList(attributes()); + } + } + + // FieldModel + + @Override + public void writeTo(DirectClassBuilder builder) { + if (builder.canWriteDirect(reader)) { + builder.withField(this); + } + else { + builder.withField(fieldName(), fieldType(), new Consumer<>() { + @Override + public void accept(FieldBuilder fb) { + FieldImpl.this.forEachElement(fb); + } + }); + } + } + + @Override + public void forEachElement(Consumer consumer) { + consumer.accept(flags()); + for (Attribute attr : attributes()) { + if (attr instanceof FieldElement e) + consumer.accept(e); + } + } + + @Override + public String toString() { + return String.format("FieldModel[fieldName=%s, fieldType=%s, flags=%d]", + fieldName().stringValue(), fieldType().stringValue(), flags().flagsMask()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/InterfacesImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/InterfacesImpl.class new file mode 100644 index 00000000..f5336b91 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/InterfacesImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/InterfacesImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/InterfacesImpl.java new file mode 100644 index 00000000..242ec02c --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/InterfacesImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.stream.Collectors; + +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.Interfaces; + +public final class InterfacesImpl + extends AbstractElement + implements Interfaces { + private final List interfaces; + + public InterfacesImpl(List interfaces) { + this.interfaces = List.copyOf(interfaces); + } + + @Override + public List interfaces() { + return interfaces; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setInterfaces(interfaces); + } + + @Override + public String toString() { + return String.format("Interfaces[interfaces=%s]", interfaces.stream() + .map(iface -> iface.name().stringValue()) + .collect(Collectors.joining(", "))); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/LabelContext.class b/tests/test_data/std/jdk/internal/classfile/impl/LabelContext.class new file mode 100644 index 00000000..0676014e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/LabelContext.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/LabelContext.java b/tests/test_data/std/jdk/internal/classfile/impl/LabelContext.java new file mode 100644 index 00000000..749abbed --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/LabelContext.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.Label; + +public sealed interface LabelContext + permits TerminalCodeBuilder, CodeImpl { + Label newLabel(); + Label getLabel(int bci); + void setLabelTarget(Label label, int bci); + int labelToBci(Label label); +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/LabelImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/LabelImpl.class new file mode 100644 index 00000000..37d6a3f5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/LabelImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/LabelImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/LabelImpl.java new file mode 100644 index 00000000..b316c5a6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/LabelImpl.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Objects; + +import java.lang.classfile.Label; +import java.lang.classfile.instruction.LabelTarget; + +/** + * Labels are created with a parent context, which is either a code attribute + * or a code builder. A label originating in a code attribute context may be + * reused in a code builder context, but only labels from a single code + * attribute may be reused by a single code builder. Mappings to and from + * BCI are the responsibility of the context in which it is used; a single + * word of mutable state is provided, for the exclusive use of the owning + * context. + * + * In practice, this means that labels created in a code attribute can simply + * store the BCI in the state on creation, and labels created in in a code + * builder can store the BCI in the state when the label is eventually set; if + * a code attribute label is reused in a builder, the original BCI can be used + * as an index into an array. + */ +public final class LabelImpl + extends AbstractElement + implements Label, LabelTarget { + + private final LabelContext labelContext; + private int bci; + + public LabelImpl(LabelContext labelContext, int bci) { + this.labelContext = Objects.requireNonNull(labelContext); + this.bci = bci; + } + + public LabelContext labelContext() { + return labelContext; + } + + public int getBCI() { + return bci; + } + + public void setBCI(int bci) { + this.bci = bci; + } + + @Override + public Label label() { + return this; + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.setLabelTarget(this); + } + + @Override + public String toString() { + return String.format("Label[context=%s, bci=%d]", labelContext, bci); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/LineNumberImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/LineNumberImpl.class new file mode 100644 index 00000000..71bd5aba Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/LineNumberImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/LineNumberImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/LineNumberImpl.java new file mode 100644 index 00000000..4fc4a342 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/LineNumberImpl.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.instruction.LineNumber; + +public final class LineNumberImpl + extends AbstractElement + implements LineNumber { + private static final int INTERN_LIMIT = 1000; + private static final LineNumber[] internCache = new LineNumber[INTERN_LIMIT]; + static { + for (int i=0; i> attributes; + private int[] parameterSlots; + private MethodTypeDesc mDesc; + + public MethodImpl(ClassReader reader, int startPos, int endPos, int attrStart) { + this.reader = reader; + this.startPos = startPos; + this.endPos = endPos; + this.attributesPos = attrStart; + } + + @Override + public AccessFlags flags() { + return AccessFlags.ofMethod(reader.readU2(startPos)); + } + + @Override + public Optional parent() { + if (reader instanceof ClassReaderImpl cri) + return Optional.of(cri.getContainedClass()); + else + return Optional.empty(); + } + + @Override + public Utf8Entry methodName() { + return reader.readUtf8Entry(startPos + 2); + } + + @Override + public Utf8Entry methodType() { + return reader.readUtf8Entry(startPos + 4); + } + + @Override + public MethodTypeDesc methodTypeSymbol() { + if (mDesc == null) { + mDesc = MethodTypeDesc.ofDescriptor(methodType().stringValue()); + } + return mDesc; + } + + @Override + public int methodFlags() { + return reader.readU2(startPos); + } + + @Override + public int parameterSlot(int paramNo) { + if (parameterSlots == null) + parameterSlots = Util.parseParameterSlots(methodFlags(), methodTypeSymbol()); + return parameterSlots[paramNo]; + } + + @Override + public List> attributes() { + if (attributes == null) { + attributes = BoundAttribute.readAttributes(this, reader, attributesPos, reader.customAttributes()); + } + return attributes; + } + + @Override + public void writeTo(BufWriter b) { + BufWriterImpl buf = (BufWriterImpl) b; + if (buf.canWriteDirect(reader)) { + reader.copyBytesTo(buf, startPos, endPos - startPos); + } + else { + buf.writeU2(flags().flagsMask()); + buf.writeIndex(methodName()); + buf.writeIndex(methodType()); + buf.writeList(attributes()); + } + } + + // MethodModel + + @Override + public Optional code() { + return findAttribute(Attributes.code()).map(a -> (CodeModel) a); + } + + @Override + public void forEachElement(Consumer consumer) { + consumer.accept(flags()); + for (Attribute attr : attributes()) { + if (attr instanceof MethodElement e) + consumer.accept(e); + } + } + + @Override + public void writeTo(DirectClassBuilder builder) { + if (builder.canWriteDirect(reader)) { + builder.withMethod(this); + } + else { + builder.withMethod(methodName(), methodType(), methodFlags(), + new Consumer<>() { + @Override + public void accept(MethodBuilder mb) { + MethodImpl.this.forEachElement(mb); + } + }); + } + } + + @Override + public String toString() { + return String.format("MethodModel[methodName=%s, methodType=%s, flags=%d]", + methodName().stringValue(), methodType().stringValue(), flags().flagsMask()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/MethodInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/MethodInfo.class new file mode 100644 index 00000000..cf4b528d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/MethodInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/MethodInfo.java b/tests/test_data/std/jdk/internal/classfile/impl/MethodInfo.java new file mode 100644 index 00000000..0dd81776 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/MethodInfo.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.MethodTypeDesc; +import java.lang.classfile.constantpool.Utf8Entry; + +import static java.lang.classfile.ClassFile.ACC_STATIC; + +public interface MethodInfo { + Utf8Entry methodName(); + Utf8Entry methodType(); + MethodTypeDesc methodTypeSymbol(); + int methodFlags(); + + default int receiverSlot() { + if ((methodFlags() & ACC_STATIC) != 0) + throw new IllegalStateException("not an instance method"); + return 0; + } + + int parameterSlot(int paramNo); +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.class new file mode 100644 index 00000000..528f5f93 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.java new file mode 100644 index 00000000..1aad47cf --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/ModuleAttributeBuilderImpl.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.classfile.attribute.*; +import java.lang.classfile.attribute.ModuleAttribute.ModuleAttributeBuilder; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.Utf8Entry; +import java.lang.constant.ModuleDesc; +import java.lang.constant.PackageDesc; + +import java.lang.constant.ClassDesc; +import java.util.*; + +public final class ModuleAttributeBuilderImpl + implements ModuleAttributeBuilder { + + private ModuleEntry moduleEntry; + private Utf8Entry moduleVersion; + private int moduleFlags; + + private final Set requires = new LinkedHashSet<>(); + private final Set exports = new LinkedHashSet<>(); + private final Set opens = new LinkedHashSet<>(); + private final Set uses = new LinkedHashSet<>(); + private final Set provides = new LinkedHashSet<>(); + + public ModuleAttributeBuilderImpl(ModuleEntry moduleName) { + this.moduleEntry = moduleName; + this.moduleFlags = 0; + } + + public ModuleAttributeBuilderImpl(ModuleDesc moduleName) { + this(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(moduleName.name()))); + } + + @Override + public ModuleAttribute build() { + return new UnboundAttribute.UnboundModuleAttribute(moduleEntry, moduleFlags, moduleVersion, + requires, exports, opens, uses, provides); + } + + @Override + public ModuleAttributeBuilder moduleName(ModuleDesc moduleName) { + Objects.requireNonNull(moduleName); + moduleEntry = TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(moduleName.name())); + return this; + } + + @Override + public ModuleAttributeBuilder moduleFlags(int flags) { + this.moduleFlags = flags; + return this; + } + + @Override + public ModuleAttributeBuilder moduleVersion(String version) { + moduleVersion = version == null ? null : TemporaryConstantPool.INSTANCE.utf8Entry(version); + return this; + } + + @Override + public ModuleAttributeBuilder requires(ModuleDesc module, int flags, String version) { + Objects.requireNonNull(module); + return requires(ModuleRequireInfo.of(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(module.name())), flags, version == null ? null : TemporaryConstantPool.INSTANCE.utf8Entry(version))); + } + + @Override + public ModuleAttributeBuilder requires(ModuleRequireInfo requires) { + Objects.requireNonNull(requires); + this.requires.add(requires); + return this; + } + + @Override + public ModuleAttributeBuilder exports(PackageDesc pkge, int flags, ModuleDesc... exportsToModules) { + Objects.requireNonNull(pkge); + var exportsTo = new ArrayList(exportsToModules.length); + for (var e : exportsToModules) + exportsTo.add(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(e.name()))); + return exports(ModuleExportInfo.of(TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(pkge.internalName())), flags, exportsTo)); + } + + @Override + public ModuleAttributeBuilder exports(ModuleExportInfo exports) { + Objects.requireNonNull(exports); + this.exports.add(exports); + return this; + } + + @Override + public ModuleAttributeBuilder opens(PackageDesc pkge, int flags, ModuleDesc... opensToModules) { + Objects.requireNonNull(pkge); + var opensTo = new ArrayList(opensToModules.length); + for (var e : opensToModules) + opensTo.add(TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(e.name()))); + return opens(ModuleOpenInfo.of(TemporaryConstantPool.INSTANCE.packageEntry(TemporaryConstantPool.INSTANCE.utf8Entry(pkge.internalName())), flags, opensTo)); + } + + @Override + public ModuleAttributeBuilder opens(ModuleOpenInfo opens) { + Objects.requireNonNull(opens); + this.opens.add(opens); + return this; + } + + @Override + public ModuleAttributeBuilder uses(ClassDesc service) { + Objects.requireNonNull(service); + return uses(TemporaryConstantPool.INSTANCE.classEntry(service)); + } + + @Override + public ModuleAttributeBuilder uses(ClassEntry uses) { + Objects.requireNonNull(uses); + this.uses.add(uses); + return this; + } + + @Override + public ModuleAttributeBuilder provides(ClassDesc service, ClassDesc... implClasses) { + Objects.requireNonNull(service); + var impls = new ArrayList(implClasses.length); + for (var seq : implClasses) + impls.add(TemporaryConstantPool.INSTANCE.classEntry(seq)); + return provides(ModuleProvideInfo.of(TemporaryConstantPool.INSTANCE.classEntry(service), impls)); + } + + @Override + public ModuleAttributeBuilder provides(ModuleProvideInfo provides) { + Objects.requireNonNull(provides); + this.provides.add(provides); + return this; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/NonterminalCodeBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/NonterminalCodeBuilder.class new file mode 100644 index 00000000..3f6341ca Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/NonterminalCodeBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/NonterminalCodeBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/NonterminalCodeBuilder.java new file mode 100644 index 00000000..9c27f0f5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/NonterminalCodeBuilder.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Optional; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeModel; +import java.lang.classfile.Label; +import java.lang.classfile.constantpool.ConstantPoolBuilder; + +public abstract sealed class NonterminalCodeBuilder implements CodeBuilder + permits ChainedCodeBuilder, BlockCodeBuilderImpl { + protected final TerminalCodeBuilder terminal; + protected final CodeBuilder parent; + + public NonterminalCodeBuilder(CodeBuilder parent) { + this.parent = parent; + this.terminal = switch (parent) { + case NonterminalCodeBuilder cb -> cb.terminal; + case TerminalCodeBuilder cb -> cb; + }; + } + + @Override + public int receiverSlot() { + return terminal.receiverSlot(); + } + + @Override + public int parameterSlot(int paramNo) { + return terminal.parameterSlot(paramNo); + } + + @Override + public ConstantPoolBuilder constantPool() { + return terminal.constantPool(); + } + + @Override + public Optional original() { + return terminal.original(); + } + + @Override + public Label newLabel() { + return terminal.newLabel(); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/RawBytecodeHelper.class b/tests/test_data/std/jdk/internal/classfile/impl/RawBytecodeHelper.class new file mode 100644 index 00000000..5c1cbe2c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/RawBytecodeHelper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/RawBytecodeHelper.java b/tests/test_data/std/jdk/internal/classfile/impl/RawBytecodeHelper.java new file mode 100644 index 00000000..3e5d6624 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/RawBytecodeHelper.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.nio.ByteBuffer; +import static java.lang.classfile.ClassFile.ASTORE_3; +import static java.lang.classfile.ClassFile.ISTORE; +import static java.lang.classfile.ClassFile.LOOKUPSWITCH; +import static java.lang.classfile.ClassFile.TABLESWITCH; +import static java.lang.classfile.ClassFile.WIDE; + +public final class RawBytecodeHelper { + + public static final int ILLEGAL = -1; + + private static final byte[] LENGTHS = new byte[] { + 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, + 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, + 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, + 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, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + public static boolean isStoreIntoLocal(int code) { + return (ISTORE <= code && code <= ASTORE_3); + } + + public static int align(int n) { + return (n + 3) & ~3; + } + + private final ByteBuffer bytecode; + public int bci, nextBci, endBci; + public int rawCode; + public boolean isWide; + + public RawBytecodeHelper(ByteBuffer bytecode) { + this.bytecode = bytecode; + this.bci = 0; + this.nextBci = 0; + this.endBci = bytecode.capacity(); + } + + public boolean isLastBytecode() { + return nextBci >= endBci; + } + + public int getShort(int bci) { + return bytecode.getShort(bci); + } + + public int dest() { + return bci + getShort(bci + 1); + } + + public int getInt(int bci) { + return bytecode.getInt(bci); + } + + public int destW() { + return bci + getInt(bci + 1); + } + + public int getIndexU1() { + return bytecode.get(bci + 1) & 0xff; + } + + public int getU1(int bci) { + return bytecode.get(bci) & 0xff; + } + + public int rawNext(int jumpTo) { + this.nextBci = jumpTo; + return rawNext(); + } + + public int rawNext() { + bci = nextBci; + int code = bytecode.get(bci) & 0xff; + int len = LENGTHS[code] & 0xf; + if (len > 0 && (bci <= endBci - len)) { + isWide = false; + nextBci += len; + if (nextBci <= bci) { + code = ILLEGAL; + } + rawCode = code; + return code; + } else { + len = switch (bytecode.get(bci) & 0xff) { + case WIDE -> { + if (bci + 1 >= endBci) { + yield -1; + } + yield LENGTHS[bytecode.get(bci + 1) & 0xff] >> 4; + } + case TABLESWITCH -> { + int aligned_bci = align(bci + 1); + if (aligned_bci + 3 * 4 >= endBci) { + yield -1; + } + int lo = bytecode.getInt(aligned_bci + 1 * 4); + int hi = bytecode.getInt(aligned_bci + 2 * 4); + int l = aligned_bci - bci + (3 + hi - lo + 1) * 4; + if (l > 0) yield l; else yield -1; + } + case LOOKUPSWITCH -> { + int aligned_bci = align(bci + 1); + if (aligned_bci + 2 * 4 >= endBci) { + yield -1; + } + int npairs = bytecode.getInt(aligned_bci + 4); + int l = aligned_bci - bci + (2 + 2 * npairs) * 4; + if (l > 0) yield l; else yield -1; + } + default -> + 0; + }; + if (len <= 0 || (bci > endBci - len) || (bci - len >= nextBci)) { + code = ILLEGAL; + } else { + nextBci += len; + isWide = false; + if (code == WIDE) { + if (bci + 1 >= endBci) { + code = ILLEGAL; + } else { + code = bytecode.get(bci + 1) & 0xff; + isWide = true; + } + } + } + rawCode = code; + return code; + } + } + + public int getIndex() { + return (isWide) ? getIndexU2Raw(bci + 2) : getIndexU1(); + } + + public int getIndexU2() { + return getIndexU2Raw(bci + 1); + } + + public int getIndexU2Raw(int bci) { + return bytecode.getShort(bci) & 0xffff; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$1.class new file mode 100644 index 00000000..0e0ce8c5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ArrayTypeSigImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ArrayTypeSigImpl.class new file mode 100644 index 00000000..2982b1fd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ArrayTypeSigImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$BaseTypeSigImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$BaseTypeSigImpl.class new file mode 100644 index 00000000..bad09180 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$BaseTypeSigImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ClassSignatureImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ClassSignatureImpl.class new file mode 100644 index 00000000..eec9d901 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ClassSignatureImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ClassTypeSigImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ClassTypeSigImpl.class new file mode 100644 index 00000000..60c856b0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$ClassTypeSigImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$MethodSignatureImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$MethodSignatureImpl.class new file mode 100644 index 00000000..90cb96bb Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$MethodSignatureImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeArgImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeArgImpl.class new file mode 100644 index 00000000..08ec79a7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeArgImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeParamImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeParamImpl.class new file mode 100644 index 00000000..a3dc1178 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeParamImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeVarSigImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeVarSigImpl.class new file mode 100644 index 00000000..2fb1367c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$TypeVarSigImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$UnboundedTypeArgImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$UnboundedTypeArgImpl.class new file mode 100644 index 00000000..aac716d4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl$UnboundedTypeArgImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl.class new file mode 100644 index 00000000..329e3aaf Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl.java new file mode 100644 index 00000000..77f6933a --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/SignaturesImpl.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Collections; +import java.lang.classfile.ClassSignature; +import java.lang.classfile.MethodSignature; +import java.lang.classfile.Signature; +import java.lang.classfile.Signature.*; + +public final class SignaturesImpl { + + public SignaturesImpl(String signature) { + this.sig = Objects.requireNonNull(signature); + this.sigp = 0; + } + + private final String sig; + private int sigp; + + public ClassSignature parseClassSignature() { + try { + List typeParamTypes = parseParamTypes(); + ClassTypeSig superclass = classTypeSig(); + ArrayList superinterfaces = null; + while (sigp < sig.length()) { + if (superinterfaces == null) + superinterfaces = new ArrayList<>(); + superinterfaces.add(classTypeSig()); + } + return new ClassSignatureImpl(typeParamTypes, superclass, null2Empty(superinterfaces)); + } catch (IndexOutOfBoundsException e) { + throw error("Not a valid class signature"); + } + } + + public MethodSignature parseMethodSignature() { + try { + List typeParamTypes = parseParamTypes(); + require('('); + ArrayList paramTypes = null; + while (!match(')')) { + if (paramTypes == null) + paramTypes = new ArrayList<>(); + paramTypes.add(typeSig()); + } + Signature returnType = typeSig(); + ArrayList throwsTypes = null; + while (sigp < sig.length()) { + require('^'); + if (throwsTypes == null) + throwsTypes = new ArrayList<>(); + var t = referenceTypeSig(); + if (t instanceof ThrowableSig ts) + throwsTypes.add(ts); + else + throw error("Not a valid throwable signature %s in".formatted(t.signatureString())); + } + return new MethodSignatureImpl(typeParamTypes, null2Empty(throwsTypes), returnType, null2Empty(paramTypes)); + } catch (IndexOutOfBoundsException e) { + throw error("Not a valid method signature"); + } + } + + public Signature parseSignature() { + try { + var s = typeSig(); + if (sigp == sig.length()) + return s; + } catch (IndexOutOfBoundsException e) { + } + throw error("Not a valid type signature"); + } + + private List parseParamTypes() { + ArrayList typeParamTypes = null; + if (match('<')) { + typeParamTypes = new ArrayList<>(); + // cannot have empty <> + do { + String name = sig.substring(sigp, requireIdentifier()); + RefTypeSig classBound = null; + ArrayList interfaceBounds = null; + require(':'); + if (sig.charAt(sigp) != ':') + classBound = referenceTypeSig(); + while (match(':')) { + if (interfaceBounds == null) + interfaceBounds = new ArrayList<>(); + interfaceBounds.add(referenceTypeSig()); + } + typeParamTypes.add(new TypeParamImpl(name, Optional.ofNullable(classBound), null2Empty(interfaceBounds))); + } while (!match('>')); + } + return null2Empty(typeParamTypes); + } + + private Signature typeSig() { + char c = sig.charAt(sigp++); + switch (c) { + case 'B','C','D','F','I','J','V','S','Z': return Signature.BaseTypeSig.of(c); + default: + sigp--; + return referenceTypeSig(); + } + } + + private RefTypeSig referenceTypeSig() { + return switch (sig.charAt(sigp)) { + case 'L' -> classTypeSig(); + case 'T' -> { + sigp++; + var ty = Signature.TypeVarSig.of(sig.substring(sigp, requireIdentifier())); + require(';'); + yield ty; + } + case '[' -> { + sigp++; + yield ArrayTypeSig.of(typeSig()); + } + default -> throw unexpectedError("a type signature"); + }; + } + + private TypeArg typeArg() { + char c = sig.charAt(sigp++); + switch (c) { + case '*': return TypeArg.unbounded(); + case '+': return TypeArg.extendsOf(referenceTypeSig()); + case '-': return TypeArg.superOf(referenceTypeSig()); + default: + sigp--; + return TypeArg.of(referenceTypeSig()); + } + } + + private ClassTypeSig classTypeSig() { + require('L'); + Signature.ClassTypeSig t = null; + + do { + int start = sigp; + requireIdentifier(); + if (t == null) { + while (match('/')) { + requireIdentifier(); + } + } + String className = sig.substring(start, sigp); + + ArrayList argTypes; + if (match('<')) { + // cannot have empty <> + argTypes = new ArrayList<>(); + do { + argTypes.add(typeArg()); + } while (!match('>')); + } else { + argTypes = null; + } + + boolean end = match(';'); + if (end || match('.')) { + t = new ClassTypeSigImpl(Optional.ofNullable(t), className, null2Empty(argTypes)); + if (end) + return t; + } else { + throw unexpectedError(". or ;"); + } + } while (true); + } + + /** + * Tries to match a character, and moves pointer if it matches. + */ + private boolean match(char c) { + if (sigp < sig.length() && sig.charAt(sigp) == c) { + sigp++; + return true; + } + return false; + } + + /** + * Requires a character and moves past it, failing otherwise. + */ + private void require(char c) { + if (!match(c)) + throw unexpectedError(String.valueOf(c)); + } + + /** + * Requires an identifier, moving pointer to next illegal character and returning + * its position. Fails if the identifier is empty. + */ + private int requireIdentifier() { + int start = sigp; + l: + while (sigp < sig.length()) { + switch (sig.charAt(sigp)) { + case '.', ';', '[', '/', '<', '>', ':' -> { + break l; + } + } + sigp++; + } + if (start == sigp) { + throw unexpectedError("an identifier"); + } + return sigp; + } + + public static record BaseTypeSigImpl(char baseType) implements Signature.BaseTypeSig { + + @Override + public String signatureString() { + return "" + baseType; + } + } + + public static record TypeVarSigImpl(String identifier) implements Signature.TypeVarSig { + + @Override + public String signatureString() { + return "T" + identifier + ';'; + } + } + + public static record ArrayTypeSigImpl(int arrayDepth, Signature elemType) implements Signature.ArrayTypeSig { + + @Override + public Signature componentSignature() { + return arrayDepth > 1 ? new ArrayTypeSigImpl(arrayDepth - 1, elemType) : elemType; + } + + @Override + public String signatureString() { + return "[".repeat(arrayDepth) + elemType.signatureString(); + } + } + + public static record ClassTypeSigImpl(Optional outerType, String className, List typeArgs) + implements Signature.ClassTypeSig { + + @Override + public String signatureString() { + String prefix = "L"; + if (outerType.isPresent()) { + prefix = outerType.get().signatureString(); + assert prefix.charAt(prefix.length() - 1) == ';'; + prefix = prefix.substring(0, prefix.length() - 1) + '.'; + } + String suffix = ";"; + if (!typeArgs.isEmpty()) { + var sb = new StringBuilder(); + sb.append('<'); + for (var ta : typeArgs) { + switch (ta) { + case TypeArg.Bounded b -> { + switch (b.wildcardIndicator()) { + case SUPER -> sb.append('-'); + case EXTENDS -> sb.append('+'); + } + sb.append(b.boundType().signatureString()); + } + case TypeArg.Unbounded _ -> sb.append('*'); + } + } + suffix = sb.append(">;").toString(); + } + return prefix + className + suffix; + } + } + + public static enum UnboundedTypeArgImpl implements TypeArg.Unbounded { + INSTANCE; + } + + public static record TypeArgImpl(WildcardIndicator wildcardIndicator, RefTypeSig boundType) implements Signature.TypeArg.Bounded { + } + + public static record TypeParamImpl(String identifier, Optional classBound, List interfaceBounds) + implements TypeParam { + } + + private static StringBuilder printTypeParameters(List typeParameters) { + var sb = new StringBuilder(); + if (typeParameters != null && !typeParameters.isEmpty()) { + sb.append('<'); + for (var tp : typeParameters) { + sb.append(tp.identifier()).append(':'); + if (tp.classBound().isPresent()) + sb.append(tp.classBound().get().signatureString()); + if (tp.interfaceBounds() != null) for (var is : tp.interfaceBounds()) + sb.append(':').append(is.signatureString()); + } + sb.append('>'); + } + return sb; + } + + public static record ClassSignatureImpl(List typeParameters, ClassTypeSig superclassSignature, + List superinterfaceSignatures) implements ClassSignature { + + @Override + public String signatureString() { + var sb = printTypeParameters(typeParameters); + sb.append(superclassSignature.signatureString()); + if (superinterfaceSignatures != null) for (var in : superinterfaceSignatures) + sb.append(in.signatureString()); + return sb.toString(); + } + } + + public static record MethodSignatureImpl( + List typeParameters, + List throwableSignatures, + Signature result, + List arguments) implements MethodSignature { + + @Override + public String signatureString() { + var sb = printTypeParameters(typeParameters); + sb.append('('); + for (var a : arguments) + sb.append(a.signatureString()); + sb.append(')').append(result.signatureString()); + if (!throwableSignatures.isEmpty()) + for (var t : throwableSignatures) + sb.append('^').append(t.signatureString()); + return sb.toString(); + } + } + + private static List null2Empty(ArrayList l) { + return l == null ? List.of() : Collections.unmodifiableList(l); + } + + private IllegalArgumentException unexpectedError(String expected) { + return error(sigp < sig.length() ? "Unexpected character %c at position %d, expected %s" + .formatted(sig.charAt(sigp), sigp, expected) + : "Unexpected end of signature at position %d, expected %s".formatted(sigp, expected)); + } + + private IllegalArgumentException error(String message) { + return new IllegalArgumentException("%s: %s".formatted(message, sig)); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$1.class b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$1.class new file mode 100644 index 00000000..e0dba980 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$2.class b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$2.class new file mode 100644 index 00000000..0074ee13 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$2.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$3.class b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$3.class new file mode 100644 index 00000000..a150688a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool$3.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool.class b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool.class new file mode 100644 index 00000000..3f23da77 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool.java b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool.java new file mode 100644 index 00000000..b034328f --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/SplitConstantPool.java @@ -0,0 +1,606 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.Arrays; +import java.util.List; + +import java.lang.classfile.Attribute; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassReader; +import java.lang.classfile.ClassFile; +import java.lang.classfile.BootstrapMethodEntry; +import java.lang.classfile.BufWriter; +import java.lang.classfile.attribute.BootstrapMethodsAttribute; +import java.lang.classfile.constantpool.*; +import java.util.Objects; + +import static java.lang.classfile.ClassFile.TAG_CLASS; +import static java.lang.classfile.ClassFile.TAG_CONSTANTDYNAMIC; +import static java.lang.classfile.ClassFile.TAG_DOUBLE; +import static java.lang.classfile.ClassFile.TAG_FIELDREF; +import static java.lang.classfile.ClassFile.TAG_FLOAT; +import static java.lang.classfile.ClassFile.TAG_INTEGER; +import static java.lang.classfile.ClassFile.TAG_INTERFACEMETHODREF; +import static java.lang.classfile.ClassFile.TAG_INVOKEDYNAMIC; +import static java.lang.classfile.ClassFile.TAG_LONG; +import static java.lang.classfile.ClassFile.TAG_METHODHANDLE; +import static java.lang.classfile.ClassFile.TAG_METHODREF; +import static java.lang.classfile.ClassFile.TAG_METHODTYPE; +import static java.lang.classfile.ClassFile.TAG_MODULE; +import static java.lang.classfile.ClassFile.TAG_NAMEANDTYPE; +import static java.lang.classfile.ClassFile.TAG_PACKAGE; +import static java.lang.classfile.ClassFile.TAG_STRING; + +public final class SplitConstantPool implements ConstantPoolBuilder { + + private final ClassReaderImpl parent; + private final int parentSize, parentBsmSize; + + private int size, bsmSize; + private PoolEntry[] myEntries; + private BootstrapMethodEntryImpl[] myBsmEntries; + private boolean doneFullScan; + private EntryMap map; + private EntryMap bsmMap; + + public SplitConstantPool() { + this.size = 1; + this.bsmSize = 0; + this.myEntries = new PoolEntry[1024]; + this.myBsmEntries = new BootstrapMethodEntryImpl[8]; + this.parent = null; + this.parentSize = 0; + this.parentBsmSize = 0; + this.doneFullScan = true; + } + + public SplitConstantPool(ClassReader parent) { + this.parent = (ClassReaderImpl) parent; + this.parentSize = parent.size(); + this.parentBsmSize = parent.bootstrapMethodCount(); + this.size = parentSize; + this.bsmSize = parentBsmSize; + this.myEntries = new PoolEntry[8]; + this.myBsmEntries = new BootstrapMethodEntryImpl[8]; + this.doneFullScan = true; + } + + @Override + public int size() { + return size; + } + + @Override + public int bootstrapMethodCount() { + return bsmSize; + } + + @Override + public PoolEntry entryByIndex(int index) { + if (index <= 0 || index >= size()) { + throw new ConstantPoolException("Bad CP index: " + index); + } + PoolEntry pe = (index < parentSize) + ? parent.entryByIndex(index) + : myEntries[index - parentSize]; + if (pe == null) { + throw new ConstantPoolException("Unusable CP index: " + index); + } + return pe; + } + + @Override + public T entryByIndex(int index, Class cls) { + Objects.requireNonNull(cls); + return ClassReaderImpl.checkType(entryByIndex(index), index, cls); + } + + @Override + public BootstrapMethodEntryImpl bootstrapMethodEntry(int index) { + if (index < 0 || index >= bootstrapMethodCount()) { + throw new ConstantPoolException("Bad BSM index: " + index); + } + return (index < parentBsmSize) + ? parent.bootstrapMethodEntry(index) + : myBsmEntries[index - parentBsmSize]; + } + + @Override + public boolean canWriteDirect(ConstantPool other) { + return this == other || parent == other; + } + + @Override + public boolean writeBootstrapMethods(BufWriter buf) { + if (bsmSize == 0) + return false; + int pos = buf.size(); + if (parent != null && parentBsmSize != 0) { + parent.writeBootstrapMethods(buf); + for (int i = parentBsmSize; i < bsmSize; i++) + bootstrapMethodEntry(i).writeTo(buf); + int attrLen = buf.size() - pos; + buf.patchInt(pos + 2, 4, attrLen - 6); + buf.patchInt(pos + 6, 2, bsmSize); + } + else { + Attribute a + = new UnboundAttribute.AdHocAttribute<>(Attributes.bootstrapMethods()) { + + @Override + public void writeBody(BufWriter b) { + buf.writeU2(bsmSize); + for (int i = 0; i < bsmSize; i++) + bootstrapMethodEntry(i).writeTo(buf); + } + }; + a.writeTo(buf); + } + return true; + } + + @Override + public void writeTo(BufWriter buf) { + int writeFrom = 1; + if (size() >= 65536) { + throw new IllegalArgumentException(String.format("Constant pool is too large %d", size())); + } + buf.writeU2(size()); + if (parent != null && buf.constantPool().canWriteDirect(this)) { + parent.writeConstantPoolEntries(buf); + writeFrom = parent.size(); + } + for (int i = writeFrom; i < size(); ) { + PoolEntry info = entryByIndex(i); + info.writeTo(buf); + i += info.width(); + } + } + + private EntryMap map() { + if (map == null) { + map = new EntryMap<>(Math.max(size, 1024), .75f) { + @Override + protected PoolEntry fetchElement(int index) { + return entryByIndex(index); + } + }; + // Doing a full scan here yields fall-off-the-cliff performance results, + // especially if we only need a few entries that are already + // inflated (such as attribute names). + // So we inflate the map with whatever we've got from the parent, and + // later, if we miss, we do a one-time full inflation before creating + // a new entry. + for (int i=1; i bsmMap() { + if (bsmMap == null) { + bsmMap = new EntryMap<>(Math.max(bsmSize, 16), .75f) { + @Override + protected BootstrapMethodEntryImpl fetchElement(int index) { + return bootstrapMethodEntry(index); + } + }; + for (int i=0; i E internalAdd(E cpi) { + return internalAdd(cpi, cpi.hashCode()); + } + + private E internalAdd(E cpi, int hash) { + int newIndex = size-parentSize; + if (newIndex + 2 > myEntries.length) { + myEntries = Arrays.copyOf(myEntries, 2 * newIndex, PoolEntry[].class); + } + myEntries[newIndex] = cpi; + size += cpi.width(); + map().put(hash, cpi.index()); + return cpi; + } + + private BootstrapMethodEntryImpl internalAdd(BootstrapMethodEntryImpl bsm, int hash) { + int newIndex = bsmSize-parentBsmSize; + if (newIndex + 2 > myBsmEntries.length) { + myBsmEntries = Arrays.copyOf(myBsmEntries, 2 * newIndex, BootstrapMethodEntryImpl[].class); + } + myBsmEntries[newIndex] = bsm; + bsmSize += 1; + bsmMap().put(hash, bsm.index); + return bsm; + } + + private PoolEntry findPrimitiveEntry(int tag, T val) { + int hash = AbstractPoolEntry.hash1(tag, val.hashCode()); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == tag + && e instanceof AbstractPoolEntry.PrimitiveEntry ce + && ce.value().equals(val)) + return e; + } + if (!doneFullScan) { + fullScan(); + return findPrimitiveEntry(tag, val); + } + return null; + } + + private AbstractPoolEntry findEntry(int tag, T ref1) { + // invariant: canWriteDirect(ref1.constantPool()) + int hash = AbstractPoolEntry.hash1(tag, ref1.index()); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == tag + && e instanceof AbstractPoolEntry.AbstractRefEntry re + && re.ref1 == ref1) + return re; + } + if (!doneFullScan) { + fullScan(); + return findEntry(tag, ref1); + } + return null; + } + + private + AbstractPoolEntry findEntry(int tag, T ref1, U ref2) { + // invariant: canWriteDirect(ref1.constantPool()), canWriteDirect(ref2.constantPool()) + int hash = AbstractPoolEntry.hash2(tag, ref1.index(), ref2.index()); + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == tag + && e instanceof AbstractPoolEntry.AbstractRefsEntry re + && re.ref1 == ref1 + && re.ref2 == ref2) { + return re; + } + } + if (!doneFullScan) { + fullScan(); + return findEntry(tag, ref1, ref2); + } + return null; + } + + private AbstractPoolEntry.Utf8EntryImpl tryFindUtf8(int hash, String target) { + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; + token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == ClassFile.TAG_UTF8 + && e instanceof AbstractPoolEntry.Utf8EntryImpl ce + && ce.hashCode() == hash + && target.equals(ce.stringValue())) + return ce; + } + if (!doneFullScan) { + fullScan(); + return tryFindUtf8(hash, target); + } + return null; + } + + private AbstractPoolEntry.Utf8EntryImpl tryFindUtf8(int hash, AbstractPoolEntry.Utf8EntryImpl target) { + EntryMap map = map(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + PoolEntry e = map.getElementByToken(token); + if (e.tag() == ClassFile.TAG_UTF8 + && e instanceof AbstractPoolEntry.Utf8EntryImpl ce + && target.equalsUtf8(ce)) + return ce; + } + if (!doneFullScan) { + fullScan(); + return tryFindUtf8(hash, target); + } + return null; + } + + @Override + public AbstractPoolEntry.Utf8EntryImpl utf8Entry(String s) { + int hash = AbstractPoolEntry.hashString(s.hashCode()); + var ce = tryFindUtf8(hash, s); + return ce == null ? internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, s, hash)) : ce; + } + + AbstractPoolEntry.Utf8EntryImpl maybeCloneUtf8Entry(Utf8Entry entry) { + AbstractPoolEntry.Utf8EntryImpl e = (AbstractPoolEntry.Utf8EntryImpl) entry; + if (e.constantPool == this || e.constantPool == parent) + return e; + AbstractPoolEntry.Utf8EntryImpl ce = tryFindUtf8(e.hashCode(), e); + return ce == null ? internalAdd(new AbstractPoolEntry.Utf8EntryImpl(this, size, e)) : ce; + } + + @Override + public AbstractPoolEntry.ClassEntryImpl classEntry(Utf8Entry nameEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + var e = (AbstractPoolEntry.ClassEntryImpl) findEntry(TAG_CLASS, ne); + return e == null ? internalAdd(new AbstractPoolEntry.ClassEntryImpl(this, size, ne)) : e; + } + + @Override + public PackageEntry packageEntry(Utf8Entry nameEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + var e = (AbstractPoolEntry.PackageEntryImpl) findEntry(TAG_PACKAGE, ne); + return e == null ? internalAdd(new AbstractPoolEntry.PackageEntryImpl(this, size, ne)) : e; + } + + @Override + public ModuleEntry moduleEntry(Utf8Entry nameEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + var e = (AbstractPoolEntry.ModuleEntryImpl) findEntry(TAG_MODULE, ne); + return e == null ? internalAdd(new AbstractPoolEntry.ModuleEntryImpl(this, size, ne)) : e; + } + + @Override + public AbstractPoolEntry.NameAndTypeEntryImpl nameAndTypeEntry(Utf8Entry nameEntry, Utf8Entry typeEntry) { + AbstractPoolEntry.Utf8EntryImpl ne = maybeCloneUtf8Entry(nameEntry); + AbstractPoolEntry.Utf8EntryImpl te = maybeCloneUtf8Entry(typeEntry); + var e = (AbstractPoolEntry.NameAndTypeEntryImpl) findEntry(TAG_NAMEANDTYPE, ne, te); + return e == null ? internalAdd(new AbstractPoolEntry.NameAndTypeEntryImpl(this, size, ne, te)) : e; + } + + @Override + public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner; + AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + var e = (AbstractPoolEntry.FieldRefEntryImpl) findEntry(TAG_FIELDREF, oe, ne); + return e == null ? internalAdd(new AbstractPoolEntry.FieldRefEntryImpl(this, size, oe, ne)) : e; + } + + @Override + public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner; + AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + var e = (AbstractPoolEntry.MethodRefEntryImpl) findEntry(TAG_METHODREF, oe, ne); + return e == null ? internalAdd(new AbstractPoolEntry.MethodRefEntryImpl(this, size, oe, ne)) : e; + } + + @Override + public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + AbstractPoolEntry.ClassEntryImpl oe = (AbstractPoolEntry.ClassEntryImpl) owner; + AbstractPoolEntry.NameAndTypeEntryImpl ne = (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType; + if (!canWriteDirect(oe.constantPool)) + oe = classEntry(owner.name()); + if (!canWriteDirect(ne.constantPool)) + ne = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + var e = (AbstractPoolEntry.InterfaceMethodRefEntryImpl) findEntry(TAG_INTERFACEMETHODREF, oe, ne); + return e == null ? internalAdd(new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, size, oe, ne)) : e; + } + + @Override + public MethodTypeEntry methodTypeEntry(MethodTypeDesc descriptor) { + var ret = (AbstractPoolEntry.MethodTypeEntryImpl)methodTypeEntry(utf8Entry(descriptor.descriptorString())); + ret.sym = descriptor; + return ret; + } + + @Override + public MethodTypeEntry methodTypeEntry(Utf8Entry descriptor) { + AbstractPoolEntry.Utf8EntryImpl de = maybeCloneUtf8Entry(descriptor); + var e = (AbstractPoolEntry.MethodTypeEntryImpl) findEntry(TAG_METHODTYPE, de); + return e == null ? internalAdd(new AbstractPoolEntry.MethodTypeEntryImpl(this, size, de)) : e; + } + + @Override + public MethodHandleEntry methodHandleEntry(int refKind, MemberRefEntry reference) { + if (!canWriteDirect(reference.constantPool())) { + reference = switch (reference.tag()) { + case TAG_FIELDREF -> fieldRefEntry(reference.owner(), reference.nameAndType()); + case TAG_METHODREF -> methodRefEntry(reference.owner(), reference.nameAndType()); + case TAG_INTERFACEMETHODREF -> interfaceMethodRefEntry(reference.owner(), reference.nameAndType()); + default -> throw new IllegalArgumentException(String.format("Bad tag %d", reference.tag())); + }; + } + + int hash = AbstractPoolEntry.hash2(TAG_METHODHANDLE, refKind, reference.index()); + EntryMap map1 = map(); + for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) { + PoolEntry e = map1.getElementByToken(token); + if (e.tag() == TAG_METHODHANDLE + && e instanceof AbstractPoolEntry.MethodHandleEntryImpl ce + && ce.kind() == refKind && ce.reference() == reference) + return ce; + } + if (!doneFullScan) { + fullScan(); + return methodHandleEntry(refKind, reference); + } + return internalAdd(new AbstractPoolEntry.MethodHandleEntryImpl(this, size, + hash, refKind, (AbstractPoolEntry.AbstractMemberRefEntry) reference), hash); + } + + @Override + public InvokeDynamicEntry invokeDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, + NameAndTypeEntry nameAndType) { + if (!canWriteDirect(bootstrapMethodEntry.constantPool())) + bootstrapMethodEntry = bsmEntry(bootstrapMethodEntry.bootstrapMethod(), + bootstrapMethodEntry.arguments()); + if (!canWriteDirect(nameAndType.constantPool())) + nameAndType = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + int hash = AbstractPoolEntry.hash2(TAG_INVOKEDYNAMIC, + bootstrapMethodEntry.bsmIndex(), nameAndType.index()); + EntryMap map1 = map(); + for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) { + PoolEntry e = map1.getElementByToken(token); + if (e.tag() == TAG_INVOKEDYNAMIC + && e instanceof AbstractPoolEntry.InvokeDynamicEntryImpl ce + && ce.bootstrap() == bootstrapMethodEntry && ce.nameAndType() == nameAndType) + return ce; + } + if (!doneFullScan) { + fullScan(); + return invokeDynamicEntry(bootstrapMethodEntry, nameAndType); + } + + AbstractPoolEntry.InvokeDynamicEntryImpl ce = + new AbstractPoolEntry.InvokeDynamicEntryImpl(this, size, hash, + (BootstrapMethodEntryImpl) bootstrapMethodEntry, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + internalAdd(ce, hash); + return ce; + } + + @Override + public ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, + NameAndTypeEntry nameAndType) { + if (!canWriteDirect(bootstrapMethodEntry.constantPool())) + bootstrapMethodEntry = bsmEntry(bootstrapMethodEntry.bootstrapMethod(), + bootstrapMethodEntry.arguments()); + if (!canWriteDirect(nameAndType.constantPool())) + nameAndType = nameAndTypeEntry(nameAndType.name(), nameAndType.type()); + int hash = AbstractPoolEntry.hash2(TAG_CONSTANTDYNAMIC, + bootstrapMethodEntry.bsmIndex(), nameAndType.index()); + EntryMap map1 = map(); + for (int token = map1.firstToken(hash); token != -1; token = map1.nextToken(hash, token)) { + PoolEntry e = map1.getElementByToken(token); + if (e.tag() == TAG_CONSTANTDYNAMIC + && e instanceof AbstractPoolEntry.ConstantDynamicEntryImpl ce + && ce.bootstrap() == bootstrapMethodEntry && ce.nameAndType() == nameAndType) + return ce; + } + if (!doneFullScan) { + fullScan(); + return constantDynamicEntry(bootstrapMethodEntry, nameAndType); + } + + AbstractPoolEntry.ConstantDynamicEntryImpl ce = + new AbstractPoolEntry.ConstantDynamicEntryImpl(this, size, hash, + (BootstrapMethodEntryImpl) bootstrapMethodEntry, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + internalAdd(ce, hash); + return ce; + } + + @Override + public IntegerEntry intEntry(int value) { + var e = (IntegerEntry) findPrimitiveEntry(TAG_INTEGER, value); + return e == null ? internalAdd(new AbstractPoolEntry.IntegerEntryImpl(this, size, value)) : e; + } + + @Override + public FloatEntry floatEntry(float value) { + var e = (FloatEntry) findPrimitiveEntry(TAG_FLOAT, value); + return e == null ? internalAdd(new AbstractPoolEntry.FloatEntryImpl(this, size, value)) : e; + } + + @Override + public LongEntry longEntry(long value) { + var e = (LongEntry) findPrimitiveEntry(TAG_LONG, value); + return e == null ? internalAdd(new AbstractPoolEntry.LongEntryImpl(this, size, value)) : e; + } + + @Override + public DoubleEntry doubleEntry(double value) { + var e = (DoubleEntry) findPrimitiveEntry(TAG_DOUBLE, value); + return e == null ? internalAdd(new AbstractPoolEntry.DoubleEntryImpl(this, size, value)) : e; + } + + @Override + public StringEntry stringEntry(Utf8Entry utf8) { + AbstractPoolEntry.Utf8EntryImpl ue = maybeCloneUtf8Entry(utf8); + var e = (AbstractPoolEntry.StringEntryImpl) findEntry(TAG_STRING, ue); + return e == null ? internalAdd(new AbstractPoolEntry.StringEntryImpl(this, size, ue)) : e; + } + + @Override + public BootstrapMethodEntry bsmEntry(MethodHandleEntry methodReference, + List arguments) { + if (!canWriteDirect(methodReference.constantPool())) + methodReference = methodHandleEntry(methodReference.kind(), methodReference.reference()); + for (LoadableConstantEntry a : arguments) { + if (!canWriteDirect(a.constantPool())) { + // copy args list + LoadableConstantEntry[] arr = arguments.toArray(new LoadableConstantEntry[0]); + for (int i = 0; i < arr.length; i++) + arr[i] = AbstractPoolEntry.maybeClone(this, arr[i]); + arguments = List.of(arr); + + break; + } + } + AbstractPoolEntry.MethodHandleEntryImpl mre = (AbstractPoolEntry.MethodHandleEntryImpl) methodReference; + int hash = BootstrapMethodEntryImpl.computeHashCode(mre, arguments); + EntryMap map = bsmMap(); + for (int token = map.firstToken(hash); token != -1; token = map.nextToken(hash, token)) { + BootstrapMethodEntryImpl e = map.getElementByToken(token); + if (e.bootstrapMethod() == mre && e.arguments().equals(arguments)) { + return e; + } + } + BootstrapMethodEntryImpl ne = new BootstrapMethodEntryImpl(this, bsmSize, hash, mre, arguments); + return internalAdd(ne, hash); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackCounter$Target.class b/tests/test_data/std/jdk/internal/classfile/impl/StackCounter$Target.class new file mode 100644 index 00000000..da09c223 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackCounter$Target.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackCounter.class b/tests/test_data/std/jdk/internal/classfile/impl/StackCounter.class new file mode 100644 index 00000000..4ae7f68a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackCounter.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackCounter.java b/tests/test_data/std/jdk/internal/classfile/impl/StackCounter.java new file mode 100644 index 00000000..899a4357 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/StackCounter.java @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.TypeKind; +import java.lang.classfile.constantpool.ConstantDynamicEntry; +import java.lang.classfile.constantpool.DynamicConstantPoolEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.constant.MethodTypeDesc; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.BitSet; +import java.util.List; +import java.util.Queue; + +import static java.lang.classfile.ClassFile.*; +import java.lang.constant.ClassDesc; +import java.util.stream.Collectors; + +public final class StackCounter { + + private record Target(int bci, int stack) {} + + static StackCounter of(DirectCodeBuilder dcb, BufWriterImpl buf) { + return new StackCounter( + dcb, + buf.thisClass().asSymbol(), + dcb.methodInfo.methodName().stringValue(), + dcb.methodInfo.methodTypeSymbol(), + (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, + ((BufWriterImpl) dcb.bytecodesBufWriter).asByteBuffer(), + dcb.constantPool, + dcb.handlers); + } + + private int stack, maxStack, maxLocals, rets; + + private final RawBytecodeHelper bcs; + private final ClassDesc thisClass; + private final String methodName; + private final MethodTypeDesc methodDesc; + private final boolean isStatic; + private final ByteBuffer bytecode; + private final SplitConstantPool cp; + private final Queue targets; + private final BitSet visited; + + private void jump(int targetBci) { + if (!visited.get(targetBci)) { + targets.add(new Target(targetBci, stack)); + } + } + + private void addStackSlot(int delta) { + stack += delta; + if (stack > maxStack) maxStack = stack; + } + + private void ensureLocalSlot(int index) { + if (index >= maxLocals) maxLocals = index + 1; + } + + private boolean next() { + Target en; + while ((en = targets.poll()) != null) { + if (!visited.get(en.bci)) { + bcs.nextBci = en.bci; + stack = en.stack; + return true; + } + } + bcs.nextBci = bcs.endBci; + return false; + } + + public StackCounter(LabelContext labelContext, + ClassDesc thisClass, + String methodName, + MethodTypeDesc methodDesc, + boolean isStatic, + ByteBuffer bytecode, + SplitConstantPool cp, + List handlers) { + this.thisClass = thisClass; + this.methodName = methodName; + this.methodDesc = methodDesc; + this.isStatic = isStatic; + this.bytecode = bytecode; + this.cp = cp; + targets = new ArrayDeque<>(); + maxStack = stack = rets = 0; + for (var h : handlers) targets.add(new Target(labelContext.labelToBci(h.handler), 1)); + maxLocals = isStatic ? 0 : 1; + maxLocals += Util.parameterSlots(methodDesc); + bcs = new RawBytecodeHelper(bytecode); + visited = new BitSet(bcs.endBci); + targets.add(new Target(0, 0)); + while (next()) { + while (!bcs.isLastBytecode()) { + bcs.rawNext(); + int opcode = bcs.rawCode; + int bci = bcs.bci; + visited.set(bci); + switch (opcode) { + case NOP, LALOAD, DALOAD, SWAP, INEG, ARRAYLENGTH, INSTANCEOF, LNEG, FNEG, DNEG, I2F, L2D, F2I, D2L, I2B, I2C, I2S, + NEWARRAY, CHECKCAST, ANEWARRAY -> {} + case RETURN -> + next(); + case ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH, + FCONST_0, FCONST_1, FCONST_2, DUP, DUP_X1, DUP_X2, I2L, I2D, F2L, F2D, NEW -> + addStackSlot(+1); + case LCONST_0, LCONST_1, DCONST_0, DCONST_1, DUP2, DUP2_X1, DUP2_X2 -> + addStackSlot(+2); + case POP, MONITORENTER, MONITOREXIT, IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND, + LSHL, LSHR, LUSHR, FADD, FSUB, FMUL, FDIV, FREM, L2I, L2F, D2F, FCMPL, FCMPG, D2I -> + addStackSlot(-1); + case POP2, LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR, DADD, DSUB, DMUL, DDIV, DREM -> + addStackSlot(-2); + case IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE, LCMP, DCMPL, DCMPG -> + addStackSlot(-3); + case LASTORE, DASTORE -> + addStackSlot(-4); + case LDC -> + processLdc(bcs.getIndexU1()); + case LDC_W, LDC2_W -> + processLdc(bcs.getIndexU2()); + case ILOAD, FLOAD, ALOAD -> { + ensureLocalSlot(bcs.getIndex()); + addStackSlot(+1); + } + case LLOAD, DLOAD -> { + ensureLocalSlot(bcs.getIndex() + 1); + addStackSlot(+2); + } + case ILOAD_0, FLOAD_0, ALOAD_0 -> { + ensureLocalSlot(0); + addStackSlot(+1); + } + case ILOAD_1, FLOAD_1, ALOAD_1 -> { + ensureLocalSlot(1); + addStackSlot(+1); + } + case ILOAD_2, FLOAD_2, ALOAD_2 -> { + ensureLocalSlot(2); + addStackSlot(+1); + } + case ILOAD_3, FLOAD_3, ALOAD_3 -> { + ensureLocalSlot(3); + addStackSlot(+1); + } + case LLOAD_0, DLOAD_0 -> { + ensureLocalSlot(1); + addStackSlot(+2); + } + case LLOAD_1, DLOAD_1 -> { + ensureLocalSlot(2); + addStackSlot(+2); + } + case LLOAD_2, DLOAD_2 -> { + ensureLocalSlot(3); + addStackSlot(+2); + } + case LLOAD_3, DLOAD_3 -> { + ensureLocalSlot(4); + addStackSlot(+2); + } + case IALOAD, BALOAD, CALOAD, SALOAD, FALOAD, AALOAD -> { + addStackSlot(-1); + } + case ISTORE, FSTORE, ASTORE -> { + ensureLocalSlot(bcs.getIndex()); + addStackSlot(-1); + } + case LSTORE, DSTORE -> { + ensureLocalSlot(bcs.getIndex() + 1); + addStackSlot(-2); + } + case ISTORE_0, FSTORE_0, ASTORE_0 -> { + ensureLocalSlot(0); + addStackSlot(-1); + } + case ISTORE_1, FSTORE_1, ASTORE_1 -> { + ensureLocalSlot(1); + addStackSlot(-1); + } + case ISTORE_2, FSTORE_2, ASTORE_2 -> { + ensureLocalSlot(2); + addStackSlot(-1); + } + case ISTORE_3, FSTORE_3, ASTORE_3 -> { + ensureLocalSlot(3); + addStackSlot(-1); + } + case LSTORE_0, DSTORE_0 -> { + ensureLocalSlot(1); + addStackSlot(-2); + } + case LSTORE_1, DSTORE_1 -> { + ensureLocalSlot(2); + addStackSlot(-2); + } + case LSTORE_2, DSTORE_2 -> { + ensureLocalSlot(3); + addStackSlot(-2); + } + case LSTORE_3, DSTORE_3 -> { + ensureLocalSlot(4); + addStackSlot(-2); + } + case IINC -> + ensureLocalSlot(bcs.getIndex()); + case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> { + addStackSlot(-2); + jump(bcs.dest()); + } + case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> { + addStackSlot(-1); + jump(bcs.dest()); + } + case GOTO -> { + jump(bcs.dest()); + next(); + } + case GOTO_W -> { + jump(bcs.destW()); + next(); + } + case TABLESWITCH, LOOKUPSWITCH -> { + int alignedBci = RawBytecodeHelper.align(bci + 1); + int defaultOfset = bcs.getInt(alignedBci); + int keys, delta; + addStackSlot(-1); + if (bcs.rawCode == TABLESWITCH) { + int low = bcs.getInt(alignedBci + 4); + int high = bcs.getInt(alignedBci + 2 * 4); + if (low > high) { + throw error("low must be less than or equal to high in tableswitch"); + } + keys = high - low + 1; + if (keys < 0) { + throw error("too many keys in tableswitch"); + } + delta = 1; + } else { + keys = bcs.getInt(alignedBci + 4); + if (keys < 0) { + throw error("number of keys in lookupswitch less than 0"); + } + delta = 2; + for (int i = 0; i < (keys - 1); i++) { + int this_key = bcs.getInt(alignedBci + (2 + 2 * i) * 4); + int next_key = bcs.getInt(alignedBci + (2 + 2 * i + 2) * 4); + if (this_key >= next_key) { + throw error("Bad lookupswitch instruction"); + } + } + } + int target = bci + defaultOfset; + jump(target); + for (int i = 0; i < keys; i++) { + alignedBci = RawBytecodeHelper.align(bcs.bci + 1); + target = bci + bcs.getInt(alignedBci + (3 + i * delta) * 4); + jump(target); + } + next(); + } + case LRETURN, DRETURN -> { + addStackSlot(-2); + next(); + } + case IRETURN, FRETURN, ARETURN, ATHROW -> { + addStackSlot(-1); + next(); + } + case GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD -> { + var tk = TypeKind.fromDescriptor(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType().type()); + switch (bcs.rawCode) { + case GETSTATIC -> + addStackSlot(tk.slotSize()); + case PUTSTATIC -> + addStackSlot(-tk.slotSize()); + case GETFIELD -> + addStackSlot(tk.slotSize() - 1); + case PUTFIELD -> + addStackSlot(-tk.slotSize() - 1); + default -> throw new AssertionError("Should not reach here"); + } + } + case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> { + var cpe = cp.entryByIndex(bcs.getIndexU2()); + var nameAndType = opcode == INVOKEDYNAMIC ? ((DynamicConstantPoolEntry)cpe).nameAndType() : ((MemberRefEntry)cpe).nameAndType(); + var mtd = Util.methodTypeSymbol(nameAndType); + addStackSlot(Util.slotSize(mtd.returnType()) - Util.parameterSlots(mtd)); + if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { + addStackSlot(-1); + } + } + case MULTIANEWARRAY -> + addStackSlot (1 - bcs.getU1(bcs.bci + 3)); + case JSR -> { + addStackSlot(+1); + jump(bcs.dest()); //here we lost track of the exact stack size after return from subroutine + addStackSlot(-1); + } + case JSR_W -> { + addStackSlot(+1); + jump(bcs.destW()); //here we lost track of the exact stack size after return from subroutine + addStackSlot(-1); + } + case RET -> { + ensureLocalSlot(bcs.getIndex()); + rets++; //subroutines must be counted for later maxStack correction + next(); + } + default -> + throw error(String.format("Bad instruction: %02x", opcode)); + } + } + } + //correction of maxStack when subroutines are present by calculation of upper bounds + //the worst scenario is that all subroutines are chained and each subroutine also requires maxStack for its own code + maxStack += rets * maxStack; + } + + /** + * Calculated maximum number of the locals required + * @return maximum number of the locals required + */ + public int maxLocals() { + return maxLocals; + } + + /** + * Calculated maximum stack size required + * @return maximum stack size required + */ + public int maxStack() { + return maxStack; + } + + private void processLdc(int index) { + switch (cp.entryByIndex(index).tag()) { + case TAG_UTF8, TAG_STRING, TAG_CLASS, TAG_INTEGER, TAG_FLOAT, TAG_METHODHANDLE, TAG_METHODTYPE -> + addStackSlot(+1); + case TAG_DOUBLE, TAG_LONG -> + addStackSlot(+2); + case TAG_CONSTANTDYNAMIC -> + addStackSlot(cp.entryByIndex(index, ConstantDynamicEntry.class).typeKind().slotSize()); + default -> + throw error("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag())); + } + } + + private IllegalArgumentException error(String msg) { + var sb = new StringBuilder("%s at bytecode offset %d of method %s(%s)".formatted( + msg, + bcs.bci, + methodName, + methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")))); + Util.dumpMethod(cp, thisClass, methodName, methodDesc, isStatic ? ACC_STATIC : 0, bytecode, sb::append); + return new IllegalArgumentException(sb.toString()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$ObjectVerificationTypeInfoImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$ObjectVerificationTypeInfoImpl.class new file mode 100644 index 00000000..f807c5bc Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$ObjectVerificationTypeInfoImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$StackMapFrameImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$StackMapFrameImpl.class new file mode 100644 index 00000000..2a246fa5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$StackMapFrameImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$UninitializedVerificationTypeInfoImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$UninitializedVerificationTypeInfoImpl.class new file mode 100644 index 00000000..811a5727 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder$UninitializedVerificationTypeInfoImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder.class new file mode 100644 index 00000000..934d3fa4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder.java b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder.java new file mode 100644 index 00000000..2ea8922d --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/StackMapDecoder.java @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.classfile.impl; + +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassReader; +import java.lang.classfile.Label; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.classfile.attribute.StackMapFrameInfo.*; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.AccessFlag; +import java.util.List; +import java.util.Objects; +import java.util.TreeMap; + +import static java.lang.classfile.ClassFile.*; + +public class StackMapDecoder { + + private static final int + SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, + SAME_EXTENDED = 251; + + private final ClassReader classReader; + private final int pos; + private final LabelContext ctx; + private final List initFrameLocals; + private int p; + + StackMapDecoder(ClassReader classReader, int pos, LabelContext ctx, List initFrameLocals) { + this.classReader = classReader; + this.pos = pos; + this.ctx = ctx; + this.initFrameLocals = initFrameLocals; + } + + static List initFrameLocals(MethodModel method) { + return initFrameLocals(method.parent().orElseThrow().thisClass(), + method.methodName().stringValue(), + method.methodTypeSymbol(), + method.flags().has(AccessFlag.STATIC)); + } + + public static List initFrameLocals(ClassEntry thisClass, String methodName, MethodTypeDesc methodType, boolean isStatic) { + VerificationTypeInfo vtis[]; + int i = 0; + if (!isStatic) { + vtis = new VerificationTypeInfo[methodType.parameterCount() + 1]; + if ("".equals(methodName) && !ConstantDescs.CD_Object.equals(thisClass.asSymbol())) { + vtis[i++] = SimpleVerificationTypeInfo.ITEM_UNINITIALIZED_THIS; + } else { + vtis[i++] = new StackMapDecoder.ObjectVerificationTypeInfoImpl(thisClass); + } + } else { + vtis = new VerificationTypeInfo[methodType.parameterCount()]; + } + for (int pi = 0; pi < methodType.parameterCount(); pi++) { + var arg = methodType.parameterType(pi); + vtis[i++] = switch (arg.descriptorString().charAt(0)) { + case 'I', 'S', 'C' ,'B', 'Z' -> SimpleVerificationTypeInfo.ITEM_INTEGER; + case 'J' -> SimpleVerificationTypeInfo.ITEM_LONG; + case 'F' -> SimpleVerificationTypeInfo.ITEM_FLOAT; + case 'D' -> SimpleVerificationTypeInfo.ITEM_DOUBLE; + case 'V' -> throw new IllegalArgumentException("Illegal method argument type: " + arg); + default -> new StackMapDecoder.ObjectVerificationTypeInfoImpl(TemporaryConstantPool.INSTANCE.classEntry(arg)); + }; + } + return List.of(vtis); + } + + public static void writeFrames(BufWriter b, List entries) { + var buf = (BufWriterImpl)b; + var dcb = (DirectCodeBuilder)buf.labelContext(); + var mi = dcb.methodInfo(); + var prevLocals = StackMapDecoder.initFrameLocals(buf.thisClass(), + mi.methodName().stringValue(), + mi.methodTypeSymbol(), + (mi.methodFlags() & ACC_STATIC) != 0); + int prevOffset = -1; + var map = new TreeMap(); + //sort by resolved label offsets first to allow unordered entries + for (var fr : entries) { + map.put(dcb.labelToBci(fr.target()), fr); + } + b.writeU2(map.size()); + for (var me : map.entrySet()) { + int offset = me.getKey(); + var fr = me.getValue(); + writeFrame(buf, offset - prevOffset - 1, prevLocals, fr); + prevOffset = offset; + prevLocals = fr.locals(); + } + } + + private static void writeFrame(BufWriterImpl out, int offsetDelta, List prevLocals, StackMapFrameInfo fr) { + if (offsetDelta < 0) throw new IllegalArgumentException("Invalid stack map frames order"); + if (fr.stack().isEmpty()) { + int commonLocalsSize = Math.min(prevLocals.size(), fr.locals().size()); + int diffLocalsSize = fr.locals().size() - prevLocals.size(); + if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(fr.locals(), prevLocals, commonLocalsSize)) { + if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame + out.writeU1(offsetDelta); + } else { //chop, same extended or append frame + out.writeU1(251 + diffLocalsSize); + out.writeU2(offsetDelta); + for (int i=commonLocalsSize; i l1, List l2, int compareSize) { + for (int i = 0; i < compareSize; i++) { + if (!l1.get(i).equals(l2.get(i))) return false; + } + return true; + } + + private static void writeTypeInfo(BufWriterImpl bw, VerificationTypeInfo vti) { + bw.writeU1(vti.tag()); + switch (vti) { + case SimpleVerificationTypeInfo svti -> + {} + case ObjectVerificationTypeInfo ovti -> + bw.writeIndex(ovti.className()); + case UninitializedVerificationTypeInfo uvti -> + bw.writeU2(bw.labelContext().labelToBci(uvti.newTarget())); + } + } + + List entries() { + p = pos; + List locals = initFrameLocals, stack = List.of(); + int bci = -1; + var entries = new StackMapFrameInfo[u2()]; + for (int ei = 0; ei < entries.length; ei++) { + int frameType = classReader.readU1(p++); + if (frameType < 64) { + bci += frameType + 1; + stack = List.of(); + } else if (frameType < 128) { + bci += frameType - 63; + stack = List.of(readVerificationTypeInfo()); + } else { + if (frameType < SAME_LOCALS_1_STACK_ITEM_EXTENDED) + throw new IllegalArgumentException("Invalid stackmap frame type: " + frameType); + bci += u2() + 1; + if (frameType == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + stack = List.of(readVerificationTypeInfo()); + } else if (frameType < SAME_EXTENDED) { + locals = locals.subList(0, locals.size() + frameType - SAME_EXTENDED); + stack = List.of(); + } else if (frameType == SAME_EXTENDED) { + stack = List.of(); + } else if (frameType < SAME_EXTENDED + 4) { + int actSize = locals.size(); + var newLocals = locals.toArray(new VerificationTypeInfo[actSize + frameType - SAME_EXTENDED]); + for (int i = actSize; i < newLocals.length; i++) + newLocals[i] = readVerificationTypeInfo(); + locals = List.of(newLocals); + stack = List.of(); + } else { + var newLocals = new VerificationTypeInfo[u2()]; + for (int i=0; i SimpleVerificationTypeInfo.ITEM_TOP; + case VT_INTEGER -> SimpleVerificationTypeInfo.ITEM_INTEGER; + case VT_FLOAT -> SimpleVerificationTypeInfo.ITEM_FLOAT; + case VT_DOUBLE -> SimpleVerificationTypeInfo.ITEM_DOUBLE; + case VT_LONG -> SimpleVerificationTypeInfo.ITEM_LONG; + case VT_NULL -> SimpleVerificationTypeInfo.ITEM_NULL; + case VT_UNINITIALIZED_THIS -> SimpleVerificationTypeInfo.ITEM_UNINITIALIZED_THIS; + case VT_OBJECT -> new ObjectVerificationTypeInfoImpl(classReader.entryByIndex(u2(), ClassEntry.class)); + case VT_UNINITIALIZED -> new UninitializedVerificationTypeInfoImpl(ctx.getLabel(u2())); + default -> throw new IllegalArgumentException("Invalid verification type tag: " + tag); + }; + } + + public static record ObjectVerificationTypeInfoImpl( + ClassEntry className) implements ObjectVerificationTypeInfo { + + @Override + public int tag() { return VT_OBJECT; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ObjectVerificationTypeInfoImpl that) { + return Objects.equals(className, that.className); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(className); + } + + @Override + public String toString() { + return className.asInternalName(); + } + } + + public static record UninitializedVerificationTypeInfoImpl(Label newTarget) implements UninitializedVerificationTypeInfo { + + @Override + public int tag() { return VT_UNINITIALIZED; } + + @Override + public String toString() { + return "UNINIT(" + newTarget +")"; + } + } + + private int u2() { + int v = classReader.readU2(p); + p += 2; + return v; + } + + public static record StackMapFrameImpl(int frameType, + Label target, + List locals, + List stack) + implements StackMapFrameInfo { + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$1.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$1.class new file mode 100644 index 00000000..45adcada Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$2.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$2.class new file mode 100644 index 00000000..cd1da3b1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$2.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$Frame.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$Frame.class new file mode 100644 index 00000000..f7fc72dd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$Frame.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$RawExceptionCatch.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$RawExceptionCatch.class new file mode 100644 index 00000000..e66ebf3b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$RawExceptionCatch.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$Type.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$Type.class new file mode 100644 index 00000000..7a428461 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator$Type.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator.class b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator.class new file mode 100644 index 00000000..100a511b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator.java b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator.java new file mode 100644 index 00000000..dd4d7d42 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/StackMapGenerator.java @@ -0,0 +1,1402 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.constantpool.InvokeDynamicEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; +import java.lang.classfile.ClassFile; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantDynamicEntry; +import java.lang.classfile.constantpool.DynamicConstantPoolEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.lang.classfile.Attribute; + +import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.BufWriter; +import java.lang.classfile.Label; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.Attributes; + +/** + * StackMapGenerator is responsible for stack map frames generation. + *

+ * Stack map frames are computed from serialized bytecode similar way they are verified during class loading process. + *

+ * The {@linkplain #generate() frames computation} consists of following steps: + *

    + *
  1. {@linkplain #detectFrameOffsets() Detection} of mandatory stack map frames offsets:
      + *
    • Mandatory stack map frame offsets include all jump and switch instructions targets, + * offsets immediately following {@linkplain #noControlFlow(int) "no control flow"} + * and all exception table handlers. + *
    • Detection is performed in a single fast pass through the bytecode, + * with no auxiliary structures construction nor further instructions processing. + *
    + *
  2. Generator loop {@linkplain #processMethod() processing bytecode instructions}:
      + *
    • Generator loop simulates sequence instructions {@linkplain #processBlock(RawBytecodeHelper) processing effect on the actual stack and locals}. + *
    • All mandatory {@linkplain Frame frames} detected in the step #1 are {@linkplain Frame#checkAssignableTo(Frame) retro-filled} + * (or {@linkplain Frame#merge(Type, Type[], int, Frame) reverse-merged} in subsequent processing) + * with the actual stack and locals for all matching jump, switch and exception handler targets. + *
    • All frames modified by reverse merges are marked as {@linkplain Frame#dirty dirty} for further processing. + *
    • Code blocks with not yet known entry frame content are skipped and related frames are also marked as dirty. + *
    • Generator loop process is repeated until all mandatory frames are cleared or until an error state is reached. + *
    • Generator loop always passes all instructions at least once to calculate {@linkplain #maxStack max stack} + * and {@linkplain #maxLocals max locals} code attributes. + *
    • More than one pass is usually not necessary, except for more complex bytecode sequences.
      + * (Note: experimental measurements showed that more than 99% of the cases required only single pass to clear all frames, + * less than 1% of the cases required second pass and remaining 0,01% of the cases required third pass to clear all frames.). + *
    + *
  3. Dead code patching to pass class loading verification:
      + *
    • Dead code blocks are indicated by frames remaining without content after leaving the Generator loop. + *
    • Each dead code block is filled with NOP instructions, terminated with + * ATHROW instruction, and removed from exception handlers table. + *
    • Dead code block entry frame is set to java.lang.Throwable single stack item and no locals. + *
    + *
+ *

+ * {@linkplain Frame#merge(Type, Type[], int, Frame) Reverse-merge} of the stack map frames + * may in some situations require to determine {@linkplain ClassHierarchyImpl class hierarchy} relations. + *

+ * Reverse-merge of individual {@linkplain Type types} is performed when a target frame has already been retro-filled + * and it is necessary to adjust its existing stack entries and locals to also match actual stack map frame conditions. + * Following tables describe how new target stack entry or local type is calculated, based on the actual frame stack entry or local ("from") + * and actual value of the target stack entry or local ("to"). + * + * + * + *
Reverse-merge of general type categories
to \ fromTOPPRIMITIVEUNINITIALIZEDREFERENCE + *
TOPTOPTOPTOPTOP + *
PRIMITIVETOPReverse-merge of primitive typesTOPTOP + *
UNINITIALIZEDTOPTOPIs NEW offset matching ? UNINITIALIZED : TOPTOP + *
REFERENCETOPTOPTOPReverse-merge of reference types + *
+ *

+ * + * + *
Reverse-merge of primitive types
to \ fromSHORTBYTEBOOLEANLONGDOUBLEFLOATINTEGER + *
SHORTSHORTTOPTOPTOPTOPTOPSHORT + *
BYTETOPBYTETOPTOPTOPTOPBYTE + *
BOOLEANTOPTOPBOOLEANTOPTOPTOPBOOLEAN + *
LONGTOPTOPTOPLONGTOPTOPTOP + *
DOUBLETOPTOPTOPTOPDOUBLETOPTOP + *
FLOATTOPTOPTOPTOPTOPFLOATTOP + *
INTEGERTOPTOPTOPTOPTOPTOPINTEGER + *
+ *

+ * + * + *
Reverse merge of reference types
to \ fromNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACE*OBJECT** + *
NULLNULLj.l.Objectj.l.Cloneablej.i.SerializableARRAYINTERFACEOBJECT + *
j.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object + *
j.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Cloneablej.l.Objectj.l.Cloneablej.l.Cloneable + *
j.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.i.Serializablej.l.Objectj.i.Serializablej.i.Serializable + *
ARRAYARRAYj.l.Objectj.l.Objectj.l.ObjectReverse merge of arraysj.l.Objectj.l.Object + *
INTERFACE*INTERFACEj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.Object + *
OBJECT**OBJECTj.l.Objectj.l.Objectj.l.Objectj.l.Objectj.l.ObjectResolved common ancestor + *
*any interface reference except for j.l.Cloneable and j.i.Serializable
**any object reference except for j.l.Object + *
+ *

+ * Array types are reverse-merged as reference to array type constructed from reverse-merged components. + * Reference to j.l.Object is an alternate result when construction of the array type is not possible (when reverse-merge of components returned TOP or other non-reference and non-primitive type). + *

+ * Custom class hierarchy resolver has been implemented as a part of the library to avoid heavy class loading + * and to allow stack maps generation even for code with incomplete dependency classpath. + * However stack maps generated with {@linkplain ClassHierarchyImpl#resolve(java.lang.constant.ClassDesc) warnings of unresolved dependencies} may later fail to verify during class loading process. + *

+ * Focus of the whole algorithm is on high performance and low memory footprint:

    + *
  • It does not produce, collect nor visit any complex intermediate structures + * (beside {@linkplain RawBytecodeHelper traversing} the {@linkplain #bytecode bytecode in binary form}). + *
  • It works with only minimal mandatory stack map frames. + *
  • It does not spend time on any non-essential verifications. + *
+ */ + +public final class StackMapGenerator { + + static StackMapGenerator of(DirectCodeBuilder dcb, BufWriterImpl buf) { + return new StackMapGenerator( + dcb, + buf.thisClass().asSymbol(), + dcb.methodInfo.methodName().stringValue(), + dcb.methodInfo.methodTypeSymbol(), + (dcb.methodInfo.methodFlags() & ACC_STATIC) != 0, + ((BufWriterImpl) dcb.bytecodesBufWriter).asByteBuffer(), + dcb.constantPool, + dcb.context, + dcb.handlers); + } + + private static final String OBJECT_INITIALIZER_NAME = ""; + private static final int FLAG_THIS_UNINIT = 0x01; + private static final int FRAME_DEFAULT_CAPACITY = 10; + private static final int T_BOOLEAN = 4, T_LONG = 11; + + private static final int ITEM_TOP = 0, + ITEM_INTEGER = 1, + ITEM_FLOAT = 2, + ITEM_DOUBLE = 3, + ITEM_LONG = 4, + ITEM_NULL = 5, + ITEM_UNINITIALIZED_THIS = 6, + ITEM_OBJECT = 7, + ITEM_UNINITIALIZED = 8, + ITEM_BOOLEAN = 9, + ITEM_BYTE = 10, + ITEM_SHORT = 11, + ITEM_CHAR = 12, + ITEM_LONG_2ND = 13, + ITEM_DOUBLE_2ND = 14; + + private static final Type[] ARRAY_FROM_BASIC_TYPE = {null, null, null, null, + Type.BOOLEAN_ARRAY_TYPE, Type.CHAR_ARRAY_TYPE, Type.FLOAT_ARRAY_TYPE, Type.DOUBLE_ARRAY_TYPE, + Type.BYTE_ARRAY_TYPE, Type.SHORT_ARRAY_TYPE, Type.INT_ARRAY_TYPE, Type.LONG_ARRAY_TYPE}; + + static record RawExceptionCatch(int start, int end, int handler, Type catchType) {} + + private final Type thisType; + private final String methodName; + private final MethodTypeDesc methodDesc; + private final ByteBuffer bytecode; + private final SplitConstantPool cp; + private final boolean isStatic; + private final LabelContext labelContext; + private final List handlers; + private final List rawHandlers; + private final ClassHierarchyImpl classHierarchy; + private final boolean patchDeadCode; + private final boolean filterDeadLabels; + private List frames; + private final Frame currentFrame; + private int maxStack, maxLocals; + + /** + * Primary constructor of the Generator class. + * New Generator instance must be created for each individual class/method. + * Instance contains only immutable results, all the calculations are processed during instance construction. + * + * @param labelContext LabelContext instance used to resolve or patch ExceptionHandler + * labels to bytecode offsets (or vice versa) + * @param thisClass class to generate stack maps for + * @param methodName method name to generate stack maps for + * @param methodDesc method descriptor to generate stack maps for + * @param isStatic information whether the method is static + * @param bytecode R/W ByteBuffer wrapping method bytecode, the content is altered in case Generator detects and patches dead code + * @param cp R/W ConstantPoolBuilder instance used to resolve all involved CP entries and also generate new entries referenced from the generated stack maps + * @param handlers R/W ExceptionHandler list used to detect mandatory frame offsets as well as to determine stack maps in exception handlers + * and also to be altered when dead code is detected and must be excluded from exception handlers + */ + public StackMapGenerator(LabelContext labelContext, + ClassDesc thisClass, + String methodName, + MethodTypeDesc methodDesc, + boolean isStatic, + ByteBuffer bytecode, + SplitConstantPool cp, + ClassFileImpl context, + List handlers) { + this.thisType = Type.referenceType(thisClass); + this.methodName = methodName; + this.methodDesc = methodDesc; + this.isStatic = isStatic; + this.bytecode = bytecode; + this.cp = cp; + this.labelContext = labelContext; + this.handlers = handlers; + this.rawHandlers = new ArrayList<>(handlers.size()); + this.classHierarchy = new ClassHierarchyImpl(context.classHierarchyResolverOption().classHierarchyResolver()); + this.patchDeadCode = context.deadCodeOption() == ClassFile.DeadCodeOption.PATCH_DEAD_CODE; + this.filterDeadLabels = context.deadLabelsOption() == ClassFile.DeadLabelsOption.DROP_DEAD_LABELS; + this.currentFrame = new Frame(classHierarchy); + generate(); + } + + /** + * Calculated maximum number of the locals required + * @return maximum number of the locals required + */ + public int maxLocals() { + return maxLocals; + } + + /** + * Calculated maximum stack size required + * @return maximum stack size required + */ + public int maxStack() { + return maxStack; + } + + private Frame getFrame(int offset) { + //binary search over frames ordered by offset + int low = 0; + int high = frames.size() - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + var f = frames.get(mid); + if (f.offset < offset) + low = mid + 1; + else if (f.offset > offset) + high = mid - 1; + else + return f; + } + return null; + } + + private void checkJumpTarget(Frame frame, int target) { + frame.checkAssignableTo(getFrame(target)); + } + + private int exMin, exMax; + + private boolean isAnyFrameDirty() { + for (var f : frames) { + if (f.dirty) return true; + } + return false; + } + + private void generate() { + exMin = bytecode.capacity(); + exMax = -1; + for (var exhandler : handlers) { + int start_pc = labelContext.labelToBci(exhandler.tryStart()); + int end_pc = labelContext.labelToBci(exhandler.tryEnd()); + int handler_pc = labelContext.labelToBci(exhandler.handler()); + if (start_pc >= 0 && end_pc >= 0 && end_pc > start_pc && handler_pc >= 0) { + if (start_pc < exMin) exMin = start_pc; + if (end_pc > exMax) exMax = end_pc; + var catchType = exhandler.catchType(); + rawHandlers.add(new RawExceptionCatch(start_pc, end_pc, handler_pc, + catchType.isPresent() ? cpIndexToType(catchType.get().index(), cp) + : Type.THROWABLE_TYPE)); + } + } + BitSet frameOffsets = detectFrameOffsets(); + int framesCount = frameOffsets.cardinality(); + frames = new ArrayList<>(framesCount); + int offset = -1; + for (int i = 0; i < framesCount; i++) { + offset = frameOffsets.nextSetBit(offset + 1); + frames.add(new Frame(offset, classHierarchy)); + } + do { + processMethod(); + } while (isAnyFrameDirty()); + maxLocals = currentFrame.frameMaxLocals; + maxStack = currentFrame.frameMaxStack; + + //dead code patching + for (int i = 0; i < framesCount; i++) { + var frame = frames.get(i); + if (frame.flags == -1) { + if (!patchDeadCode) throw generatorError("Unable to generate stack map frame for dead code", frame.offset); + //patch frame + frame.pushStack(Type.THROWABLE_TYPE); + if (maxStack < 1) maxStack = 1; + int blockSize = (i < framesCount - 1 ? frames.get(i + 1).offset : bytecode.limit()) - frame.offset; + //patch bytecode + bytecode.position(frame.offset); + for (int n=1; n= handlerEnd || rangeEnd <= handlerStart) { + //out of range + continue; + } + if (rangeStart <= handlerStart) { + if (rangeEnd >= handlerEnd) { + //complete removal + it.remove(); + } else { + //cut from left + Label newStart = labelContext.newLabel(); + labelContext.setLabelTarget(newStart, rangeEnd); + it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType())); + } + } else if (rangeEnd >= handlerEnd) { + //cut from right + Label newEnd = labelContext.newLabel(); + labelContext.setLabelTarget(newEnd, rangeStart); + it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); + } else { + //split + Label newStart = labelContext.newLabel(); + labelContext.setLabelTarget(newStart, rangeEnd); + Label newEnd = labelContext.newLabel(); + labelContext.setLabelTarget(newEnd, rangeStart); + it.set(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), e.tryStart(), newEnd, e.catchType())); + it.add(new AbstractPseudoInstruction.ExceptionCatchImpl(e.handler(), newStart, e.tryEnd(), e.catchType())); + } + } + } + + /** + * Getter of the generated StackMapTableAttribute or null if stack map is empty + * @return StackMapTableAttribute or null if stack map is empty + */ + public Attribute stackMapTableAttribute() { + return frames.isEmpty() ? null : new UnboundAttribute.AdHocAttribute<>(Attributes.stackMapTable()) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(frames.size()); + Frame prevFrame = new Frame(classHierarchy); + prevFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); + prevFrame.trimAndCompress(); + for (var fr : frames) { + fr.trimAndCompress(); + fr.writeTo(b, prevFrame, cp); + prevFrame = fr; + } + } + }; + } + + private static Type cpIndexToType(int index, ConstantPoolBuilder cp) { + return Type.referenceType(cp.entryByIndex(index, ClassEntry.class).asSymbol()); + } + + private void processMethod() { + currentFrame.setLocalsFromArg(methodName, methodDesc, isStatic, thisType); + currentFrame.stackSize = 0; + currentFrame.flags = 0; + currentFrame.offset = -1; + int stackmapIndex = 0; + RawBytecodeHelper bcs = new RawBytecodeHelper(bytecode); + boolean ncf = false; + while (!bcs.isLastBytecode()) { + bcs.rawNext(); + currentFrame.offset = bcs.bci; + if (stackmapIndex < frames.size()) { + int thisOffset = frames.get(stackmapIndex).offset; + if (ncf && thisOffset > bcs.bci) { + throw generatorError("Expecting a stack map frame"); + } + if (thisOffset == bcs.bci) { + if (!ncf) { + currentFrame.checkAssignableTo(frames.get(stackmapIndex)); + } + Frame nextFrame = frames.get(stackmapIndex++); + while (!nextFrame.dirty) { //skip unmatched frames + if (stackmapIndex == frames.size()) return; //skip the rest of this round + nextFrame = frames.get(stackmapIndex++); + } + bcs.rawNext(nextFrame.offset); //skip code up-to the next frame + currentFrame.offset = bcs.bci; + currentFrame.copyFrom(nextFrame); + nextFrame.dirty = false; + } else if (thisOffset < bcs.bci) { + throw new ClassFormatError(String.format("Bad stack map offset %d", thisOffset)); + } + } else if (ncf) { + throw generatorError("Expecting a stack map frame"); + } + ncf = processBlock(bcs); + } + } + + private boolean processBlock(RawBytecodeHelper bcs) { + int opcode = bcs.rawCode; + boolean ncf = false; + boolean this_uninit = false; + boolean verified_exc_handlers = false; + int bci = bcs.bci; + Type type1, type2, type3, type4; + if (RawBytecodeHelper.isStoreIntoLocal(opcode) && bci >= exMin && bci < exMax) { + processExceptionHandlerTargets(bci, this_uninit); + verified_exc_handlers = true; + } + switch (opcode) { + case NOP -> {} + case RETURN -> { + ncf = true; + } + case ACONST_NULL -> + currentFrame.pushStack(Type.NULL_TYPE); + case ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, SIPUSH, BIPUSH -> + currentFrame.pushStack(Type.INTEGER_TYPE); + case LCONST_0, LCONST_1 -> + currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case FCONST_0, FCONST_1, FCONST_2 -> + currentFrame.pushStack(Type.FLOAT_TYPE); + case DCONST_0, DCONST_1 -> + currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case LDC -> + processLdc(bcs.getIndexU1()); + case LDC_W, LDC2_W -> + processLdc(bcs.getIndexU2()); + case ILOAD -> + currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.INTEGER_TYPE); + case ILOAD_0, ILOAD_1, ILOAD_2, ILOAD_3 -> + currentFrame.checkLocal(opcode - ILOAD_0).pushStack(Type.INTEGER_TYPE); + case LLOAD -> + currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case LLOAD_0, LLOAD_1, LLOAD_2, LLOAD_3 -> + currentFrame.checkLocal(opcode - LLOAD_0 + 1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case FLOAD -> + currentFrame.checkLocal(bcs.getIndex()).pushStack(Type.FLOAT_TYPE); + case FLOAD_0, FLOAD_1, FLOAD_2, FLOAD_3 -> + currentFrame.checkLocal(opcode - FLOAD_0).pushStack(Type.FLOAT_TYPE); + case DLOAD -> + currentFrame.checkLocal(bcs.getIndex() + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case DLOAD_0, DLOAD_1, DLOAD_2, DLOAD_3 -> + currentFrame.checkLocal(opcode - DLOAD_0 + 1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case ALOAD -> + currentFrame.pushStack(currentFrame.getLocal(bcs.getIndex())); + case ALOAD_0, ALOAD_1, ALOAD_2, ALOAD_3 -> + currentFrame.pushStack(currentFrame.getLocal(opcode - ALOAD_0)); + case IALOAD, BALOAD, CALOAD, SALOAD -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case LALOAD -> + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case FALOAD -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case DALOAD -> + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case AALOAD -> + currentFrame.pushStack((type1 = currentFrame.decStack(1).popStack()) == Type.NULL_TYPE ? Type.NULL_TYPE : type1.getComponent()); + case ISTORE -> + currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.INTEGER_TYPE); + case ISTORE_0, ISTORE_1, ISTORE_2, ISTORE_3 -> + currentFrame.decStack(1).setLocal(opcode - ISTORE_0, Type.INTEGER_TYPE); + case LSTORE -> + currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.LONG_TYPE, Type.LONG2_TYPE); + case LSTORE_0, LSTORE_1, LSTORE_2, LSTORE_3 -> + currentFrame.decStack(2).setLocal2(opcode - LSTORE_0, Type.LONG_TYPE, Type.LONG2_TYPE); + case FSTORE -> + currentFrame.decStack(1).setLocal(bcs.getIndex(), Type.FLOAT_TYPE); + case FSTORE_0, FSTORE_1, FSTORE_2, FSTORE_3 -> + currentFrame.decStack(1).setLocal(opcode - FSTORE_0, Type.FLOAT_TYPE); + case DSTORE -> + currentFrame.decStack(2).setLocal2(bcs.getIndex(), Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case DSTORE_0, DSTORE_1, DSTORE_2, DSTORE_3 -> + currentFrame.decStack(2).setLocal2(opcode - DSTORE_0, Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case ASTORE -> + currentFrame.setLocal(bcs.getIndex(), currentFrame.popStack()); + case ASTORE_0, ASTORE_1, ASTORE_2, ASTORE_3 -> + currentFrame.setLocal(opcode - ASTORE_0, currentFrame.popStack()); + case LASTORE, DASTORE -> + currentFrame.decStack(4); + case IASTORE, BASTORE, CASTORE, SASTORE, FASTORE, AASTORE -> + currentFrame.decStack(3); + case POP, MONITORENTER, MONITOREXIT -> + currentFrame.decStack(1); + case POP2 -> + currentFrame.decStack(2); + case DUP -> + currentFrame.pushStack(type1 = currentFrame.popStack()).pushStack(type1); + case DUP_X1 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + currentFrame.pushStack(type1).pushStack(type2).pushStack(type1); + } + case DUP_X2 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + type3 = currentFrame.popStack(); + currentFrame.pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); + } + case DUP2 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + currentFrame.pushStack(type2).pushStack(type1).pushStack(type2).pushStack(type1); + } + case DUP2_X1 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + type3 = currentFrame.popStack(); + currentFrame.pushStack(type2).pushStack(type1).pushStack(type3).pushStack(type2).pushStack(type1); + } + case DUP2_X2 -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + type3 = currentFrame.popStack(); + type4 = currentFrame.popStack(); + currentFrame.pushStack(type2).pushStack(type1).pushStack(type4).pushStack(type3).pushStack(type2).pushStack(type1); + } + case SWAP -> { + type1 = currentFrame.popStack(); + type2 = currentFrame.popStack(); + currentFrame.pushStack(type1); + currentFrame.pushStack(type2); + } + case IADD, ISUB, IMUL, IDIV, IREM, ISHL, ISHR, IUSHR, IOR, IXOR, IAND -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case INEG, ARRAYLENGTH, INSTANCEOF -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case LADD, LSUB, LMUL, LDIV, LREM, LAND, LOR, LXOR -> + currentFrame.decStack(4).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case LNEG -> + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case LSHL, LSHR, LUSHR -> + currentFrame.decStack(3).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case FADD, FSUB, FMUL, FDIV, FREM -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case FNEG -> + currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); + case DADD, DSUB, DMUL, DDIV, DREM -> + currentFrame.decStack(4).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case DNEG -> + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case IINC -> + currentFrame.checkLocal(bcs.getIndex()); + case I2L -> + currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case L2I -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case I2F -> + currentFrame.decStack(1).pushStack(Type.FLOAT_TYPE); + case I2D -> + currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case L2F -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case L2D -> + currentFrame.decStack(2).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case F2I -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case F2L -> + currentFrame.decStack(1).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case F2D -> + currentFrame.decStack(1).pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case D2L -> + currentFrame.decStack(2).pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case D2F -> + currentFrame.decStack(2).pushStack(Type.FLOAT_TYPE); + case I2B, I2C, I2S -> + currentFrame.decStack(1).pushStack(Type.INTEGER_TYPE); + case LCMP, DCMPL, DCMPG -> + currentFrame.decStack(4).pushStack(Type.INTEGER_TYPE); + case FCMPL, FCMPG, D2I -> + currentFrame.decStack(2).pushStack(Type.INTEGER_TYPE); + case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE -> + checkJumpTarget(currentFrame.decStack(2), bcs.dest()); + case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IFNULL, IFNONNULL -> + checkJumpTarget(currentFrame.decStack(1), bcs.dest()); + case GOTO -> { + checkJumpTarget(currentFrame, bcs.dest()); + ncf = true; + } + case GOTO_W -> { + checkJumpTarget(currentFrame, bcs.destW()); + ncf = true; + } + case TABLESWITCH, LOOKUPSWITCH -> { + processSwitch(bcs); + ncf = true; + } + case LRETURN, DRETURN -> { + currentFrame.decStack(2); + ncf = true; + } + case IRETURN, FRETURN, ARETURN, ATHROW -> { + currentFrame.decStack(1); + ncf = true; + } + case GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD -> + processFieldInstructions(bcs); + case INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE, INVOKEDYNAMIC -> + this_uninit = processInvokeInstructions(bcs, (bci >= exMin && bci < exMax), this_uninit); + case NEW -> + currentFrame.pushStack(Type.uninitializedType(bci)); + case NEWARRAY -> + currentFrame.decStack(1).pushStack(getNewarrayType(bcs.getIndex())); + case ANEWARRAY -> + processAnewarray(bcs.getIndexU2()); + case CHECKCAST -> + currentFrame.decStack(1).pushStack(cpIndexToType(bcs.getIndexU2(), cp)); + case MULTIANEWARRAY -> { + type1 = cpIndexToType(bcs.getIndexU2(), cp); + int dim = bcs.getU1(bcs.bci + 3); + for (int i = 0; i < dim; i++) { + currentFrame.popStack(); + } + currentFrame.pushStack(type1); + } + case JSR, JSR_W, RET -> + throw generatorError("Instructions jsr, jsr_w, or ret must not appear in the class file version >= 51.0"); + default -> + throw generatorError(String.format("Bad instruction: %02x", opcode)); + } + if (!verified_exc_handlers && bci >= exMin && bci < exMax) { + processExceptionHandlerTargets(bci, this_uninit); + } + return ncf; + } + + private void processExceptionHandlerTargets(int bci, boolean this_uninit) { + for (var ex : rawHandlers) { + if (bci == ex.start || (currentFrame.localsChanged && bci > ex.start && bci < ex.end)) { + int flags = currentFrame.flags; + if (this_uninit) flags |= FLAG_THIS_UNINIT; + Frame newFrame = currentFrame.frameInExceptionHandler(flags, ex.catchType); + checkJumpTarget(newFrame, ex.handler); + } + } + currentFrame.localsChanged = false; + } + + private void processLdc(int index) { + switch (cp.entryByIndex(index).tag()) { + case TAG_UTF8 -> + currentFrame.pushStack(Type.OBJECT_TYPE); + case TAG_STRING -> + currentFrame.pushStack(Type.STRING_TYPE); + case TAG_CLASS -> + currentFrame.pushStack(Type.CLASS_TYPE); + case TAG_INTEGER -> + currentFrame.pushStack(Type.INTEGER_TYPE); + case TAG_FLOAT -> + currentFrame.pushStack(Type.FLOAT_TYPE); + case TAG_DOUBLE -> + currentFrame.pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case TAG_LONG -> + currentFrame.pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case TAG_METHODHANDLE -> + currentFrame.pushStack(Type.METHOD_HANDLE_TYPE); + case TAG_METHODTYPE -> + currentFrame.pushStack(Type.METHOD_TYPE); + case TAG_CONSTANTDYNAMIC -> + currentFrame.pushStack(cp.entryByIndex(index, ConstantDynamicEntry.class).asSymbol().constantType()); + default -> + throw generatorError("CP entry #%d %s is not loadable constant".formatted(index, cp.entryByIndex(index).tag())); + } + } + + private void processSwitch(RawBytecodeHelper bcs) { + int bci = bcs.bci; + int alignedBci = RawBytecodeHelper.align(bci + 1); + int defaultOfset = bcs.getInt(alignedBci); + int keys, delta; + currentFrame.popStack(); + if (bcs.rawCode == TABLESWITCH) { + int low = bcs.getInt(alignedBci + 4); + int high = bcs.getInt(alignedBci + 2 * 4); + if (low > high) { + throw generatorError("low must be less than or equal to high in tableswitch"); + } + keys = high - low + 1; + if (keys < 0) { + throw generatorError("too many keys in tableswitch"); + } + delta = 1; + } else { + keys = bcs.getInt(alignedBci + 4); + if (keys < 0) { + throw generatorError("number of keys in lookupswitch less than 0"); + } + delta = 2; + for (int i = 0; i < (keys - 1); i++) { + int this_key = bcs.getInt(alignedBci + (2 + 2 * i) * 4); + int next_key = bcs.getInt(alignedBci + (2 + 2 * i + 2) * 4); + if (this_key >= next_key) { + throw generatorError("Bad lookupswitch instruction"); + } + } + } + int target = bci + defaultOfset; + checkJumpTarget(currentFrame, target); + for (int i = 0; i < keys; i++) { + alignedBci = RawBytecodeHelper.align(bcs.bci + 1); + target = bci + bcs.getInt(alignedBci + (3 + i * delta) * 4); + checkJumpTarget(currentFrame, target); + } + } + + private void processFieldInstructions(RawBytecodeHelper bcs) { + var desc = Util.fieldTypeSymbol(cp.entryByIndex(bcs.getIndexU2(), MemberRefEntry.class).nameAndType()); + switch (bcs.rawCode) { + case GETSTATIC -> + currentFrame.pushStack(desc); + case PUTSTATIC -> { + currentFrame.popStack(); + if (Util.isDoubleSlot(desc)) currentFrame.popStack(); + } + case GETFIELD -> { + currentFrame.popStack(); + currentFrame.pushStack(desc); + } + case PUTFIELD -> { + currentFrame.popStack(); + currentFrame.popStack(); + if (Util.isDoubleSlot(desc)) currentFrame.popStack(); + } + default -> throw new AssertionError("Should not reach here"); + } + } + + private boolean processInvokeInstructions(RawBytecodeHelper bcs, boolean inTryBlock, boolean thisUninit) { + int index = bcs.getIndexU2(); + int opcode = bcs.rawCode; + var nameAndType = opcode == INVOKEDYNAMIC + ? cp.entryByIndex(index, InvokeDynamicEntry.class).nameAndType() + : cp.entryByIndex(index, MemberRefEntry.class).nameAndType(); + String invokeMethodName = nameAndType.name().stringValue(); + var mDesc = Util.methodTypeSymbol(nameAndType); + int bci = bcs.bci; + currentFrame.decStack(Util.parameterSlots(mDesc)); + if (opcode != INVOKESTATIC && opcode != INVOKEDYNAMIC) { + if (OBJECT_INITIALIZER_NAME.equals(invokeMethodName)) { + Type type = currentFrame.popStack(); + if (type == Type.UNITIALIZED_THIS_TYPE) { + if (inTryBlock) { + processExceptionHandlerTargets(bci, true); + } + currentFrame.initializeObject(type, thisType); + thisUninit = true; + } else if (type.tag == ITEM_UNINITIALIZED) { + int new_offset = type.bci; + int new_class_index = bcs.getIndexU2Raw(new_offset + 1); + Type new_class_type = cpIndexToType(new_class_index, cp); + if (inTryBlock) { + processExceptionHandlerTargets(bci, thisUninit); + } + currentFrame.initializeObject(type, new_class_type); + } else { + throw generatorError("Bad operand type when invoking "); + } + } else { + currentFrame.popStack(); + } + } + currentFrame.pushStack(mDesc.returnType()); + return thisUninit; + } + + private Type getNewarrayType(int index) { + if (index < T_BOOLEAN || index > T_LONG) throw generatorError("Illegal newarray instruction type %d".formatted(index)); + return ARRAY_FROM_BASIC_TYPE[index]; + } + + private void processAnewarray(int index) { + currentFrame.popStack(); + currentFrame.pushStack(cpIndexToType(index, cp).toArray()); + } + + /** + * {@return the generator error with attached details} + * @param msg error message + */ + private IllegalArgumentException generatorError(String msg) { + return generatorError(msg, currentFrame.offset); + } + + /** + * {@return the generator error with attached details} + * @param msg error message + * @param offset bytecode offset where the error occurred + */ + private IllegalArgumentException generatorError(String msg, int offset) { + var sb = new StringBuilder("%s at bytecode offset %d of method %s(%s)".formatted( + msg, + offset, + methodName, + methodDesc.parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")))); + Util.dumpMethod(cp, thisType.sym(), methodName, methodDesc, isStatic ? ACC_STATIC : 0, bytecode, sb::append); + return new IllegalArgumentException(sb.toString()); + } + + /** + * Performs detection of mandatory stack map frames offsets + * in a single bytecode traversing pass + * @return java.lang.BitSet of detected frames offsets + */ + private BitSet detectFrameOffsets() { + var offsets = new BitSet() { + @Override + public void set(int i) { + if (i < 0 || i >= bytecode.capacity()) throw new IllegalArgumentException(); + super.set(i); + } + }; + RawBytecodeHelper bcs = new RawBytecodeHelper(bytecode); + boolean no_control_flow = false; + int opcode, bci = 0; + while (!bcs.isLastBytecode()) try { + opcode = bcs.rawNext(); + bci = bcs.bci; + if (no_control_flow) { + offsets.set(bci); + } + no_control_flow = switch (opcode) { + case GOTO -> { + offsets.set(bcs.dest()); + yield true; + } + case GOTO_W -> { + offsets.set(bcs.destW()); + yield true; + } + case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, + IF_ICMPGT, IF_ICMPLE, IFEQ, IFNE, + IFLT, IFGE, IFGT, IFLE, IF_ACMPEQ, + IF_ACMPNE , IFNULL , IFNONNULL -> { + offsets.set(bcs.dest()); + yield false; + } + case TABLESWITCH, LOOKUPSWITCH -> { + int aligned_bci = RawBytecodeHelper.align(bci + 1); + int default_ofset = bcs.getInt(aligned_bci); + int keys, delta; + if (bcs.rawCode == TABLESWITCH) { + int low = bcs.getInt(aligned_bci + 4); + int high = bcs.getInt(aligned_bci + 2 * 4); + keys = high - low + 1; + delta = 1; + } else { + keys = bcs.getInt(aligned_bci + 4); + delta = 2; + } + offsets.set(bci + default_ofset); + for (int i = 0; i < keys; i++) { + offsets.set(bci + bcs.getInt(aligned_bci + (3 + i * delta) * 4)); + } + yield true; + } + case IRETURN, LRETURN, FRETURN, DRETURN, + ARETURN, RETURN, ATHROW -> true; + default -> false; + }; + } catch (IllegalArgumentException iae) { + throw generatorError("Detected branch target out of bytecode range", bci); + } + for (var exhandler : rawHandlers) try { + offsets.set(exhandler.handler()); + } catch (IllegalArgumentException iae) { + if (!filterDeadLabels) + throw generatorError("Detected exception handler out of bytecode range"); + } + return offsets; + } + + private final class Frame { + + int offset; + int localsSize, stackSize; + int flags; + int frameMaxStack = 0, frameMaxLocals = 0; + boolean dirty = false; + boolean localsChanged = false; + + private final ClassHierarchyImpl classHierarchy; + + private Type[] locals, stack; + + Frame(ClassHierarchyImpl classHierarchy) { + this(-1, 0, 0, 0, null, null, classHierarchy); + } + + Frame(int offset, ClassHierarchyImpl classHierarchy) { + this(offset, -1, 0, 0, null, null, classHierarchy); + } + + Frame(int offset, int flags, int locals_size, int stack_size, Type[] locals, Type[] stack, ClassHierarchyImpl classHierarchy) { + this.offset = offset; + this.localsSize = locals_size; + this.stackSize = stack_size; + this.flags = flags; + this.locals = locals; + this.stack = stack; + this.classHierarchy = classHierarchy; + } + + @Override + public String toString() { + return (dirty ? "frame* @" : "frame @") + offset + " with locals " + (locals == null ? "[]" : Arrays.asList(locals).subList(0, localsSize)) + " and stack " + (stack == null ? "[]" : Arrays.asList(stack).subList(0, stackSize)); + } + + Frame pushStack(ClassDesc desc) { + return switch (desc.descriptorString().charAt(0)) { + case 'J' -> + pushStack(Type.LONG_TYPE, Type.LONG2_TYPE); + case 'D' -> + pushStack(Type.DOUBLE_TYPE, Type.DOUBLE2_TYPE); + case 'I', 'Z', 'B', 'C', 'S' -> + pushStack(Type.INTEGER_TYPE); + case 'F' -> + pushStack(Type.FLOAT_TYPE); + case 'V' -> + this; + default -> + pushStack(Type.referenceType(desc)); + }; + } + + Frame pushStack(Type type) { + checkStack(stackSize); + stack[stackSize++] = type; + return this; + } + + Frame pushStack(Type type1, Type type2) { + checkStack(stackSize + 1); + stack[stackSize++] = type1; + stack[stackSize++] = type2; + return this; + } + + Type popStack() { + if (stackSize < 1) throw generatorError("Operand stack underflow"); + return stack[--stackSize]; + } + + Frame decStack(int size) { + stackSize -= size; + if (stackSize < 0) throw generatorError("Operand stack underflow"); + return this; + } + + Frame frameInExceptionHandler(int flags, Type excType) { + return new Frame(offset, flags, localsSize, 1, locals, new Type[] {excType}, classHierarchy); + } + + void initializeObject(Type old_object, Type new_object) { + int i; + for (i = 0; i < localsSize; i++) { + if (locals[i].equals(old_object)) { + locals[i] = new_object; + localsChanged = true; + } + } + for (i = 0; i < stackSize; i++) { + if (stack[i].equals(old_object)) { + stack[i] = new_object; + } + } + if (old_object == Type.UNITIALIZED_THIS_TYPE) { + flags = 0; + } + } + + Frame checkLocal(int index) { + if (index >= frameMaxLocals) frameMaxLocals = index + 1; + if (locals == null) { + locals = new Type[index + FRAME_DEFAULT_CAPACITY]; + Arrays.fill(locals, Type.TOP_TYPE); + } else if (index >= locals.length) { + int current = locals.length; + locals = Arrays.copyOf(locals, index + FRAME_DEFAULT_CAPACITY); + Arrays.fill(locals, current, locals.length, Type.TOP_TYPE); + } + return this; + } + + private void checkStack(int index) { + if (index >= frameMaxStack) frameMaxStack = index + 1; + if (stack == null) { + stack = new Type[index + FRAME_DEFAULT_CAPACITY]; + Arrays.fill(stack, Type.TOP_TYPE); + } else if (index >= stack.length) { + int current = stack.length; + stack = Arrays.copyOf(stack, index + FRAME_DEFAULT_CAPACITY); + Arrays.fill(stack, current, stack.length, Type.TOP_TYPE); + } + } + + private void setLocalRawInternal(int index, Type type) { + checkLocal(index); + localsChanged |= !type.equals(locals[index]); + locals[index] = type; + } + + void setLocalsFromArg(String name, MethodTypeDesc methodDesc, boolean isStatic, Type thisKlass) { + int localsSize = 0; + // Pre-emptively create a locals array that encompass all parameter slots + checkLocal(methodDesc.parameterCount() + (isStatic ? -1 : 0)); + if (!isStatic) { + localsSize++; + if (OBJECT_INITIALIZER_NAME.equals(name) && !CD_Object.equals(thisKlass.sym)) { + setLocal(0, Type.UNITIALIZED_THIS_TYPE); + flags |= FLAG_THIS_UNINIT; + } else { + setLocalRawInternal(0, thisKlass); + } + } + for (int i = 0; i < methodDesc.parameterCount(); i++) { + var desc = methodDesc.parameterType(i); + if (!desc.isPrimitive()) { + setLocalRawInternal(localsSize++, Type.referenceType(desc)); + } else switch (desc.descriptorString().charAt(0)) { + case 'J' -> { + setLocalRawInternal(localsSize++, Type.LONG_TYPE); + setLocalRawInternal(localsSize++, Type.LONG2_TYPE); + } + case 'D' -> { + setLocalRawInternal(localsSize++, Type.DOUBLE_TYPE); + setLocalRawInternal(localsSize++, Type.DOUBLE2_TYPE); + } + case 'I', 'Z', 'B', 'C', 'S' -> + setLocalRawInternal(localsSize++, Type.INTEGER_TYPE); + case 'F' -> + setLocalRawInternal(localsSize++, Type.FLOAT_TYPE); + default -> throw new AssertionError("Should not reach here"); + } + } + this.localsSize = localsSize; + } + + void copyFrom(Frame src) { + if (locals != null && src.localsSize < locals.length) Arrays.fill(locals, src.localsSize, locals.length, Type.TOP_TYPE); + localsSize = src.localsSize; + checkLocal(src.localsSize - 1); + if (src.localsSize > 0) System.arraycopy(src.locals, 0, locals, 0, src.localsSize); + if (stack != null && src.stackSize < stack.length) Arrays.fill(stack, src.stackSize, stack.length, Type.TOP_TYPE); + stackSize = src.stackSize; + checkStack(src.stackSize - 1); + if (src.stackSize > 0) System.arraycopy(src.stack, 0, stack, 0, src.stackSize); + flags = src.flags; + localsChanged = true; + } + + void checkAssignableTo(Frame target) { + if (target.flags == -1) { + target.locals = locals == null ? null : Arrays.copyOf(locals, localsSize); + target.localsSize = localsSize; + target.stack = stack == null ? null : Arrays.copyOf(stack, stackSize); + target.stackSize = stackSize; + target.flags = flags; + target.dirty = true; + } else { + if (target.localsSize > localsSize) { + target.localsSize = localsSize; + target.dirty = true; + } + for (int i = 0; i < target.localsSize; i++) { + merge(locals[i], target.locals, i, target); + } + if (stackSize != target.stackSize) { + throw generatorError("Stack size mismatch"); + } + for (int i = 0; i < target.stackSize; i++) { + if (merge(stack[i], target.stack, i, target) == Type.TOP_TYPE) { + throw generatorError("Stack content mismatch"); + } + } + } + } + + private Type getLocalRawInternal(int index) { + checkLocal(index); + return locals[index]; + } + + Type getLocal(int index) { + Type ret = getLocalRawInternal(index); + if (index >= localsSize) { + localsSize = index + 1; + } + return ret; + } + + void setLocal(int index, Type type) { + Type old = getLocalRawInternal(index); + if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { + setLocalRawInternal(index + 1, Type.TOP_TYPE); + } + if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { + setLocalRawInternal(index - 1, Type.TOP_TYPE); + } + setLocalRawInternal(index, type); + if (index >= localsSize) { + localsSize = index + 1; + } + } + + void setLocal2(int index, Type type1, Type type2) { + Type old = getLocalRawInternal(index + 1); + if (old == Type.DOUBLE_TYPE || old == Type.LONG_TYPE) { + setLocalRawInternal(index + 2, Type.TOP_TYPE); + } + old = getLocalRawInternal(index); + if (old == Type.DOUBLE2_TYPE || old == Type.LONG2_TYPE) { + setLocalRawInternal(index - 1, Type.TOP_TYPE); + } + setLocalRawInternal(index, type1); + setLocalRawInternal(index + 1, type2); + if (index >= localsSize - 1) { + localsSize = index + 2; + } + } + + private Type merge(Type me, Type[] toTypes, int i, Frame target) { + var to = toTypes[i]; + var newTo = to.mergeFrom(me, classHierarchy); + if (to != newTo && !to.equals(newTo)) { + toTypes[i] = newTo; + target.dirty = true; + } + return newTo; + } + + private static int trimAndCompress(Type[] types, int count) { + while (count > 0 && types[count - 1] == Type.TOP_TYPE) count--; + int compressed = 0; + for (int i = 0; i < count; i++) { + if (!types[i].isCategory2_2nd()) { + types[compressed++] = types[i]; + } + } + return compressed; + } + + void trimAndCompress() { + localsSize = trimAndCompress(locals, localsSize); + stackSize = trimAndCompress(stack, stackSize); + } + + private static boolean equals(Type[] l1, Type[] l2, int commonSize) { + if (l1 == null || l2 == null) return commonSize == 0; + return Arrays.equals(l1, 0, commonSize, l2, 0, commonSize); + } + + void writeTo(BufWriter out, Frame prevFrame, ConstantPoolBuilder cp) { + int offsetDelta = offset - prevFrame.offset - 1; + if (stackSize == 0) { + int commonLocalsSize = localsSize > prevFrame.localsSize ? prevFrame.localsSize : localsSize; + int diffLocalsSize = localsSize - prevFrame.localsSize; + if (-3 <= diffLocalsSize && diffLocalsSize <= 3 && equals(locals, prevFrame.locals, commonLocalsSize)) { + if (diffLocalsSize == 0 && offsetDelta < 64) { //same frame + out.writeU1(offsetDelta); + } else { //chop, same extended or append frame + out.writeU1(251 + diffLocalsSize); + out.writeU2(offsetDelta); + for (int i=commonLocalsSize; i + from == INTEGER_TYPE ? this : TOP_TYPE; + default -> + isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; + }; + } + } + + Type mergeComponentFrom(Type from, ClassHierarchyImpl context) { + if (this == TOP_TYPE || this == from || equals(from)) { + return this; + } else { + return switch (tag) { + case ITEM_BOOLEAN, ITEM_BYTE, ITEM_CHAR, ITEM_SHORT -> + TOP_TYPE; + default -> + isReference() && from.isReference() ? mergeReferenceFrom(from, context) : TOP_TYPE; + }; + } + } + + private static final ClassDesc CD_Cloneable = ClassDesc.of("java.lang.Cloneable"); + private static final ClassDesc CD_Serializable = ClassDesc.of("java.io.Serializable"); + + private Type mergeReferenceFrom(Type from, ClassHierarchyImpl context) { + if (from == NULL_TYPE) { + return this; + } else if (this == NULL_TYPE) { + return from; + } else if (sym.equals(from.sym)) { + return this; + } else if (isObject()) { + if (CD_Object.equals(sym)) { + return this; + } + if (context.isInterface(sym)) { + if (!from.isArray() || CD_Cloneable.equals(sym) || CD_Serializable.equals(sym)) { + return this; + } + } else if (from.isObject()) { + var anc = context.commonAncestor(sym, from.sym); + return anc == null ? this : Type.referenceType(anc); + } + } else if (isArray() && from.isArray()) { + Type compThis = getComponent(); + Type compFrom = from.getComponent(); + if (compThis != TOP_TYPE && compFrom != TOP_TYPE) { + return compThis.mergeComponentFrom(compFrom, context).toArray(); + } + } + return OBJECT_TYPE; + } + + Type toArray() { + return switch (tag) { + case ITEM_BOOLEAN -> BOOLEAN_ARRAY_TYPE; + case ITEM_BYTE -> BYTE_ARRAY_TYPE; + case ITEM_CHAR -> CHAR_ARRAY_TYPE; + case ITEM_SHORT -> SHORT_ARRAY_TYPE; + case ITEM_INTEGER -> INT_ARRAY_TYPE; + case ITEM_LONG -> LONG_ARRAY_TYPE; + case ITEM_FLOAT -> FLOAT_ARRAY_TYPE; + case ITEM_DOUBLE -> DOUBLE_ARRAY_TYPE; + case ITEM_OBJECT -> Type.referenceType(sym.arrayType()); + default -> OBJECT_TYPE; + }; + } + + Type getComponent() { + if (isArray()) { + var comp = sym.componentType(); + if (comp.isPrimitive()) { + return switch (comp.descriptorString().charAt(0)) { + case 'Z' -> Type.BOOLEAN_TYPE; + case 'B' -> Type.BYTE_TYPE; + case 'C' -> Type.CHAR_TYPE; + case 'S' -> Type.SHORT_TYPE; + case 'I' -> Type.INTEGER_TYPE; + case 'J' -> Type.LONG_TYPE; + case 'F' -> Type.FLOAT_TYPE; + case 'D' -> Type.DOUBLE_TYPE; + default -> Type.TOP_TYPE; + }; + } + return Type.referenceType(comp); + } + return Type.TOP_TYPE; + } + + void writeTo(BufWriter bw, ConstantPoolBuilder cp) { + bw.writeU1(tag); + switch (tag) { + case ITEM_OBJECT -> + bw.writeU2(cp.classEntry(sym).index()); + case ITEM_UNINITIALIZED -> + bw.writeU2(bci); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SuperclassImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/SuperclassImpl.class new file mode 100644 index 00000000..ef004e70 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/SuperclassImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/SuperclassImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/SuperclassImpl.java new file mode 100644 index 00000000..2e93c9b9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/SuperclassImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.Superclass; + +import static java.util.Objects.requireNonNull; + +public final class SuperclassImpl + extends AbstractElement + implements Superclass { + private final ClassEntry superclassEntry; + + public SuperclassImpl(ClassEntry superclassEntry) { + requireNonNull(superclassEntry); + this.superclassEntry = superclassEntry; + } + + @Override + public ClassEntry superclassEntry() { + return superclassEntry; + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.setSuperclass(superclassEntry); + } + + @Override + public String toString() { + return String.format("Superclass[superclassEntry=%s]", superclassEntry.name().stringValue()); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$CatchTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$CatchTargetImpl.class new file mode 100644 index 00000000..d9d2004e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$CatchTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$EmptyTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$EmptyTargetImpl.class new file mode 100644 index 00000000..6d3294d7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$EmptyTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$FormalParameterTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$FormalParameterTargetImpl.class new file mode 100644 index 00000000..1bcc8113 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$FormalParameterTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$LocalVarTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$LocalVarTargetImpl.class new file mode 100644 index 00000000..8c1ca4fc Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$LocalVarTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$LocalVarTargetInfoImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$LocalVarTargetInfoImpl.class new file mode 100644 index 00000000..8ed4f21b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$LocalVarTargetInfoImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$OffsetTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$OffsetTargetImpl.class new file mode 100644 index 00000000..f1fd0af9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$OffsetTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$SupertypeTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$SupertypeTargetImpl.class new file mode 100644 index 00000000..40175e8a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$SupertypeTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$ThrowsTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$ThrowsTargetImpl.class new file mode 100644 index 00000000..c8d18c30 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$ThrowsTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeArgumentTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeArgumentTargetImpl.class new file mode 100644 index 00000000..9c2f59a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeArgumentTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeParameterBoundTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeParameterBoundTargetImpl.class new file mode 100644 index 00000000..2973c60f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeParameterBoundTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeParameterTargetImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeParameterTargetImpl.class new file mode 100644 index 00000000..5b610d78 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl$TypeParameterTargetImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl.class new file mode 100644 index 00000000..00fa9004 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl.java new file mode 100644 index 00000000..3377e3eb --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/TargetInfoImpl.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.List; +import java.util.Objects; +import java.lang.classfile.Label; +import java.lang.classfile.TypeAnnotation.*; +import static java.lang.classfile.ClassFile.*; +import static java.util.Objects.requireNonNull; + +public final class TargetInfoImpl { + + private TargetInfoImpl() { + } + + private static TargetType checkValid(TargetType targetType, int rangeFrom, int rangeTo) { + Objects.requireNonNull(targetType); + if (targetType.targetTypeValue() < rangeFrom || targetType.targetTypeValue() > rangeTo) + throw new IllegalArgumentException("Wrong target type specified " + targetType); + return targetType; + } + + public record TypeParameterTargetImpl(TargetType targetType, int typeParameterIndex) + implements TypeParameterTarget { + + public TypeParameterTargetImpl(TargetType targetType, int typeParameterIndex) { + this.targetType = checkValid(targetType, TAT_CLASS_TYPE_PARAMETER, TAT_METHOD_TYPE_PARAMETER); + this.typeParameterIndex = typeParameterIndex; + } + } + + public record SupertypeTargetImpl(int supertypeIndex) implements SupertypeTarget { + @Override + public TargetType targetType() { + return TargetType.CLASS_EXTENDS; + } + } + + public record TypeParameterBoundTargetImpl(TargetType targetType, int typeParameterIndex, int boundIndex) + implements TypeParameterBoundTarget { + + public TypeParameterBoundTargetImpl(TargetType targetType, int typeParameterIndex, int boundIndex) { + this.targetType = checkValid(targetType, TAT_CLASS_TYPE_PARAMETER_BOUND, TAT_METHOD_TYPE_PARAMETER_BOUND); + this.typeParameterIndex = typeParameterIndex; + this.boundIndex = boundIndex; + } + } + + public record EmptyTargetImpl(TargetType targetType) implements EmptyTarget { + + public EmptyTargetImpl(TargetType targetType) { + this.targetType = checkValid(targetType, TAT_FIELD, TAT_METHOD_RECEIVER); + } + } + + public record FormalParameterTargetImpl(int formalParameterIndex) implements FormalParameterTarget { + @Override + public TargetType targetType() { + return TargetType.METHOD_FORMAL_PARAMETER; + } + } + + public record ThrowsTargetImpl(int throwsTargetIndex) implements ThrowsTarget { + @Override + public TargetType targetType() { + return TargetType.THROWS; + } + } + + public record LocalVarTargetImpl(TargetType targetType, List table) + implements LocalVarTarget { + + public LocalVarTargetImpl(TargetType targetType, List table) { + this.targetType = checkValid(targetType, TAT_LOCAL_VARIABLE, TAT_RESOURCE_VARIABLE); + this.table = List.copyOf(table); + } + @Override + public int size() { + return 2 + 6 * table.size(); + } + } + + public record LocalVarTargetInfoImpl(Label startLabel, Label endLabel, int index) + implements LocalVarTargetInfo { + + public LocalVarTargetInfoImpl { + requireNonNull(startLabel); + requireNonNull(endLabel); + } + } + + public record CatchTargetImpl(int exceptionTableIndex) implements CatchTarget { + @Override + public TargetType targetType() { + return TargetType.EXCEPTION_PARAMETER; + } + } + + public record OffsetTargetImpl(TargetType targetType, Label target) implements OffsetTarget { + + public OffsetTargetImpl(TargetType targetType, Label target) { + this.targetType = checkValid(targetType, TAT_INSTANCEOF, TAT_METHOD_REFERENCE); + this.target = requireNonNull(target); + } + } + + public record TypeArgumentTargetImpl(TargetType targetType, Label target, int typeArgumentIndex) + implements TypeArgumentTarget { + + public TypeArgumentTargetImpl(TargetType targetType, Label target, int typeArgumentIndex) { + this.targetType = checkValid(targetType, TAT_CAST, TAT_METHOD_REFERENCE_TYPE_ARGUMENT); + this.target = requireNonNull(target); + this.typeArgumentIndex = typeArgumentIndex; + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TemporaryConstantPool.class b/tests/test_data/std/jdk/internal/classfile/impl/TemporaryConstantPool.class new file mode 100644 index 00000000..b6c6ca5a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TemporaryConstantPool.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TemporaryConstantPool.java b/tests/test_data/std/jdk/internal/classfile/impl/TemporaryConstantPool.java new file mode 100644 index 00000000..7b15489a --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/TemporaryConstantPool.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.*; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantDynamicEntry; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.DoubleEntry; +import java.lang.classfile.constantpool.FieldRefEntry; +import java.lang.classfile.constantpool.FloatEntry; +import java.lang.classfile.constantpool.IntegerEntry; +import java.lang.classfile.constantpool.InterfaceMethodRefEntry; +import java.lang.classfile.constantpool.InvokeDynamicEntry; +import java.lang.classfile.constantpool.LoadableConstantEntry; +import java.lang.classfile.constantpool.LongEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.constantpool.MethodHandleEntry; +import java.lang.classfile.constantpool.MethodRefEntry; +import java.lang.classfile.constantpool.MethodTypeEntry; +import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.PackageEntry; +import java.lang.classfile.constantpool.PoolEntry; +import java.lang.classfile.constantpool.StringEntry; +import java.lang.classfile.constantpool.Utf8Entry; + +import java.lang.constant.MethodTypeDesc; +import java.util.List; + +public final class TemporaryConstantPool implements ConstantPoolBuilder { + + private TemporaryConstantPool() {} + + public static final TemporaryConstantPool INSTANCE = new TemporaryConstantPool(); + + @Override + public Utf8Entry utf8Entry(String s) { + return new AbstractPoolEntry.Utf8EntryImpl(this, -1, s); + } + + @Override + public IntegerEntry intEntry(int value) { + return new AbstractPoolEntry.IntegerEntryImpl(this, -1, value); + } + + @Override + public FloatEntry floatEntry(float value) { + return new AbstractPoolEntry.FloatEntryImpl(this, -1, value); + } + + @Override + public LongEntry longEntry(long value) { + return new AbstractPoolEntry.LongEntryImpl(this, -1, value); + } + + @Override + public DoubleEntry doubleEntry(double value) { + return new AbstractPoolEntry.DoubleEntryImpl(this, -1, value); + } + + @Override + public ClassEntry classEntry(Utf8Entry name) { + return new AbstractPoolEntry.ClassEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) name); + } + + @Override + public PackageEntry packageEntry(Utf8Entry name) { + return new AbstractPoolEntry.PackageEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) name); + } + + @Override + public ModuleEntry moduleEntry(Utf8Entry name) { + return new AbstractPoolEntry.ModuleEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) name); + } + + @Override + public NameAndTypeEntry nameAndTypeEntry(Utf8Entry nameEntry, Utf8Entry typeEntry) { + return new AbstractPoolEntry.NameAndTypeEntryImpl(this, -3, + (AbstractPoolEntry.Utf8EntryImpl) nameEntry, + (AbstractPoolEntry.Utf8EntryImpl) typeEntry); + } + + @Override + public FieldRefEntry fieldRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new AbstractPoolEntry.FieldRefEntryImpl(this, -3, + (AbstractPoolEntry.ClassEntryImpl) owner, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + } + + @Override + public MethodRefEntry methodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new AbstractPoolEntry.MethodRefEntryImpl(this, -3, + (AbstractPoolEntry.ClassEntryImpl) owner, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + } + + @Override + public InterfaceMethodRefEntry interfaceMethodRefEntry(ClassEntry owner, NameAndTypeEntry nameAndType) { + return new AbstractPoolEntry.InterfaceMethodRefEntryImpl(this, -3, + (AbstractPoolEntry.ClassEntryImpl) owner, + (AbstractPoolEntry.NameAndTypeEntryImpl) nameAndType); + } + + @Override + public MethodTypeEntry methodTypeEntry(MethodTypeDesc descriptor) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodTypeEntry methodTypeEntry(Utf8Entry descriptor) { + throw new UnsupportedOperationException(); + } + + @Override + public MethodHandleEntry methodHandleEntry(int refKind, MemberRefEntry reference) { + throw new UnsupportedOperationException(); + } + + @Override + public InvokeDynamicEntry invokeDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, NameAndTypeEntry nameAndType) { + throw new UnsupportedOperationException(); + } + + @Override + public ConstantDynamicEntry constantDynamicEntry(BootstrapMethodEntry bootstrapMethodEntry, NameAndTypeEntry nameAndType) { + throw new UnsupportedOperationException(); + } + + @Override + public StringEntry stringEntry(Utf8Entry utf8) { + return new AbstractPoolEntry.StringEntryImpl(this, -2, (AbstractPoolEntry.Utf8EntryImpl) utf8); + } + + @Override + public BootstrapMethodEntry bsmEntry(MethodHandleEntry methodReference, List arguments) { + throw new UnsupportedOperationException(); + } + + @Override + public PoolEntry entryByIndex(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + throw new UnsupportedOperationException(); + } + + @Override + public T entryByIndex(int index, Class cls) { + throw new UnsupportedOperationException(); + } + + @Override + public BootstrapMethodEntry bootstrapMethodEntry(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public int bootstrapMethodCount() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean canWriteDirect(ConstantPool constantPool) { + return false; + } + + @Override + public boolean writeBootstrapMethods(BufWriter buf) { + throw new UnsupportedOperationException(); + } + + @Override + public void writeTo(BufWriter buf) { + throw new UnsupportedOperationException(); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TerminalCodeBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/TerminalCodeBuilder.class new file mode 100644 index 00000000..2f5b9aba Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TerminalCodeBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TerminalCodeBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/TerminalCodeBuilder.java new file mode 100644 index 00000000..6e3ca516 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/TerminalCodeBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeBuilder; + +public sealed interface TerminalCodeBuilder extends CodeBuilder, LabelContext + permits DirectCodeBuilder, BufferedCodeBuilder { + int curTopLocal(); +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TerminalFieldBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/TerminalFieldBuilder.class new file mode 100644 index 00000000..ecd87de5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TerminalFieldBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TerminalFieldBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/TerminalFieldBuilder.java new file mode 100644 index 00000000..b9fbf7fe --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/TerminalFieldBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.FieldBuilder; + +public sealed interface TerminalFieldBuilder + extends FieldBuilder + permits DirectFieldBuilder, BufferedFieldBuilder { +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TerminalMethodBuilder.class b/tests/test_data/std/jdk/internal/classfile/impl/TerminalMethodBuilder.class new file mode 100644 index 00000000..97ec7f8e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TerminalMethodBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TerminalMethodBuilder.java b/tests/test_data/std/jdk/internal/classfile/impl/TerminalMethodBuilder.java new file mode 100644 index 00000000..636b12a9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/TerminalMethodBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.classfile.CodeModel; +import java.lang.classfile.MethodBuilder; + +public sealed interface TerminalMethodBuilder + extends MethodBuilder + permits BufferedMethodBuilder, DirectMethodBuilder { + BufferedCodeBuilder bufferedCodeBuilder(CodeModel original); +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedClassTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedClassTransform.class new file mode 100644 index 00000000..767d8723 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedClassTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedCodeTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedCodeTransform.class new file mode 100644 index 00000000..38a65584 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedCodeTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedFieldTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedFieldTransform.class new file mode 100644 index 00000000..9f522c84 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedFieldTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedMethodTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedMethodTransform.class new file mode 100644 index 00000000..b1d907c6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ChainedMethodTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ClassFieldTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ClassFieldTransform.class new file mode 100644 index 00000000..7edc017d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ClassFieldTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ClassMethodTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ClassMethodTransform.class new file mode 100644 index 00000000..5d801677 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ClassMethodTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$MethodCodeTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$MethodCodeTransform.class new file mode 100644 index 00000000..b1a07cf5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$MethodCodeTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ResolvedTransformImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ResolvedTransformImpl.class new file mode 100644 index 00000000..01495789 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$ResolvedTransformImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierClassTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierClassTransform.class new file mode 100644 index 00000000..a70df56d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierClassTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierCodeTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierCodeTransform.class new file mode 100644 index 00000000..0f5a5201 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierCodeTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierFieldTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierFieldTransform.class new file mode 100644 index 00000000..de5af3ab Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierFieldTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierMethodTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierMethodTransform.class new file mode 100644 index 00000000..c6d5dbf3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$SupplierMethodTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedClassTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedClassTransform.class new file mode 100644 index 00000000..d3d6e78d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedClassTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedCodeTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedCodeTransform.class new file mode 100644 index 00000000..074b229a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedCodeTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedFieldTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedFieldTransform.class new file mode 100644 index 00000000..467c35f0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedFieldTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedMethodTransform.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedMethodTransform.class new file mode 100644 index 00000000..ccc52437 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl$UnresolvedMethodTransform.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl.class new file mode 100644 index 00000000..348f218b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl.java new file mode 100644 index 00000000..1677d421 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/TransformImpl.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.ClassFileElement; +import java.lang.classfile.ClassFileTransform; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.CodeElement; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CodeTransform; +import java.lang.classfile.FieldBuilder; +import java.lang.classfile.FieldElement; +import java.lang.classfile.FieldModel; +import java.lang.classfile.FieldTransform; +import java.lang.classfile.MethodBuilder; +import java.lang.classfile.MethodElement; +import java.lang.classfile.MethodModel; +import java.lang.classfile.MethodTransform; + +public class TransformImpl { + // ClassTransform + + private TransformImpl() { + } + + private static Runnable chainRunnable(Runnable a, Runnable b) { + return () -> { a.run(); b.run(); }; + } + + private static final Runnable NOTHING = () -> { }; + + interface UnresolvedClassTransform extends ClassTransform { + @Override + default void accept(ClassBuilder builder, ClassElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(ClassBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(ClassBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ResolvedTransformImpl(Consumer consumer, + Runnable endHandler, + Runnable startHandler) + implements ClassFileTransform.ResolvedTransform { + + public ResolvedTransformImpl(Consumer consumer) { + this(consumer, NOTHING, NOTHING); + } + } + + public record ChainedClassTransform(ClassTransform t, + ClassTransform next) + implements UnresolvedClassTransform { + @Override + public ResolvedTransformImpl resolve(ClassBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + ClassBuilder chainedBuilder = new ChainedClassBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierClassTransform(Supplier supplier) + implements UnresolvedClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return supplier.get().resolve(builder); + } + } + + public record ClassMethodTransform(MethodTransform transform, + Predicate filter) + implements UnresolvedClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return new ResolvedTransformImpl<>(ce -> { + if (ce instanceof MethodModel mm && filter.test(mm)) + builder.transformMethod(mm, transform); + else + builder.with(ce); + }); + } + + @Override + public ClassTransform andThen(ClassTransform next) { + if (next instanceof ClassMethodTransform cmt) + return new ClassMethodTransform(transform.andThen(cmt.transform), + mm -> filter.test(mm) && cmt.filter.test(mm)); + else + return UnresolvedClassTransform.super.andThen(next); + } + } + + public record ClassFieldTransform(FieldTransform transform, + Predicate filter) + implements UnresolvedClassTransform { + @Override + public ResolvedTransform resolve(ClassBuilder builder) { + return new ResolvedTransformImpl<>(ce -> { + if (ce instanceof FieldModel fm && filter.test(fm)) + builder.transformField(fm, transform); + else + builder.with(ce); + }); + } + + @Override + public ClassTransform andThen(ClassTransform next) { + if (next instanceof ClassFieldTransform cft) + return new ClassFieldTransform(transform.andThen(cft.transform), + mm -> filter.test(mm) && cft.filter.test(mm)); + else + return UnresolvedClassTransform.super.andThen(next); + } + } + + // MethodTransform + + interface UnresolvedMethodTransform extends MethodTransform { + @Override + default void accept(MethodBuilder builder, MethodElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(MethodBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(MethodBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ChainedMethodTransform(MethodTransform t, + MethodTransform next) + implements TransformImpl.UnresolvedMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + MethodBuilder chainedBuilder = new ChainedMethodBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierMethodTransform(Supplier supplier) + implements TransformImpl.UnresolvedMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + return supplier.get().resolve(builder); + } + } + + public record MethodCodeTransform(CodeTransform xform) + implements TransformImpl.UnresolvedMethodTransform { + @Override + public ResolvedTransform resolve(MethodBuilder builder) { + return new ResolvedTransformImpl<>(me -> { + if (me instanceof CodeModel cm) { + builder.transformCode(cm, xform); + } + else { + builder.with(me); + } + }, NOTHING, NOTHING); + } + + @Override + public MethodTransform andThen(MethodTransform next) { + return (next instanceof TransformImpl.MethodCodeTransform mct) + ? new TransformImpl.MethodCodeTransform(xform.andThen(mct.xform)) + : UnresolvedMethodTransform.super.andThen(next); + + } + } + + // FieldTransform + + interface UnresolvedFieldTransform extends FieldTransform { + @Override + default void accept(FieldBuilder builder, FieldElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(FieldBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(FieldBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ChainedFieldTransform(FieldTransform t, FieldTransform next) + implements UnresolvedFieldTransform { + @Override + public ResolvedTransform resolve(FieldBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + FieldBuilder chainedBuilder = new ChainedFieldBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierFieldTransform(Supplier supplier) + implements UnresolvedFieldTransform { + @Override + public ResolvedTransform resolve(FieldBuilder builder) { + return supplier.get().resolve(builder); + } + } + + // CodeTransform + + interface UnresolvedCodeTransform extends CodeTransform { + @Override + default void accept(CodeBuilder builder, CodeElement element) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atEnd(CodeBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + + @Override + default void atStart(CodeBuilder builder) { + throw new UnsupportedOperationException("transforms must be resolved before running"); + } + } + + public record ChainedCodeTransform(CodeTransform t, CodeTransform next) + implements UnresolvedCodeTransform { + @Override + public ResolvedTransform resolve(CodeBuilder builder) { + ResolvedTransform downstream = next.resolve(builder); + CodeBuilder chainedBuilder = new ChainedCodeBuilder(builder, downstream.consumer()); + ResolvedTransform upstream = t.resolve(chainedBuilder); + return new ResolvedTransformImpl<>(upstream.consumer(), + chainRunnable(upstream.endHandler(), downstream.endHandler()), + chainRunnable(upstream.startHandler(), downstream.startHandler())); + } + } + + public record SupplierCodeTransform(Supplier supplier) + implements UnresolvedCodeTransform { + @Override + public ResolvedTransform resolve(CodeBuilder builder) { + return supplier.get().resolve(builder); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$AdHocAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$AdHocAttribute.class new file mode 100644 index 00000000..33b0bb36 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$AdHocAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$EmptyBootstrapAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$EmptyBootstrapAttribute.class new file mode 100644 index 00000000..f9796b95 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$EmptyBootstrapAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$TypePathComponentImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$TypePathComponentImpl.class new file mode 100644 index 00000000..abca17e7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$TypePathComponentImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundAnnotationDefaultAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundAnnotationDefaultAttribute.class new file mode 100644 index 00000000..53a41ce8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundAnnotationDefaultAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCharacterRangeInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCharacterRangeInfo.class new file mode 100644 index 00000000..b64b4163 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCharacterRangeInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCharacterRangeTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCharacterRangeTableAttribute.class new file mode 100644 index 00000000..10ad02e9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCharacterRangeTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCompilationIDAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCompilationIDAttribute.class new file mode 100644 index 00000000..c1aa0a49 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundCompilationIDAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundConstantValueAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundConstantValueAttribute.class new file mode 100644 index 00000000..38405509 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundConstantValueAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundDeprecatedAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundDeprecatedAttribute.class new file mode 100644 index 00000000..c009fc35 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundDeprecatedAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundEnclosingMethodAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundEnclosingMethodAttribute.class new file mode 100644 index 00000000..8ce199db Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundEnclosingMethodAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundExceptionsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundExceptionsAttribute.class new file mode 100644 index 00000000..fed9d025 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundExceptionsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundInnerClassInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundInnerClassInfo.class new file mode 100644 index 00000000..d213b36a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundInnerClassInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundInnerClassesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundInnerClassesAttribute.class new file mode 100644 index 00000000..f6c7c41f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundInnerClassesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLineNumberInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLineNumberInfo.class new file mode 100644 index 00000000..0fce583b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLineNumberInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLineNumberTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLineNumberTableAttribute.class new file mode 100644 index 00000000..65f8bf5d Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLineNumberTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableInfo.class new file mode 100644 index 00000000..5a248a5b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTableAttribute.class new file mode 100644 index 00000000..0d104d8e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTypeInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTypeInfo.class new file mode 100644 index 00000000..828a0f7f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTypeInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTypeTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTypeTableAttribute.class new file mode 100644 index 00000000..e56e7604 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundLocalVariableTypeTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundMethodParameterInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundMethodParameterInfo.class new file mode 100644 index 00000000..9f9377ef Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundMethodParameterInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundMethodParametersAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundMethodParametersAttribute.class new file mode 100644 index 00000000..a054e07e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundMethodParametersAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleAttribute.class new file mode 100644 index 00000000..9d53bf78 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleExportInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleExportInfo.class new file mode 100644 index 00000000..0ba9958f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleExportInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleHashInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleHashInfo.class new file mode 100644 index 00000000..9f85ebe6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleHashInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleHashesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleHashesAttribute.class new file mode 100644 index 00000000..65305c09 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleHashesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleMainClassAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleMainClassAttribute.class new file mode 100644 index 00000000..9d16cafe Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleMainClassAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleOpenInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleOpenInfo.class new file mode 100644 index 00000000..70f7a3a6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleOpenInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModulePackagesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModulePackagesAttribute.class new file mode 100644 index 00000000..27e951c5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModulePackagesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleProvideInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleProvideInfo.class new file mode 100644 index 00000000..22be82f3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleProvideInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleRequiresInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleRequiresInfo.class new file mode 100644 index 00000000..08a5fc7e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleRequiresInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleResolutionAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleResolutionAttribute.class new file mode 100644 index 00000000..be760d9f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleResolutionAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleTargetAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleTargetAttribute.class new file mode 100644 index 00000000..ee720858 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundModuleTargetAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundNestHostAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundNestHostAttribute.class new file mode 100644 index 00000000..58ee6478 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundNestHostAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundNestMembersAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundNestMembersAttribute.class new file mode 100644 index 00000000..56c38585 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundNestMembersAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundPermittedSubclassesAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundPermittedSubclassesAttribute.class new file mode 100644 index 00000000..57d68d71 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundPermittedSubclassesAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRecordAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRecordAttribute.class new file mode 100644 index 00000000..5d470394 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRecordAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRecordComponentInfo.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRecordComponentInfo.class new file mode 100644 index 00000000..00149a8f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRecordComponentInfo.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleAnnotationsAttribute.class new file mode 100644 index 00000000..c46cb811 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleParameterAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleParameterAnnotationsAttribute.class new file mode 100644 index 00000000..3c0e913c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleParameterAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleTypeAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleTypeAnnotationsAttribute.class new file mode 100644 index 00000000..84479496 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeInvisibleTypeAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleAnnotationsAttribute.class new file mode 100644 index 00000000..86fea551 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleParameterAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleParameterAnnotationsAttribute.class new file mode 100644 index 00000000..3fc521d2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleParameterAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleTypeAnnotationsAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleTypeAnnotationsAttribute.class new file mode 100644 index 00000000..c25b6660 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleTypeAnnotationsAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSignatureAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSignatureAttribute.class new file mode 100644 index 00000000..7ac7c2b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSignatureAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceDebugExtensionAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceDebugExtensionAttribute.class new file mode 100644 index 00000000..3d5ad75f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceDebugExtensionAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceFileAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceFileAttribute.class new file mode 100644 index 00000000..c1144767 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceFileAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceIDAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceIDAttribute.class new file mode 100644 index 00000000..909bc5cb Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceIDAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundStackMapTableAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundStackMapTableAttribute.class new file mode 100644 index 00000000..4fb1e029 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundStackMapTableAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSyntheticAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSyntheticAttribute.class new file mode 100644 index 00000000..61e110ca Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundSyntheticAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundTypeAnnotation.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundTypeAnnotation.class new file mode 100644 index 00000000..650ec761 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute$UnboundTypeAnnotation.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute.class b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute.class new file mode 100644 index 00000000..b936016e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute.java b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute.java new file mode 100644 index 00000000..70b58e42 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/UnboundAttribute.java @@ -0,0 +1,937 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import java.lang.classfile.Annotation; +import java.lang.classfile.AnnotationElement; +import java.lang.classfile.AnnotationValue; +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.Attributes; +import java.lang.classfile.BootstrapMethodEntry; +import java.lang.classfile.BufWriter; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.Label; +import java.lang.classfile.TypeAnnotation; +import java.lang.classfile.attribute.AnnotationDefaultAttribute; +import java.lang.classfile.attribute.BootstrapMethodsAttribute; +import java.lang.classfile.attribute.CharacterRangeInfo; +import java.lang.classfile.attribute.CharacterRangeTableAttribute; +import java.lang.classfile.attribute.CompilationIDAttribute; +import java.lang.classfile.attribute.ConstantValueAttribute; +import java.lang.classfile.attribute.DeprecatedAttribute; +import java.lang.classfile.attribute.EnclosingMethodAttribute; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.attribute.InnerClassInfo; +import java.lang.classfile.attribute.InnerClassesAttribute; +import java.lang.classfile.attribute.LineNumberInfo; +import java.lang.classfile.attribute.LineNumberTableAttribute; +import java.lang.classfile.attribute.LocalVariableInfo; +import java.lang.classfile.attribute.LocalVariableTableAttribute; +import java.lang.classfile.attribute.LocalVariableTypeInfo; +import java.lang.classfile.attribute.LocalVariableTypeTableAttribute; +import java.lang.classfile.attribute.MethodParameterInfo; +import java.lang.classfile.attribute.MethodParametersAttribute; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModuleExportInfo; +import java.lang.classfile.attribute.ModuleHashInfo; +import java.lang.classfile.attribute.ModuleHashesAttribute; +import java.lang.classfile.attribute.ModuleMainClassAttribute; +import java.lang.classfile.attribute.ModuleOpenInfo; +import java.lang.classfile.attribute.ModulePackagesAttribute; +import java.lang.classfile.attribute.ModuleProvideInfo; +import java.lang.classfile.attribute.ModuleRequireInfo; +import java.lang.classfile.attribute.ModuleResolutionAttribute; +import java.lang.classfile.attribute.ModuleTargetAttribute; +import java.lang.classfile.attribute.NestHostAttribute; +import java.lang.classfile.attribute.NestMembersAttribute; +import java.lang.classfile.attribute.PermittedSubclassesAttribute; +import java.lang.classfile.attribute.RecordAttribute; +import java.lang.classfile.attribute.RecordComponentInfo; +import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleParameterAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleParameterAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.SignatureAttribute; +import java.lang.classfile.attribute.SourceDebugExtensionAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.classfile.attribute.SourceIDAttribute; +import java.lang.classfile.attribute.StackMapTableAttribute; +import java.lang.classfile.attribute.StackMapFrameInfo; +import java.lang.classfile.attribute.SyntheticAttribute; +import java.lang.classfile.constantpool.ConstantValueEntry; +import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.classfile.constantpool.PackageEntry; +import java.lang.classfile.constantpool.Utf8Entry; + +public abstract sealed class UnboundAttribute> + extends AbstractElement + implements Attribute { + protected final AttributeMapper mapper; + + public UnboundAttribute(AttributeMapper mapper) { + this.mapper = mapper; + } + + @Override + public AttributeMapper attributeMapper() { + return mapper; + } + + @Override + public String attributeName() { + return mapper.name(); + } + + @Override + @SuppressWarnings("unchecked") + public void writeTo(BufWriter buf) { + mapper.writeAttribute(buf, (T) this); + } + + @Override + public void writeTo(DirectClassBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectCodeBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectMethodBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public void writeTo(DirectFieldBuilder builder) { + builder.writeAttribute(this); + } + + @Override + public String toString() { + return String.format("Attribute[name=%s]", mapper.name()); + } + public static final class UnboundConstantValueAttribute + extends UnboundAttribute + implements ConstantValueAttribute { + + private final ConstantValueEntry entry; + + public UnboundConstantValueAttribute(ConstantValueEntry entry) { + super(Attributes.constantValue()); + this.entry = entry; + } + + @Override + public ConstantValueEntry constant() { + return entry; + } + + } + + public static final class UnboundDeprecatedAttribute + extends UnboundAttribute + implements DeprecatedAttribute { + public UnboundDeprecatedAttribute() { + super(Attributes.deprecated()); + } + } + + public static final class UnboundSyntheticAttribute + extends UnboundAttribute + implements SyntheticAttribute { + public UnboundSyntheticAttribute() { + super(Attributes.synthetic()); + } + } + + public static final class UnboundSignatureAttribute + extends UnboundAttribute + implements SignatureAttribute { + private final Utf8Entry signature; + + public UnboundSignatureAttribute(Utf8Entry signature) { + super(Attributes.signature()); + this.signature = signature; + } + + @Override + public Utf8Entry signature() { + return signature; + } + } + + public static final class UnboundExceptionsAttribute + extends UnboundAttribute + implements ExceptionsAttribute { + private final List exceptions; + + public UnboundExceptionsAttribute(List exceptions) { + super(Attributes.exceptions()); + this.exceptions = List.copyOf(exceptions); + } + + @Override + public List exceptions() { + return exceptions; + } + } + + public static final class UnboundAnnotationDefaultAttribute + extends UnboundAttribute + implements AnnotationDefaultAttribute { + private final AnnotationValue annotationDefault; + + public UnboundAnnotationDefaultAttribute(AnnotationValue annotationDefault) { + super(Attributes.annotationDefault()); + this.annotationDefault = annotationDefault; + } + + @Override + public AnnotationValue defaultValue() { + return annotationDefault; + } + } + + public static final class UnboundSourceFileAttribute extends UnboundAttribute + implements SourceFileAttribute { + private final Utf8Entry sourceFile; + + public UnboundSourceFileAttribute(Utf8Entry sourceFile) { + super(Attributes.sourceFile()); + this.sourceFile = sourceFile; + } + + @Override + public Utf8Entry sourceFile() { + return sourceFile; + } + + } + + public static final class UnboundStackMapTableAttribute extends UnboundAttribute + implements StackMapTableAttribute { + private final List entries; + + public UnboundStackMapTableAttribute(List entries) { + super(Attributes.stackMapTable()); + this.entries = List.copyOf(entries); + } + + @Override + public List entries() { + return entries; + } + } + + public static final class UnboundInnerClassesAttribute + extends UnboundAttribute + implements InnerClassesAttribute { + private final List innerClasses; + + public UnboundInnerClassesAttribute(List innerClasses) { + super(Attributes.innerClasses()); + this.innerClasses = List.copyOf(innerClasses); + } + + @Override + public List classes() { + return innerClasses; + } + } + + public static final class UnboundRecordAttribute + extends UnboundAttribute + implements RecordAttribute { + private final List components; + + public UnboundRecordAttribute(List components) { + super(Attributes.record()); + this.components = List.copyOf(components); + } + + @Override + public List components() { + return components; + } + } + + public static final class UnboundEnclosingMethodAttribute + extends UnboundAttribute + implements EnclosingMethodAttribute { + private final ClassEntry classEntry; + private final NameAndTypeEntry method; + + public UnboundEnclosingMethodAttribute(ClassEntry classEntry, NameAndTypeEntry method) { + super(Attributes.enclosingMethod()); + this.classEntry = classEntry; + this.method = method; + } + + @Override + public ClassEntry enclosingClass() { + return classEntry; + } + + @Override + public Optional enclosingMethod() { + return Optional.ofNullable(method); + } + } + + public static final class UnboundMethodParametersAttribute + extends UnboundAttribute + implements MethodParametersAttribute { + private final List parameters; + + public UnboundMethodParametersAttribute(List parameters) { + super(Attributes.methodParameters()); + this.parameters = List.copyOf(parameters); + } + + @Override + public List parameters() { + return parameters; + } + } + + public static final class UnboundModuleTargetAttribute + extends UnboundAttribute + implements ModuleTargetAttribute { + final Utf8Entry moduleTarget; + + public UnboundModuleTargetAttribute(Utf8Entry moduleTarget) { + super(Attributes.moduleTarget()); + this.moduleTarget = moduleTarget; + } + + @Override + public Utf8Entry targetPlatform() { + return moduleTarget; + } + } + + public static final class UnboundModuleMainClassAttribute + extends UnboundAttribute + implements ModuleMainClassAttribute { + final ClassEntry mainClass; + + public UnboundModuleMainClassAttribute(ClassEntry mainClass) { + super(Attributes.moduleMainClass()); + this.mainClass = mainClass; + } + + @Override + public ClassEntry mainClass() { + return mainClass; + } + } + + public static final class UnboundModuleHashesAttribute + extends UnboundAttribute + implements ModuleHashesAttribute { + private final Utf8Entry algorithm; + private final List hashes; + + public UnboundModuleHashesAttribute(Utf8Entry algorithm, List hashes) { + super(Attributes.moduleHashes()); + this.algorithm = algorithm; + this.hashes = List.copyOf(hashes); + } + + @Override + public Utf8Entry algorithm() { + return algorithm; + } + + @Override + public List hashes() { + return hashes; + } + } + + public static final class UnboundModulePackagesAttribute + extends UnboundAttribute + implements ModulePackagesAttribute { + private final Collection packages; + + public UnboundModulePackagesAttribute(Collection packages) { + super(Attributes.modulePackages()); + this.packages = List.copyOf(packages); + } + + @Override + public List packages() { + return List.copyOf(packages); + } + } + + public static final class UnboundModuleResolutionAttribute + extends UnboundAttribute + implements ModuleResolutionAttribute { + private final int resolutionFlags; + + public UnboundModuleResolutionAttribute(int flags) { + super(Attributes.moduleResolution()); + resolutionFlags = flags; + } + + @Override + public int resolutionFlags() { + return resolutionFlags; + } + } + + public static final class UnboundPermittedSubclassesAttribute + extends UnboundAttribute + implements PermittedSubclassesAttribute { + private final List permittedSubclasses; + + public UnboundPermittedSubclassesAttribute(List permittedSubclasses) { + super(Attributes.permittedSubclasses()); + this.permittedSubclasses = List.copyOf(permittedSubclasses); + } + + @Override + public List permittedSubclasses() { + return permittedSubclasses; + } + } + + public static final class UnboundNestMembersAttribute + extends UnboundAttribute + implements NestMembersAttribute { + private final List memberEntries; + + public UnboundNestMembersAttribute(List memberEntries) { + super(Attributes.nestMembers()); + this.memberEntries = List.copyOf(memberEntries); + } + + @Override + public List nestMembers() { + return memberEntries; + } + } + + public static final class UnboundNestHostAttribute + extends UnboundAttribute + implements NestHostAttribute { + private final ClassEntry hostEntry; + + public UnboundNestHostAttribute(ClassEntry hostEntry) { + super(Attributes.nestHost()); + this.hostEntry = hostEntry; + } + + @Override + public ClassEntry nestHost() { + return hostEntry; + } + } + + public static final class UnboundCompilationIDAttribute + extends UnboundAttribute + implements CompilationIDAttribute { + private final Utf8Entry idEntry; + + public UnboundCompilationIDAttribute(Utf8Entry idEntry) { + super(Attributes.compilationId()); + this.idEntry = idEntry; + } + + @Override + public Utf8Entry compilationId() { + return idEntry; + } + } + + public static final class UnboundSourceIDAttribute + extends UnboundAttribute + implements SourceIDAttribute { + private final Utf8Entry idEntry; + + public UnboundSourceIDAttribute(Utf8Entry idEntry) { + super(Attributes.sourceId()); + this.idEntry = idEntry; + } + + @Override + public Utf8Entry sourceId() { + return idEntry; + } + } + + public static final class UnboundSourceDebugExtensionAttribute + extends UnboundAttribute + implements SourceDebugExtensionAttribute { + private final byte[] contents; + + public UnboundSourceDebugExtensionAttribute(byte[] contents) { + super(Attributes.sourceDebugExtension()); + this.contents = contents; + } + + @Override + public byte[] contents() { + return contents; + } + } + + public static final class UnboundCharacterRangeTableAttribute + extends UnboundAttribute + implements CharacterRangeTableAttribute { + private final List ranges; + + public UnboundCharacterRangeTableAttribute(List ranges) { + super(Attributes.characterRangeTable()); + this.ranges = List.copyOf(ranges); + } + + @Override + public List characterRangeTable() { + return ranges; + } + } + + public static final class UnboundLineNumberTableAttribute + extends UnboundAttribute + implements LineNumberTableAttribute { + private final List lines; + + public UnboundLineNumberTableAttribute(List lines) { + super(Attributes.lineNumberTable()); + this.lines = List.copyOf(lines); + } + + @Override + public List lineNumbers() { + return lines; + } + } + + public static final class UnboundLocalVariableTableAttribute + extends UnboundAttribute + implements LocalVariableTableAttribute { + private final List locals; + + public UnboundLocalVariableTableAttribute(List locals) { + super(Attributes.localVariableTable()); + this.locals = List.copyOf(locals); + } + + @Override + public List localVariables() { + return locals; + } + } + + public static final class UnboundLocalVariableTypeTableAttribute + extends UnboundAttribute + implements LocalVariableTypeTableAttribute { + private final List locals; + + public UnboundLocalVariableTypeTableAttribute(List locals) { + super(Attributes.localVariableTypeTable()); + this.locals = List.copyOf(locals); + } + + @Override + public List localVariableTypes() { + return locals; + } + } + + public static final class UnboundRuntimeVisibleAnnotationsAttribute + extends UnboundAttribute + implements RuntimeVisibleAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeVisibleAnnotationsAttribute(List elements) { + super(Attributes.runtimeVisibleAnnotations()); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public static final class UnboundRuntimeInvisibleAnnotationsAttribute + extends UnboundAttribute + implements RuntimeInvisibleAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeInvisibleAnnotationsAttribute(List elements) { + super(Attributes.runtimeInvisibleAnnotations()); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public static final class UnboundRuntimeVisibleParameterAnnotationsAttribute + extends UnboundAttribute + implements RuntimeVisibleParameterAnnotationsAttribute { + private final List> elements; + + public UnboundRuntimeVisibleParameterAnnotationsAttribute(List> elements) { + super(Attributes.runtimeVisibleParameterAnnotations()); + this.elements = List.copyOf(elements); + } + + @Override + public List> parameterAnnotations() { + return elements; + } + } + + public static final class UnboundRuntimeInvisibleParameterAnnotationsAttribute + extends UnboundAttribute + implements RuntimeInvisibleParameterAnnotationsAttribute { + private final List> elements; + + public UnboundRuntimeInvisibleParameterAnnotationsAttribute(List> elements) { + super(Attributes.runtimeInvisibleParameterAnnotations()); + this.elements = List.copyOf(elements); + } + + @Override + public List> parameterAnnotations() { + return elements; + } + } + + public static final class UnboundRuntimeVisibleTypeAnnotationsAttribute + extends UnboundAttribute + implements RuntimeVisibleTypeAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeVisibleTypeAnnotationsAttribute(List elements) { + super(Attributes.runtimeVisibleTypeAnnotations()); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public static final class UnboundRuntimeInvisibleTypeAnnotationsAttribute + extends UnboundAttribute + implements RuntimeInvisibleTypeAnnotationsAttribute { + private final List elements; + + public UnboundRuntimeInvisibleTypeAnnotationsAttribute(List elements) { + super(Attributes.runtimeInvisibleTypeAnnotations()); + this.elements = List.copyOf(elements); + } + + @Override + public List annotations() { + return elements; + } + } + + public record UnboundCharacterRangeInfo(int startPc, int endPc, + int characterRangeStart, + int characterRangeEnd, + int flags) + implements CharacterRangeInfo { } + + public record UnboundInnerClassInfo(ClassEntry innerClass, + Optional outerClass, + Optional innerName, + int flagsMask) + implements InnerClassInfo {} + + public record UnboundLineNumberInfo(int startPc, int lineNumber) + implements LineNumberInfo { } + + public record UnboundLocalVariableInfo(int startPc, int length, + Utf8Entry name, + Utf8Entry type, + int slot) + implements LocalVariableInfo { } + + public record UnboundLocalVariableTypeInfo(int startPc, int length, + Utf8Entry name, + Utf8Entry signature, + int slot) + implements LocalVariableTypeInfo { } + + public record UnboundMethodParameterInfo(Optional name, int flagsMask) + implements MethodParameterInfo {} + + public record UnboundModuleExportInfo(PackageEntry exportedPackage, + int exportsFlagsMask, + List exportsTo) + implements ModuleExportInfo { + public UnboundModuleExportInfo(PackageEntry exportedPackage, int exportsFlagsMask, + List exportsTo) { + this.exportedPackage = exportedPackage; + this.exportsFlagsMask = exportsFlagsMask; + this.exportsTo = List.copyOf(exportsTo); + } + } + + public record UnboundModuleHashInfo(ModuleEntry moduleName, + byte[] hash) implements ModuleHashInfo { } + + public record UnboundModuleOpenInfo(PackageEntry openedPackage, int opensFlagsMask, + List opensTo) + implements ModuleOpenInfo { + public UnboundModuleOpenInfo(PackageEntry openedPackage, int opensFlagsMask, + List opensTo) { + this.openedPackage = openedPackage; + this.opensFlagsMask = opensFlagsMask; + this.opensTo = List.copyOf(opensTo); + } + } + + public record UnboundModuleProvideInfo(ClassEntry provides, + List providesWith) + implements ModuleProvideInfo { + public UnboundModuleProvideInfo(ClassEntry provides, List providesWith) { + this.provides = provides; + this.providesWith = List.copyOf(providesWith); + } + } + + public record UnboundModuleRequiresInfo(ModuleEntry requires, int requiresFlagsMask, + Optional requiresVersion) + implements ModuleRequireInfo {} + + public record UnboundRecordComponentInfo(Utf8Entry name, + Utf8Entry descriptor, + List> attributes) + implements RecordComponentInfo { + public UnboundRecordComponentInfo(Utf8Entry name, Utf8Entry descriptor, List> attributes) { + this.name = name; + this.descriptor = descriptor; + this.attributes = List.copyOf(attributes); + } + } + + public record UnboundTypeAnnotation(TargetInfo targetInfo, + List targetPath, + Utf8Entry className, + List elements) implements TypeAnnotation { + + public UnboundTypeAnnotation(TargetInfo targetInfo, List targetPath, + Utf8Entry className, List elements) { + this.targetInfo = targetInfo; + this.targetPath = List.copyOf(targetPath); + this.className = className; + this.elements = List.copyOf(elements); + } + + private int labelToBci(LabelContext lr, Label label) { + //helper method to avoid NPE + if (lr == null) throw new IllegalArgumentException("Illegal targetType '%s' in TypeAnnotation outside of Code attribute".formatted(targetInfo.targetType())); + return lr.labelToBci(label); + } + + @Override + public void writeTo(BufWriter buf) { + LabelContext lr = ((BufWriterImpl) buf).labelContext(); + // target_type + buf.writeU1(targetInfo.targetType().targetTypeValue()); + + // target_info + switch (targetInfo) { + case TypeParameterTarget tpt -> buf.writeU1(tpt.typeParameterIndex()); + case SupertypeTarget st -> buf.writeU2(st.supertypeIndex()); + case TypeParameterBoundTarget tpbt -> { + buf.writeU1(tpbt.typeParameterIndex()); + buf.writeU1(tpbt.boundIndex()); + } + case EmptyTarget et -> { + // nothing to write + } + case FormalParameterTarget fpt -> buf.writeU1(fpt.formalParameterIndex()); + case ThrowsTarget tt -> buf.writeU2(tt.throwsTargetIndex()); + case LocalVarTarget lvt -> { + buf.writeU2(lvt.table().size()); + for (var e : lvt.table()) { + int startPc = labelToBci(lr, e.startLabel()); + buf.writeU2(startPc); + buf.writeU2(labelToBci(lr, e.endLabel()) - startPc); + buf.writeU2(e.index()); + } + } + case CatchTarget ct -> buf.writeU2(ct.exceptionTableIndex()); + case OffsetTarget ot -> buf.writeU2(labelToBci(lr, ot.target())); + case TypeArgumentTarget tat -> { + buf.writeU2(labelToBci(lr, tat.target())); + buf.writeU1(tat.typeArgumentIndex()); + } + } + + // target_path + buf.writeU1(targetPath().size()); + for (TypePathComponent component : targetPath()) { + buf.writeU1(component.typePathKind().tag()); + buf.writeU1(component.typeArgumentIndex()); + } + + // type_index + buf.writeIndex(className); + + // element_value_pairs + buf.writeU2(elements.size()); + for (AnnotationElement pair : elements()) { + buf.writeIndex(pair.name()); + pair.value().writeTo(buf); + } + } + } + + public record TypePathComponentImpl(TypeAnnotation.TypePathComponent.Kind typePathKind, int typeArgumentIndex) + implements TypeAnnotation.TypePathComponent {} + + public static final class UnboundModuleAttribute extends UnboundAttribute implements ModuleAttribute { + private final ModuleEntry moduleName; + private final int moduleFlags; + private final Utf8Entry moduleVersion; + private final List requires; + private final List exports; + private final List opens; + private final List uses; + private final List provides; + + public UnboundModuleAttribute(ModuleEntry moduleName, + int moduleFlags, + Utf8Entry moduleVersion, + Collection requires, + Collection exports, + Collection opens, + Collection uses, + Collection provides) + { + super(Attributes.module()); + this.moduleName = moduleName; + this.moduleFlags = moduleFlags; + this.moduleVersion = moduleVersion; + this.requires = List.copyOf(requires); + this.exports = List.copyOf(exports); + this.opens = List.copyOf(opens); + this.uses = List.copyOf(uses); + this.provides = List.copyOf(provides); + } + + @Override + public ModuleEntry moduleName() { + return moduleName; + } + + @Override + public int moduleFlagsMask() { + return moduleFlags; + } + + @Override + public Optional moduleVersion() { + return Optional.ofNullable(moduleVersion); + } + + @Override + public List requires() { + return requires; + } + + @Override + public List exports() { + return exports; + } + + @Override + public List opens() { + return opens; + } + + @Override + public List uses() { + return uses; + } + + @Override + public List provides() { + return provides; + } + } + + public abstract static non-sealed class AdHocAttribute> + extends UnboundAttribute { + + public AdHocAttribute(AttributeMapper mapper) { + super(mapper); + } + + public abstract void writeBody(BufWriter b); + + @Override + public void writeTo(BufWriter b) { + b.writeIndex(b.constantPool().utf8Entry(mapper.name())); + b.writeInt(0); + int start = b.size(); + writeBody(b); + int written = b.size() - start; + b.patchInt(start - 4, 4, written); + } + } + + public static final class EmptyBootstrapAttribute + extends UnboundAttribute + implements BootstrapMethodsAttribute { + public EmptyBootstrapAttribute() { + super(Attributes.bootstrapMethods()); + } + + @Override + public int bootstrapMethodsSize() { + return 0; + } + + @Override + public List bootstrapMethods() { + return List.of(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/Util$1.class b/tests/test_data/std/jdk/internal/classfile/impl/Util$1.class new file mode 100644 index 00000000..e6f155ef Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/Util$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/Util$2.class b/tests/test_data/std/jdk/internal/classfile/impl/Util$2.class new file mode 100644 index 00000000..d297c975 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/Util$2.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/Util.class b/tests/test_data/std/jdk/internal/classfile/impl/Util.class new file mode 100644 index 00000000..984eff4b Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/Util.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/Util.java b/tests/test_data/std/jdk/internal/classfile/impl/Util.java new file mode 100644 index 00000000..0e969272 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/Util.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.util.AbstractList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributeMapper; +import java.lang.classfile.Attributes; +import java.lang.classfile.BufWriter; +import java.lang.classfile.ClassFile; +import java.lang.classfile.Opcode; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ModuleEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.constant.ModuleDesc; +import java.lang.reflect.AccessFlag; +import jdk.internal.access.SharedSecrets; + +import static java.lang.classfile.ClassFile.ACC_STATIC; +import java.lang.classfile.attribute.CodeAttribute; +import java.lang.classfile.components.ClassPrinter; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +/** + * Helper to create and manipulate type descriptors, where type descriptors are + * represented as JVM type descriptor strings and symbols are represented as + * name strings + */ +public class Util { + + private Util() { + } + + private static final int ATTRIBUTE_STABILITY_COUNT = AttributeMapper.AttributeStability.values().length; + + public static boolean isAttributeAllowed(final Attribute attr, + final ClassFile.AttributesProcessingOption processingOption) { + return attr instanceof BoundAttribute + ? ATTRIBUTE_STABILITY_COUNT - attr.attributeMapper().stability().ordinal() > processingOption.ordinal() + : true; + } + + public static int parameterSlots(MethodTypeDesc mDesc) { + int count = 0; + for (int i = 0; i < mDesc.parameterCount(); i++) { + count += slotSize(mDesc.parameterType(i)); + } + return count; + } + + public static int[] parseParameterSlots(int flags, MethodTypeDesc mDesc) { + int[] result = new int[mDesc.parameterCount()]; + int count = ((flags & ACC_STATIC) != 0) ? 0 : 1; + for (int i = 0; i < result.length; i++) { + result[i] = count; + count += slotSize(mDesc.parameterType(i)); + } + return result; + } + + public static int maxLocals(int flags, MethodTypeDesc mDesc) { + int count = ((flags & ACC_STATIC) != 0) ? 0 : 1; + for (int i = 0; i < mDesc.parameterCount(); i++) { + count += slotSize(mDesc.parameterType(i)); + } + return count; + } + + /** + * Converts a descriptor of classes or interfaces into + * a binary name. Rejects primitive types or arrays. + * This is an inverse of {@link ClassDesc#of(String)}. + */ + public static String toBinaryName(ClassDesc cd) { + return toInternalName(cd).replace('/', '.'); + } + + public static String toInternalName(ClassDesc cd) { + var desc = cd.descriptorString(); + if (desc.charAt(0) == 'L') + return desc.substring(1, desc.length() - 1); + throw new IllegalArgumentException(desc); + } + + public static ClassDesc toClassDesc(String classInternalNameOrArrayDesc) { + return classInternalNameOrArrayDesc.charAt(0) == '[' + ? ClassDesc.ofDescriptor(classInternalNameOrArrayDesc) + : ClassDesc.ofInternalName(classInternalNameOrArrayDesc); + } + + public static List mappedList(List list, Function mapper) { + return new AbstractList<>() { + @Override + public U get(int index) { + return mapper.apply(list.get(index)); + } + + @Override + public int size() { + return list.size(); + } + }; + } + + public static List entryList(List list) { + var result = new Object[list.size()]; // null check + for (int i = 0; i < result.length; i++) { + result[i] = TemporaryConstantPool.INSTANCE.classEntry(list.get(i)); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(result); + } + + public static List moduleEntryList(List list) { + var result = new Object[list.size()]; // null check + for (int i = 0; i < result.length; i++) { + result[i] = TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(list.get(i).name())); + } + return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(result); + } + + public static void checkKind(Opcode op, Opcode.Kind k) { + if (op.kind() != k) + throw new IllegalArgumentException( + String.format("Wrong opcode kind specified; found %s(%s), expected %s", op, op.kind(), k)); + } + + public static int flagsToBits(AccessFlag.Location location, Collection flags) { + int i = 0; + for (AccessFlag f : flags) { + if (!f.locations().contains(location)) { + throw new IllegalArgumentException("unexpected flag: " + f + " use in target location: " + location); + } + i |= f.mask(); + } + return i; + } + + public static int flagsToBits(AccessFlag.Location location, AccessFlag... flags) { + int i = 0; + for (AccessFlag f : flags) { + if (!f.locations().contains(location)) { + throw new IllegalArgumentException("unexpected flag: " + f + " use in target location: " + location); + } + i |= f.mask(); + } + return i; + } + + public static boolean has(AccessFlag.Location location, int flagsMask, AccessFlag flag) { + return (flag.mask() & flagsMask) == flag.mask() && flag.locations().contains(location); + } + + public static ClassDesc fieldTypeSymbol(NameAndTypeEntry nat) { + return ((AbstractPoolEntry.NameAndTypeEntryImpl)nat).fieldTypeSymbol(); + } + + public static MethodTypeDesc methodTypeSymbol(NameAndTypeEntry nat) { + return ((AbstractPoolEntry.NameAndTypeEntryImpl)nat).methodTypeSymbol(); + } + + public static int slotSize(ClassDesc desc) { + return switch (desc.descriptorString().charAt(0)) { + case 'V' -> 0; + case 'D','J' -> 2; + default -> 1; + }; + } + + public static boolean isDoubleSlot(ClassDesc desc) { + char ch = desc.descriptorString().charAt(0); + return ch == 'D' || ch == 'J'; + } + + public static void dumpMethod(SplitConstantPool cp, + ClassDesc cls, + String methodName, + MethodTypeDesc methodDesc, + int acc, + ByteBuffer bytecode, + Consumer dump) { + + // try to dump debug info about corrupted bytecode + try { + var cc = ClassFile.of(); + var clm = cc.parse(cc.build(cp.classEntry(cls), cp, clb -> + clb.withMethod(methodName, methodDesc, acc, mb -> + ((DirectMethodBuilder)mb).writeAttribute(new UnboundAttribute.AdHocAttribute(Attributes.code()) { + @Override + public void writeBody(BufWriter b) { + b.writeU2(-1);//max stack + b.writeU2(-1);//max locals + b.writeInt(bytecode.limit()); + b.writeBytes(bytecode.array(), 0, bytecode.limit()); + b.writeU2(0);//exception handlers + b.writeU2(0);//attributes + } + })))); + ClassPrinter.toYaml(clm.methods().get(0).code().get(), ClassPrinter.Verbosity.TRACE_ALL, dump); + } catch (Error | Exception _) { + // fallback to bytecode hex dump + bytecode.rewind(); + while (bytecode.position() < bytecode.limit()) { + dump.accept("%n%04x:".formatted(bytecode.position())); + for (int i = 0; i < 16 && bytecode.position() < bytecode.limit(); i++) { + dump.accept(" %02x".formatted(bytecode.get())); + } + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1.class new file mode 100644 index 00000000..d6d35c5f Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1F.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1F.class new file mode 100644 index 00000000..10fe5dd8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1F.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1M.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1M.class new file mode 100644 index 00000000..210d6876 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier$1M.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier.class new file mode 100644 index 00000000..315ca38e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier.java b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier.java new file mode 100644 index 00000000..4a2ffd3f --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/verifier/ParserVerifier.java @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl.verifier; + +import java.lang.classfile.Annotation; +import java.lang.classfile.AnnotationValue; +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME; +import static java.lang.constant.ConstantDescs.INIT_NAME; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.lang.classfile.Attribute; +import java.lang.classfile.AttributedElement; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassModel; +import java.lang.classfile.ClassFileElement; +import java.lang.classfile.CodeModel; +import java.lang.classfile.CompoundElement; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.FieldModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.TypeAnnotation; +import java.lang.classfile.TypeKind; +import java.lang.classfile.attribute.*; +import java.lang.classfile.constantpool.*; +import java.lang.constant.ConstantDescs; +import java.lang.reflect.AccessFlag; +import java.util.Collection; +import java.util.function.Function; +import java.util.function.ToIntFunction; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.Util; + +/** + * ParserVerifier performs selected checks of the class file format according to + * {@jvms 4.8 Format Checking} + * + * @see hotspot/share/classfile/classFileParser.cpp + */ +public record ParserVerifier(ClassModel classModel) { + + List verify() { + var errors = new ArrayList(); + verifyConstantPool(errors); + verifyInterfaces(errors); + verifyFields(errors); + verifyMethods(errors); + verifyAttributes(classModel, errors); + return errors; + } + + private void verifyConstantPool(List errors) { + for (var cpe : classModel.constantPool()) { + Consumer check = c -> { + try { + c.run(); + } catch (VerifyError|Exception e) { + errors.add(new VerifyError("%s at constant pool index %d in %s".formatted(e.getMessage(), cpe.index(), toString(classModel)))); + } + }; + check.accept(switch (cpe) { + case DoubleEntry de -> de::doubleValue; + case FloatEntry fe -> fe::floatValue; + case IntegerEntry ie -> ie::intValue; + case LongEntry le -> le::longValue; + case Utf8Entry ue -> ue::stringValue; + case ConstantDynamicEntry cde -> cde::asSymbol; + case InvokeDynamicEntry ide -> ide::asSymbol; + case ClassEntry ce -> ce::asSymbol; + case StringEntry se -> se::stringValue; + case MethodHandleEntry mhe -> mhe::asSymbol; + case MethodTypeEntry mte -> mte::asSymbol; + case FieldRefEntry fre -> { + check.accept(fre.owner()::asSymbol); + check.accept(fre::typeSymbol); + yield () -> verifyFieldName(fre.name().stringValue()); + } + case InterfaceMethodRefEntry imre -> { + check.accept(imre.owner()::asSymbol); + check.accept(imre::typeSymbol); + yield () -> verifyMethodName(imre.name().stringValue()); + } + case MethodRefEntry mre -> { + check.accept(mre.owner()::asSymbol); + check.accept(mre::typeSymbol); + yield () -> verifyMethodName(mre.name().stringValue()); + } + case ModuleEntry me -> me::asSymbol; + case NameAndTypeEntry nate -> { + check.accept(nate.name()::stringValue); + yield () -> nate.type().stringValue(); + } + case PackageEntry pe -> pe::asSymbol; + }); + } + } + + private void verifyFieldName(String name) { + if (name.length() == 0 || name.chars().anyMatch(ch -> switch(ch) { + case '.', ';', '[', '/' -> true; + default -> false; + })) { + throw new VerifyError("Illegal field name %s in %s".formatted(name, toString(classModel))); + } + } + + private void verifyMethodName(String name) { + if (!name.equals(INIT_NAME) + && !name.equals(CLASS_INIT_NAME) + && (name.length() == 0 || name.chars().anyMatch(ch -> switch(ch) { + case '.', ';', '[', '/', '<', '>' -> true; + default -> false; + }))) { + throw new VerifyError("Illegal method name %s in %s".formatted(name, toString(classModel))); + } + } + + private void verifyInterfaces(List errors) { + var intfs = new HashSet(); + for (var intf : classModel.interfaces()) { + if (!intfs.add(intf)) { + errors.add(new VerifyError("Duplicate interface %s in %s".formatted(intf.asSymbol().displayName(), toString(classModel)))); + } + } + } + + private void verifyFields(List errors) { + record F(Utf8Entry name, Utf8Entry type) {}; + var fields = new HashSet(); + for (var f : classModel.fields()) try { + if (!fields.add(new F(f.fieldName(), f.fieldType()))) { + errors.add(new VerifyError("Duplicate field name %s with signature %s in %s".formatted(f.fieldName().stringValue(), f.fieldType().stringValue(), toString(classModel)))); + } + verifyFieldName(f.fieldName().stringValue()); + } catch (VerifyError ve) { + errors.add(ve); + } + } + + private void verifyMethods(List errors) { + record M(Utf8Entry name, Utf8Entry type) {}; + var methods = new HashSet(); + for (var m : classModel.methods()) try { + if (!methods.add(new M(m.methodName(), m.methodType()))) { + errors.add(new VerifyError("Duplicate method name %s with signature %s in %s".formatted(m.methodName().stringValue(), m.methodType().stringValue(), toString(classModel)))); + } + if (m.methodName().equalsString(CLASS_INIT_NAME) + && !m.flags().has(AccessFlag.STATIC)) { + errors.add(new VerifyError("Method is not static in %s".formatted(toString(classModel)))); + } + if (classModel.flags().has(AccessFlag.INTERFACE) + && m.methodName().equalsString(INIT_NAME)) { + errors.add(new VerifyError("Interface cannot have a method named in %s".formatted(toString(classModel)))); + } + verifyMethodName(m.methodName().stringValue()); + } catch (VerifyError ve) { + errors.add(ve); + } + } + + private void verifyAttributes(ClassFileElement cfe, List errors) { + if (cfe instanceof AttributedElement ae) { + var attrNames = new HashSet(); + for (var a : ae.attributes()) { + if (!a.attributeMapper().allowMultiple() && !attrNames.add(a.attributeName())) { + errors.add(new VerifyError("Multiple %s attributes in %s".formatted(a.attributeName(), toString(ae)))); + } + verifyAttribute(ae, a, errors); + } + } + switch (cfe) { + case CompoundElement comp -> { + for (var e : comp) verifyAttributes(e, errors); + } + case RecordAttribute ra -> { + for(var rc : ra.components()) verifyAttributes(rc, errors); + } + default -> {} + } + } + + private void verifyAttribute(AttributedElement ae, Attribute a, List errors) { + int size = switch (a) { + case AnnotationDefaultAttribute aa -> + valueSize(aa.defaultValue()); + case BootstrapMethodsAttribute bma -> + 2 + bma.bootstrapMethods().stream().mapToInt(bm -> 4 + 2 * bm.arguments().size()).sum(); + case CharacterRangeTableAttribute cra -> + 2 + 14 * cra.characterRangeTable().size(); + case CodeAttribute ca -> { + MethodModel mm = (MethodModel)ae; + if (mm.flags().has(AccessFlag.NATIVE) || mm.flags().has(AccessFlag.ABSTRACT)) { + errors.add(new VerifyError("Code attribute in native or abstract %s".formatted(toString(ae)))); + } + if (ca.maxLocals() < Util.maxLocals(mm.flags().flagsMask(), mm.methodTypeSymbol())) { + errors.add(new VerifyError("Arguments can't fit into locals in %s".formatted(toString(ae)))); + } + yield 10 + ca.codeLength() + 8 * ca.exceptionHandlers().size() + attributesSize(ca.attributes()); + } + case CompilationIDAttribute cida -> { + cida.compilationId(); + yield 2; + } + case ConstantValueAttribute cva -> { + ClassDesc type = ((FieldModel)ae).fieldTypeSymbol(); + ConstantValueEntry cve = cva.constant(); + if (!switch (TypeKind.from(type)) { + case BooleanType, ByteType, CharType, IntType, ShortType -> cve instanceof IntegerEntry; + case DoubleType -> cve instanceof DoubleEntry; + case FloatType -> cve instanceof FloatEntry; + case LongType -> cve instanceof LongEntry; + case ReferenceType -> type.equals(ConstantDescs.CD_String) && cve instanceof StringEntry; + case VoidType -> false; + }) { + errors.add(new VerifyError("Bad constant value type in %s".formatted(toString(ae)))); + } + yield 2; + } + case DeprecatedAttribute _ -> + 0; + case EnclosingMethodAttribute ema -> { + ema.enclosingClass(); + ema.enclosingMethod(); + yield 4; + } + case ExceptionsAttribute ea -> + 2 + 2 * ea.exceptions().size(); + case InnerClassesAttribute ica -> { + for (var ici : ica.classes()) { + if (ici.outerClass().isPresent() && ici.outerClass().get().equals(ici.innerClass())) { + errors.add(new VerifyError("Class is both outer and inner class in %s".formatted(toString(ae)))); + } + } + yield 2 + 8 * ica.classes().size(); + } + case LineNumberTableAttribute lta -> + 2 + 4 * lta.lineNumbers().size(); + case LocalVariableTableAttribute lvta -> + 2 + 10 * lvta.localVariables().size(); + case LocalVariableTypeTableAttribute lvta -> + 2 + 10 * lvta.localVariableTypes().size(); + case MethodParametersAttribute mpa -> + 1 + 4 * mpa.parameters().size(); + case ModuleAttribute ma -> + 16 + subSize(ma.exports(), ModuleExportInfo::exportsTo, 6, 2) + + subSize(ma.opens(), ModuleOpenInfo::opensTo, 6, 2) + + subSize(ma.provides(), ModuleProvideInfo::providesWith, 4, 2) + + 6 * ma.requires().size() + + 2 * ma.uses().size(); + case ModuleHashesAttribute mha -> + 2 + moduleHashesSize(mha.hashes()); + case ModuleMainClassAttribute mmca -> { + mmca.mainClass(); + yield 2; + } + case ModulePackagesAttribute mpa -> + 2 + 2 * mpa.packages().size(); + case ModuleResolutionAttribute mra -> + 2; + case ModuleTargetAttribute mta -> { + mta.targetPlatform(); + yield 2; + } + case NestHostAttribute nha -> { + nha.nestHost(); + yield 2; + } + case NestMembersAttribute nma -> { + if (ae.findAttribute(Attributes.nestHost()).isPresent()) { + errors.add(new VerifyError("Conflicting NestHost and NestMembers attributes in %s".formatted(toString(ae)))); + } + yield 2 + 2 * nma.nestMembers().size(); + } + case PermittedSubclassesAttribute psa -> { + if (classModel.flags().has(AccessFlag.FINAL)) { + errors.add(new VerifyError("PermittedSubclasses attribute in final %s".formatted(toString(ae)))); + } + yield 2 + 2 * psa.permittedSubclasses().size(); + } + case RecordAttribute ra -> + componentsSize(ra.components()); + case RuntimeVisibleAnnotationsAttribute aa -> + annotationsSize(aa.annotations()); + case RuntimeInvisibleAnnotationsAttribute aa -> + annotationsSize(aa.annotations()); + case RuntimeVisibleTypeAnnotationsAttribute aa -> + typeAnnotationsSize(aa.annotations()); + case RuntimeInvisibleTypeAnnotationsAttribute aa -> + typeAnnotationsSize(aa.annotations()); + case RuntimeVisibleParameterAnnotationsAttribute aa -> + parameterAnnotationsSize(aa.parameterAnnotations()); + case RuntimeInvisibleParameterAnnotationsAttribute aa -> + parameterAnnotationsSize(aa.parameterAnnotations()); + case SignatureAttribute sa -> { + sa.signature(); + yield 2; + } + case SourceDebugExtensionAttribute sda -> + sda.contents().length; + case SourceFileAttribute sfa -> { + sfa.sourceFile(); + yield 2; + } + case SourceIDAttribute sida -> { + sida.sourceId(); + yield 2; + } + case StackMapTableAttribute smta -> + 2 + subSize(smta.entries(), frame -> stackMapFrameSize(frame)); + case SyntheticAttribute _ -> + 0; + case UnknownAttribute _ -> + -1; + case CustomAttribute _ -> + -1; + default -> // should not happen if all known attributes are verified + throw new AssertionError(a); + }; + if (size >= 0 && size != ((BoundAttribute)a).payloadLen()) { + errors.add(new VerifyError("Wrong %s attribute length in %s".formatted(a.attributeName(), toString(ae)))); + } + } + + private static > int subSize(Collection entries, Function subMH, int entrySize, int subSize) { + return subSize(entries, (ToIntFunction) t -> entrySize + subSize * subMH.apply(t).size()); + } + + private static int subSize(Collection entries, ToIntFunction subMH) { + int l = 0; + for (T entry : entries) { + l += subMH.applyAsInt(entry); + } + return l; + } + + private static int componentsSize(List comps) { + int l = 2; + for (var rc : comps) { + l += 4 + attributesSize(rc.attributes()); + } + return l; + } + + private static int attributesSize(List> attrs) { + int l = 2; + for (var a : attrs) { + l += 6 + ((BoundAttribute)a).payloadLen(); + } + return l; + } + + private static int parameterAnnotationsSize(List> pans) { + int l = 1; + for (var ans : pans) { + l += annotationsSize(ans); + } + return l; + } + + private static int annotationsSize(List ans) { + int l = 2; + for (var an : ans) { + l += annotationSize(an); + } + return l; + } + + private static int typeAnnotationsSize(List ans) { + int l = 2; + for (var an : ans) { + l += 2 + an.targetInfo().size() + 2 * an.targetPath().size() + annotationSize(an); + } + return l; + } + + private static int annotationSize(Annotation an) { + int l = 4; + for (var el : an.elements()) { + l += 2 + valueSize(el.value()); + } + return l; + } + + private static int valueSize(AnnotationValue val) { + return 1 + switch (val) { + case AnnotationValue.OfAnnotation oan -> + annotationSize(oan.annotation()); + case AnnotationValue.OfArray oar -> { + int l = 2; + for (var v : oar.values()) { + l += valueSize(v); + } + yield l; + } + case AnnotationValue.OfConstant _, AnnotationValue.OfClass _ -> 2; + case AnnotationValue.OfEnum _ -> 4; + }; + } + + private static int moduleHashesSize(List hashes) { + int l = 2; + for (var h : hashes) { + h.moduleName(); + l += 4 + h.hash().length; + } + return l; + } + + private int stackMapFrameSize(StackMapFrameInfo frame) { + int ft = frame.frameType(); + if (ft < 64) return 1; + if (ft < 128) return 1 + verificationTypeSize(frame.stack().getFirst()); + if (ft > 246) { + if (ft == 247) return 3 + verificationTypeSize(frame.stack().getFirst()); + if (ft < 252) return 3; + if (ft < 255) { + var loc = frame.locals(); + int l = 3; + for (int i = loc.size() + 251 - ft; i < loc.size(); i++) { + l += verificationTypeSize(loc.get(i)); + } + return l; + } + if (ft == 255) { + int l = 7; + for (var vt : frame.stack()) { + l += verificationTypeSize(vt); + } + for (var vt : frame.locals()) { + l += verificationTypeSize(vt); + } + return l; + } + } + throw new IllegalArgumentException("Invalid stack map frame type " + ft); + } + + private static int verificationTypeSize(StackMapFrameInfo.VerificationTypeInfo vti) { + return switch (vti) { + case StackMapFrameInfo.SimpleVerificationTypeInfo _ -> 1; + case StackMapFrameInfo.ObjectVerificationTypeInfo ovti -> { + ovti.classSymbol(); + yield 3; + } + case StackMapFrameInfo.UninitializedVerificationTypeInfo _ -> 3; + }; + } + + private String className() { + return classModel.thisClass().asSymbol().displayName(); + } + + private String toString(AttributedElement ae) { + return switch (ae) { + case CodeModel m -> "Code attribute for " + toString(m.parent().get()); + case FieldModel m -> "field %s.%s".formatted( + className(), + m.fieldName().stringValue()); + case MethodModel m -> "method %s::%s(%s)".formatted( + className(), + m.methodName().stringValue(), + m.methodTypeSymbol().parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(","))); + case RecordComponentInfo i -> "Record component %s of class %s".formatted( + i.name().stringValue(), + className()); + default -> "class " + className(); + }; + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationBytecodes.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationBytecodes.class new file mode 100644 index 00000000..50ecd554 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationBytecodes.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java new file mode 100644 index 00000000..c9b076aa --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationBytecodes.java @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl.verifier; + +import java.nio.ByteBuffer; + +import java.lang.classfile.ClassFile; +import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType; +import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.*; + +/** + * @see hotspot/share/interpreter/bytecodes.hpp + * @see hotspot/share/interpreter/bytecodes.cpp + */ +final class VerificationBytecodes { + + static final int _breakpoint = 202, + _fast_agetfield = 203, + _fast_bgetfield = 204, + _fast_cgetfield = 205, + _fast_dgetfield = 206, + _fast_fgetfield = 207, + _fast_igetfield = 208, + _fast_lgetfield = 209, + _fast_sgetfield = 210, + _fast_aputfield = 211, + _fast_bputfield = 212, + _fast_zputfield = 213, + _fast_cputfield = 214, + _fast_dputfield = 215, + _fast_fputfield = 216, + _fast_iputfield = 217, + _fast_lputfield = 218, + _fast_sputfield = 219, + _fast_aload_0 = 220, + _fast_iaccess_0 = 221, + _fast_aaccess_0 = 222, + _fast_faccess_0 = 223, + _fast_iload = 224, + _fast_iload2 = 225, + _fast_icaload = 226, + _fast_invokevfinal = 227, + _fast_linearswitch = 228, + _fast_binaryswitch = 229, + _fast_aldc = 230, + _fast_aldc_w = 231, + _return_register_finalizer = 232, + _invokehandle = 233, + _nofast_getfield = 234, + _nofast_putfield = 235, + _nofast_aload_0 = 236, + _nofast_iload = 237, + _shouldnotreachhere = 238, + number_of_codes = 239; + + static int code_or_bp_at(byte[] code, int bci) { + return code[bci] & 0xff; + } + + static boolean is_valid(int code) { + return 0 <= code && code < number_of_codes; + } + + static int wide_length_for(int code) { + return is_valid(code) ? _lengths[code] >> 4 : -1; + } + + static boolean is_store_into_local(int code) { + return (ClassFile.ISTORE <= code && code <= ClassFile.ASTORE_3); + } + + static final int _lengths[] = new int[number_of_codes]; + + static int special_length_at(int code, byte bytecode[], int bci, int end) { + switch (code) { + case ClassFile.WIDE: + if (bci + 1 >= end) { + return -1; + } + return wide_length_for(bytecode[bci + 1] & 0xff); + case ClassFile.TABLESWITCH: + int aligned_bci = align(bci + 1); + if (aligned_bci + 3 * 4 >= end) { + return -1; + } + ByteBuffer bb = ByteBuffer.wrap(bytecode, aligned_bci + 1 * 4, 2 * 4); + int lo = bb.getInt(); + int hi = bb.getInt(); + int len = aligned_bci - bci + (3 + hi - lo + 1) * 4; + return len > 0 ? len : -1; + case ClassFile.LOOKUPSWITCH: + case _fast_binaryswitch: + case _fast_linearswitch: + aligned_bci = align(bci + 1); + if (aligned_bci + 2 * 4 >= end) { + return -1; + } + int npairs = ByteBuffer.wrap(bytecode, aligned_bci + 4, 4).getInt(); + len = aligned_bci - bci + (2 + 2 * npairs) * 4; + return len > 0 ? len : -1; + default: + return 0; + } + } + + static int align(int n) { + return (n + 3) & ~3; + } + + static void def(int code, String name, String format, String wide_format, BasicType result_type, int depth) { + def(code, name, format, wide_format, result_type, depth, code); + } + + static void def(int code, String name, String format, String wide_format, BasicType result_type, int depth, int java_code) { + if (wide_format != null && format == null) throw new IllegalArgumentException("short form must exist if there's a wide form"); + int len = format != null ? format.length() : 0; + int wlen = wide_format != null ? wide_format.length() : 0; + _lengths[code] = (wlen << 4) | (len & 0xf); + } + + static { + def(ClassFile.NOP, "nop", "b", null, T_VOID, 0); + def(ClassFile.ACONST_NULL, "aconst_null", "b", null, T_OBJECT, 1); + def(ClassFile.ICONST_M1, "iconst_m1", "b", null, T_INT, 1); + def(ClassFile.ICONST_0, "iconst_0", "b", null, T_INT, 1); + def(ClassFile.ICONST_1, "iconst_1", "b", null, T_INT, 1); + def(ClassFile.ICONST_2, "iconst_2", "b", null, T_INT, 1); + def(ClassFile.ICONST_3, "iconst_3", "b", null, T_INT, 1); + def(ClassFile.ICONST_4, "iconst_4", "b", null, T_INT, 1); + def(ClassFile.ICONST_5, "iconst_5", "b", null, T_INT, 1); + def(ClassFile.LCONST_0, "lconst_0", "b", null, T_LONG, 2); + def(ClassFile.LCONST_1, "lconst_1", "b", null, T_LONG, 2); + def(ClassFile.FCONST_0, "fconst_0", "b", null, T_FLOAT, 1); + def(ClassFile.FCONST_1, "fconst_1", "b", null, T_FLOAT, 1); + def(ClassFile.FCONST_2, "fconst_2", "b", null, T_FLOAT, 1); + def(ClassFile.DCONST_0, "dconst_0", "b", null, T_DOUBLE, 2); + def(ClassFile.DCONST_1, "dconst_1", "b", null, T_DOUBLE, 2); + def(ClassFile.BIPUSH, "bipush", "bc", null, T_INT, 1); + def(ClassFile.SIPUSH, "sipush", "bcc", null, T_INT, 1); + def(ClassFile.LDC, "ldc", "bk", null, T_ILLEGAL, 1); + def(ClassFile.LDC_W, "ldc_w", "bkk", null, T_ILLEGAL, 1); + def(ClassFile.LDC2_W, "ldc2_w", "bkk", null, T_ILLEGAL, 2); + def(ClassFile.ILOAD, "iload", "bi", "wbii", T_INT, 1); + def(ClassFile.LLOAD, "lload", "bi", "wbii", T_LONG, 2); + def(ClassFile.FLOAD, "fload", "bi", "wbii", T_FLOAT, 1); + def(ClassFile.DLOAD, "dload", "bi", "wbii", T_DOUBLE, 2); + def(ClassFile.ALOAD, "aload", "bi", "wbii", T_OBJECT, 1); + def(ClassFile.ILOAD_0, "iload_0", "b", null, T_INT, 1); + def(ClassFile.ILOAD_1, "iload_1", "b", null, T_INT, 1); + def(ClassFile.ILOAD_2, "iload_2", "b", null, T_INT, 1); + def(ClassFile.ILOAD_3, "iload_3", "b", null, T_INT, 1); + def(ClassFile.LLOAD_0, "lload_0", "b", null, T_LONG, 2); + def(ClassFile.LLOAD_1, "lload_1", "b", null, T_LONG, 2); + def(ClassFile.LLOAD_2, "lload_2", "b", null, T_LONG, 2); + def(ClassFile.LLOAD_3, "lload_3", "b", null, T_LONG, 2); + def(ClassFile.FLOAD_0, "fload_0", "b", null, T_FLOAT, 1); + def(ClassFile.FLOAD_1, "fload_1", "b", null, T_FLOAT, 1); + def(ClassFile.FLOAD_2, "fload_2", "b", null, T_FLOAT, 1); + def(ClassFile.FLOAD_3, "fload_3", "b", null, T_FLOAT, 1); + def(ClassFile.DLOAD_0, "dload_0", "b", null, T_DOUBLE, 2); + def(ClassFile.DLOAD_1, "dload_1", "b", null, T_DOUBLE, 2); + def(ClassFile.DLOAD_2, "dload_2", "b", null, T_DOUBLE, 2); + def(ClassFile.DLOAD_3, "dload_3", "b", null, T_DOUBLE, 2); + def(ClassFile.ALOAD_0, "aload_0", "b", null, T_OBJECT, 1); + def(ClassFile.ALOAD_1, "aload_1", "b", null, T_OBJECT, 1); + def(ClassFile.ALOAD_2, "aload_2", "b", null, T_OBJECT, 1); + def(ClassFile.ALOAD_3, "aload_3", "b", null, T_OBJECT, 1); + def(ClassFile.IALOAD, "iaload", "b", null, T_INT, -1); + def(ClassFile.LALOAD, "laload", "b", null, T_LONG, 0); + def(ClassFile.FALOAD, "faload", "b", null, T_FLOAT, -1); + def(ClassFile.DALOAD, "daload", "b", null, T_DOUBLE, 0); + def(ClassFile.AALOAD, "aaload", "b", null, T_OBJECT, -1); + def(ClassFile.BALOAD, "baload", "b", null, T_INT, -1); + def(ClassFile.CALOAD, "caload", "b", null, T_INT, -1); + def(ClassFile.SALOAD, "saload", "b", null, T_INT, -1); + def(ClassFile.ISTORE, "istore", "bi", "wbii", T_VOID, -1); + def(ClassFile.LSTORE, "lstore", "bi", "wbii", T_VOID, -2); + def(ClassFile.FSTORE, "fstore", "bi", "wbii", T_VOID, -1); + def(ClassFile.DSTORE, "dstore", "bi", "wbii", T_VOID, -2); + def(ClassFile.ASTORE, "astore", "bi", "wbii", T_VOID, -1); + def(ClassFile.ISTORE_0, "istore_0", "b", null, T_VOID, -1); + def(ClassFile.ISTORE_1, "istore_1", "b", null, T_VOID, -1); + def(ClassFile.ISTORE_2, "istore_2", "b", null, T_VOID, -1); + def(ClassFile.ISTORE_3, "istore_3", "b", null, T_VOID, -1); + def(ClassFile.LSTORE_0, "lstore_0", "b", null, T_VOID, -2); + def(ClassFile.LSTORE_1, "lstore_1", "b", null, T_VOID, -2); + def(ClassFile.LSTORE_2, "lstore_2", "b", null, T_VOID, -2); + def(ClassFile.LSTORE_3, "lstore_3", "b", null, T_VOID, -2); + def(ClassFile.FSTORE_0, "fstore_0", "b", null, T_VOID, -1); + def(ClassFile.FSTORE_1, "fstore_1", "b", null, T_VOID, -1); + def(ClassFile.FSTORE_2, "fstore_2", "b", null, T_VOID, -1); + def(ClassFile.FSTORE_3, "fstore_3", "b", null, T_VOID, -1); + def(ClassFile.DSTORE_0, "dstore_0", "b", null, T_VOID, -2); + def(ClassFile.DSTORE_1, "dstore_1", "b", null, T_VOID, -2); + def(ClassFile.DSTORE_2, "dstore_2", "b", null, T_VOID, -2); + def(ClassFile.DSTORE_3, "dstore_3", "b", null, T_VOID, -2); + def(ClassFile.ASTORE_0, "astore_0", "b", null, T_VOID, -1); + def(ClassFile.ASTORE_1, "astore_1", "b", null, T_VOID, -1); + def(ClassFile.ASTORE_2, "astore_2", "b", null, T_VOID, -1); + def(ClassFile.ASTORE_3, "astore_3", "b", null, T_VOID, -1); + def(ClassFile.IASTORE, "iastore", "b", null, T_VOID, -3); + def(ClassFile.LASTORE, "lastore", "b", null, T_VOID, -4); + def(ClassFile.FASTORE, "fastore", "b", null, T_VOID, -3); + def(ClassFile.DASTORE, "dastore", "b", null, T_VOID, -4); + def(ClassFile.AASTORE, "aastore", "b", null, T_VOID, -3); + def(ClassFile.BASTORE, "bastore", "b", null, T_VOID, -3); + def(ClassFile.CASTORE, "castore", "b", null, T_VOID, -3); + def(ClassFile.SASTORE, "sastore", "b", null, T_VOID, -3); + def(ClassFile.POP, "pop", "b", null, T_VOID, -1); + def(ClassFile.POP2, "pop2", "b", null, T_VOID, -2); + def(ClassFile.DUP, "dup", "b", null, T_VOID, 1); + def(ClassFile.DUP_X1, "dup_x1", "b", null, T_VOID, 1); + def(ClassFile.DUP_X2, "dup_x2", "b", null, T_VOID, 1); + def(ClassFile.DUP2, "dup2", "b", null, T_VOID, 2); + def(ClassFile.DUP2_X1, "dup2_x1", "b", null, T_VOID, 2); + def(ClassFile.DUP2_X2, "dup2_x2", "b", null, T_VOID, 2); + def(ClassFile.SWAP, "swap", "b", null, T_VOID, 0); + def(ClassFile.IADD, "iadd", "b", null, T_INT, -1); + def(ClassFile.LADD, "ladd", "b", null, T_LONG, -2); + def(ClassFile.FADD, "fadd", "b", null, T_FLOAT, -1); + def(ClassFile.DADD, "dadd", "b", null, T_DOUBLE, -2); + def(ClassFile.ISUB, "isub", "b", null, T_INT, -1); + def(ClassFile.LSUB, "lsub", "b", null, T_LONG, -2); + def(ClassFile.FSUB, "fsub", "b", null, T_FLOAT, -1); + def(ClassFile.DSUB, "dsub", "b", null, T_DOUBLE, -2); + def(ClassFile.IMUL, "imul", "b", null, T_INT, -1); + def(ClassFile.LMUL, "lmul", "b", null, T_LONG, -2); + def(ClassFile.FMUL, "fmul", "b", null, T_FLOAT, -1); + def(ClassFile.DMUL, "dmul", "b", null, T_DOUBLE, -2); + def(ClassFile.IDIV, "idiv", "b", null, T_INT, -1); + def(ClassFile.LDIV, "ldiv", "b", null, T_LONG, -2); + def(ClassFile.FDIV, "fdiv", "b", null, T_FLOAT, -1); + def(ClassFile.DDIV, "ddiv", "b", null, T_DOUBLE, -2); + def(ClassFile.IREM, "irem", "b", null, T_INT, -1); + def(ClassFile.LREM, "lrem", "b", null, T_LONG, -2); + def(ClassFile.FREM, "frem", "b", null, T_FLOAT, -1); + def(ClassFile.DREM, "drem", "b", null, T_DOUBLE, -2); + def(ClassFile.INEG, "ineg", "b", null, T_INT, 0); + def(ClassFile.LNEG, "lneg", "b", null, T_LONG, 0); + def(ClassFile.FNEG, "fneg", "b", null, T_FLOAT, 0); + def(ClassFile.DNEG, "dneg", "b", null, T_DOUBLE, 0); + def(ClassFile.ISHL, "ishl", "b", null, T_INT, -1); + def(ClassFile.LSHL, "lshl", "b", null, T_LONG, -1); + def(ClassFile.ISHR, "ishr", "b", null, T_INT, -1); + def(ClassFile.LSHR, "lshr", "b", null, T_LONG, -1); + def(ClassFile.IUSHR, "iushr", "b", null, T_INT, -1); + def(ClassFile.LUSHR, "lushr", "b", null, T_LONG, -1); + def(ClassFile.IAND, "iand", "b", null, T_INT, -1); + def(ClassFile.LAND, "land", "b", null, T_LONG, -2); + def(ClassFile.IOR, "ior", "b", null, T_INT, -1); + def(ClassFile.LOR, "lor", "b", null, T_LONG, -2); + def(ClassFile.IXOR, "ixor", "b", null, T_INT, -1); + def(ClassFile.LXOR, "lxor", "b", null, T_LONG, -2); + def(ClassFile.IINC, "iinc", "bic", "wbiicc", T_VOID, 0); + def(ClassFile.I2L, "i2l", "b", null, T_LONG, 1); + def(ClassFile.I2F, "i2f", "b", null, T_FLOAT, 0); + def(ClassFile.I2D, "i2d", "b", null, T_DOUBLE, 1); + def(ClassFile.L2I, "l2i", "b", null, T_INT, -1); + def(ClassFile.L2F, "l2f", "b", null, T_FLOAT, -1); + def(ClassFile.L2D, "l2d", "b", null, T_DOUBLE, 0); + def(ClassFile.F2I, "f2i", "b", null, T_INT, 0); + def(ClassFile.F2L, "f2l", "b", null, T_LONG, 1); + def(ClassFile.F2D, "f2d", "b", null, T_DOUBLE, 1); + def(ClassFile.D2I, "d2i", "b", null, T_INT, -1); + def(ClassFile.D2L, "d2l", "b", null, T_LONG, 0); + def(ClassFile.D2F, "d2f", "b", null, T_FLOAT, -1); + def(ClassFile.I2B, "i2b", "b", null, T_BYTE, 0); + def(ClassFile.I2C, "i2c", "b", null, T_CHAR, 0); + def(ClassFile.I2S, "i2s", "b", null, T_SHORT, 0); + def(ClassFile.LCMP, "lcmp", "b", null, T_VOID, -3); + def(ClassFile.FCMPL, "fcmpl", "b", null, T_VOID, -1); + def(ClassFile.FCMPG, "fcmpg", "b", null, T_VOID, -1); + def(ClassFile.DCMPL, "dcmpl", "b", null, T_VOID, -3); + def(ClassFile.DCMPG, "dcmpg", "b", null, T_VOID, -3); + def(ClassFile.IFEQ, "ifeq", "boo", null, T_VOID, -1); + def(ClassFile.IFNE, "ifne", "boo", null, T_VOID, -1); + def(ClassFile.IFLT, "iflt", "boo", null, T_VOID, -1); + def(ClassFile.IFGE, "ifge", "boo", null, T_VOID, -1); + def(ClassFile.IFGT, "ifgt", "boo", null, T_VOID, -1); + def(ClassFile.IFLE, "ifle", "boo", null, T_VOID, -1); + def(ClassFile.IF_ICMPEQ, "if_icmpeq", "boo", null, T_VOID, -2); + def(ClassFile.IF_ICMPNE, "if_icmpne", "boo", null, T_VOID, -2); + def(ClassFile.IF_ICMPLT, "if_icmplt", "boo", null, T_VOID, -2); + def(ClassFile.IF_ICMPGE, "if_icmpge", "boo", null, T_VOID, -2); + def(ClassFile.IF_ICMPGT, "if_icmpgt", "boo", null, T_VOID, -2); + def(ClassFile.IF_ICMPLE, "if_icmple", "boo", null, T_VOID, -2); + def(ClassFile.IF_ACMPEQ, "if_acmpeq", "boo", null, T_VOID, -2); + def(ClassFile.IF_ACMPNE, "if_acmpne", "boo", null, T_VOID, -2); + def(ClassFile.GOTO, "goto", "boo", null, T_VOID, 0); + def(ClassFile.JSR, "jsr", "boo", null, T_INT, 0); + def(ClassFile.RET, "ret", "bi", "wbii", T_VOID, 0); + def(ClassFile.TABLESWITCH, "tableswitch", "", null, T_VOID, -1); // may have backward branches + def(ClassFile.LOOKUPSWITCH, "lookupswitch", "", null, T_VOID, -1); // rewriting in interpreter + def(ClassFile.IRETURN, "ireturn", "b", null, T_INT, -1); + def(ClassFile.LRETURN, "lreturn", "b", null, T_LONG, -2); + def(ClassFile.FRETURN, "freturn", "b", null, T_FLOAT, -1); + def(ClassFile.DRETURN, "dreturn", "b", null, T_DOUBLE, -2); + def(ClassFile.ARETURN, "areturn", "b", null, T_OBJECT, -1); + def(ClassFile.RETURN, "return", "b", null, T_VOID, 0); + def(ClassFile.GETSTATIC, "getstatic", "bJJ", null, T_ILLEGAL, 1); + def(ClassFile.PUTSTATIC, "putstatic", "bJJ", null, T_ILLEGAL, -1); + def(ClassFile.GETFIELD, "getfield", "bJJ", null, T_ILLEGAL, 0); + def(ClassFile.PUTFIELD, "putfield", "bJJ", null, T_ILLEGAL, -2); + def(ClassFile.INVOKEVIRTUAL, "invokevirtual", "bJJ", null, T_ILLEGAL, -1); + def(ClassFile.INVOKESPECIAL, "invokespecial", "bJJ", null, T_ILLEGAL, -1); + def(ClassFile.INVOKESTATIC, "invokestatic", "bJJ", null, T_ILLEGAL, 0); + def(ClassFile.INVOKEINTERFACE, "invokeinterface", "bJJ__", null, T_ILLEGAL, -1); + def(ClassFile.INVOKEDYNAMIC, "invokedynamic", "bJJJJ", null, T_ILLEGAL, 0); + def(ClassFile.NEW, "new", "bkk", null, T_OBJECT, 1); + def(ClassFile.NEWARRAY, "newarray", "bc", null, T_OBJECT, 0); + def(ClassFile.ANEWARRAY, "anewarray", "bkk", null, T_OBJECT, 0); + def(ClassFile.ARRAYLENGTH, "arraylength", "b", null, T_VOID, 0); + def(ClassFile.ATHROW, "athrow", "b", null, T_VOID, -1); + def(ClassFile.CHECKCAST, "checkcast", "bkk", null, T_OBJECT, 0); + def(ClassFile.INSTANCEOF, "instanceof", "bkk", null, T_INT, 0); + def(ClassFile.MONITORENTER, "monitorenter", "b", null, T_VOID, -1); + def(ClassFile.MONITOREXIT, "monitorexit", "b", null, T_VOID, -1); + def(ClassFile.WIDE, "wide", "", null, T_VOID, 0); + def(ClassFile.MULTIANEWARRAY, "multianewarray", "bkkc", null, T_OBJECT, 1); + def(ClassFile.IFNULL, "ifnull", "boo", null, T_VOID, -1); + def(ClassFile.IFNONNULL, "ifnonnull", "boo", null, T_VOID, -1); + def(ClassFile.GOTO_W, "goto_w", "boooo", null, T_VOID, 0); + def(ClassFile.JSR_W, "jsr_w", "boooo", null, T_INT, 0); + def(_breakpoint, "breakpoint", "", null, T_VOID, 0); + def(_fast_agetfield, "fast_agetfield", "bJJ", null, T_OBJECT, 0, ClassFile.GETFIELD); + def(_fast_bgetfield, "fast_bgetfield", "bJJ", null, T_INT, 0, ClassFile.GETFIELD); + def(_fast_cgetfield, "fast_cgetfield", "bJJ", null, T_CHAR, 0, ClassFile.GETFIELD); + def(_fast_dgetfield, "fast_dgetfield", "bJJ", null, T_DOUBLE, 0, ClassFile.GETFIELD); + def(_fast_fgetfield, "fast_fgetfield", "bJJ", null, T_FLOAT, 0, ClassFile.GETFIELD); + def(_fast_igetfield, "fast_igetfield", "bJJ", null, T_INT, 0, ClassFile.GETFIELD); + def(_fast_lgetfield, "fast_lgetfield", "bJJ", null, T_LONG, 0, ClassFile.GETFIELD); + def(_fast_sgetfield, "fast_sgetfield", "bJJ", null, T_SHORT, 0, ClassFile.GETFIELD); + def(_fast_aputfield, "fast_aputfield", "bJJ", null, T_OBJECT, 0, ClassFile.PUTFIELD); + def(_fast_bputfield, "fast_bputfield", "bJJ", null, T_INT, 0, ClassFile.PUTFIELD); + def(_fast_zputfield, "fast_zputfield", "bJJ", null, T_INT, 0, ClassFile.PUTFIELD); + def(_fast_cputfield, "fast_cputfield", "bJJ", null, T_CHAR, 0, ClassFile.PUTFIELD); + def(_fast_dputfield, "fast_dputfield", "bJJ", null, T_DOUBLE, 0, ClassFile.PUTFIELD); + def(_fast_fputfield, "fast_fputfield", "bJJ", null, T_FLOAT, 0, ClassFile.PUTFIELD); + def(_fast_iputfield, "fast_iputfield", "bJJ", null, T_INT, 0, ClassFile.PUTFIELD); + def(_fast_lputfield, "fast_lputfield", "bJJ", null, T_LONG, 0, ClassFile.PUTFIELD); + def(_fast_sputfield, "fast_sputfield", "bJJ", null, T_SHORT, 0, ClassFile.PUTFIELD); + def(_fast_aload_0, "fast_aload_0", "b", null, T_OBJECT, 1, ClassFile.ALOAD_0); + def(_fast_iaccess_0, "fast_iaccess_0", "b_JJ", null, T_INT, 1, ClassFile.ALOAD_0); + def(_fast_aaccess_0, "fast_aaccess_0", "b_JJ", null, T_OBJECT, 1, ClassFile.ALOAD_0); + def(_fast_faccess_0, "fast_faccess_0", "b_JJ", null, T_OBJECT, 1, ClassFile.ALOAD_0); + def(_fast_iload, "fast_iload", "bi", null, T_INT, 1, ClassFile.ILOAD); + def(_fast_iload2, "fast_iload2", "bi_i", null, T_INT, 2, ClassFile.ILOAD); + def(_fast_icaload, "fast_icaload", "bi_", null, T_INT, 0, ClassFile.ILOAD); + def(_fast_invokevfinal, "fast_invokevfinal", "bJJ", null, T_ILLEGAL, -1, ClassFile.INVOKEVIRTUAL); + def(_fast_linearswitch, "fast_linearswitch", "", null, T_VOID, -1, ClassFile.LOOKUPSWITCH); + def(_fast_binaryswitch, "fast_binaryswitch", "", null, T_VOID, -1, ClassFile.LOOKUPSWITCH); + def(_return_register_finalizer, "return_register_finalizer", "b", null, T_VOID, 0, ClassFile.RETURN); + def(_invokehandle, "invokehandle", "bJJ", null, T_ILLEGAL, -1, ClassFile.INVOKEVIRTUAL); + def(_fast_aldc, "fast_aldc", "bj", null, T_OBJECT, 1, ClassFile.LDC); + def(_fast_aldc_w, "fast_aldc_w", "bJJ", null, T_OBJECT, 1, ClassFile.LDC_W); + def(_nofast_getfield, "nofast_getfield", "bJJ", null, T_ILLEGAL, 0, ClassFile.GETFIELD); + def(_nofast_putfield, "nofast PUTFIELD", "bJJ", null, T_ILLEGAL, -2, ClassFile.PUTFIELD); + def(_nofast_aload_0, "nofast_aload_0", "b", null, T_ILLEGAL, 1, ClassFile.ALOAD_0); + def(_nofast_iload, "nofast_iload", "bi", null, T_ILLEGAL, 1, ClassFile.ILOAD); + def(_shouldnotreachhere, "_shouldnotreachhere", "b", null, T_VOID, 0); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame$1.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame$1.class new file mode 100644 index 00000000..1ae112e5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame.class new file mode 100644 index 00000000..cfded45a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame.java b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame.java new file mode 100644 index 00000000..13aac2c9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationFrame.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl.verifier; + +import java.util.Arrays; + +/** + * @see hotspot/share/classfile/stackMapFrame.hpp + * @see hotspot/share/classfile/stackMapFrame.cpp + */ +class VerificationFrame { + + public static final int FLAG_THIS_UNINIT = 0x01; + + private int _offset; + private int _locals_size, _stack_size; + private int _stack_mark; + private final int _max_locals, _max_stack; + private int _flags; + private final VerificationType[] _locals, _stack; + private final VerifierImpl _verifier; + + public VerificationFrame(int offset, int flags, int locals_size, int stack_size, int max_locals, int max_stack, VerificationType[] locals, VerificationType[] stack, VerifierImpl v) { + this._offset = offset; + this._locals_size = locals_size; + this._stack_size = stack_size; + this._stack_mark = -1; + this._max_locals = max_locals; + this._max_stack = max_stack; + this._flags = flags; + this._locals = locals; + this._stack = stack; + this._verifier = v; + } + + @Override + public String toString() { + return "frame @" + _offset + " with locals " + (_locals == null ? "[]" : Arrays.asList(_locals)) + " and stack " + (_stack == null ? "[]" : Arrays.asList(_stack)); + } + + void set_offset(int offset) { + this._offset = offset; + } + + void set_flags(int flags) { + _flags = flags; + } + + void set_locals_size(int locals_size) { + _locals_size = locals_size; + } + + void set_stack_size(int stack_size) { + _stack_size = _stack_mark = stack_size; + } + + int offset() { + return _offset; + } + + VerifierImpl verifier() { + return _verifier; + } + + int flags() { + return _flags; + } + + int locals_size() { + return _locals_size; + } + + VerificationType[] locals() { + return _locals; + } + + int stack_size() { + return _stack_size; + } + + VerificationType[] stack() { + return _stack; + } + + int max_locals() { + return _max_locals; + } + + boolean flag_this_uninit() { + return (_flags & FLAG_THIS_UNINIT) == FLAG_THIS_UNINIT; + } + + void reset() { + for (int i = 0; i < _max_locals; i++) { + _locals[i] = VerificationType.bogus_type; + } + for (int i = 0; i < _max_stack; i++) { + _stack[i] = VerificationType.bogus_type; + } + } + + void set_mark() { + if (_stack_mark != -1) { + for (int i = _stack_mark - 1; i >= _stack_size; --i) { + _stack[i] = VerificationType.bogus_type; + } + _stack_mark = _stack_size; + } + } + + void push_stack(VerificationType type) { + if (type.is_check()) _verifier.verifyError("Must be a real type"); + if (_stack_size >= _max_stack) { + _verifier.verifyError("Operand stack overflow"); + } + _stack[_stack_size++] = type; + } + + void push_stack_2(VerificationType type1, VerificationType type2) { + if (!(type1.is_long() || type1.is_double())) _verifier.verifyError("must be long/double"); + if (!(type2.is_long2() || type2.is_double2())) _verifier.verifyError("must be long/double_2"); + if (_stack_size >= _max_stack - 1) { + _verifier.verifyError("Operand stack overflow"); + } + _stack[_stack_size++] = type1; + _stack[_stack_size++] = type2; + } + + VerificationType pop_stack() { + if (_stack_size <= 0) { + _verifier.verifyError("Operand stack underflow"); + } + return _stack[--_stack_size]; + } + + VerificationType pop_stack(VerificationType type) { + if (_stack_size != 0) { + VerificationType top = _stack[_stack_size - 1]; + boolean subtype = type.is_assignable_from(top, verifier()); + if (subtype) { + --_stack_size; + return top; + } + } + return pop_stack_ex(type); + } + + void pop_stack_2(VerificationType type1, VerificationType type2) { + if (!(type1.is_long2() || type1.is_double2())) _verifier.verifyError("must be long/double"); + if (!(type2.is_long() || type2.is_double())) _verifier.verifyError("must be long/double_2"); + if (_stack_size >= 2) { + VerificationType top1 = _stack[_stack_size - 1]; + boolean subtype1 = type1.is_assignable_from(top1, verifier()); + VerificationType top2 = _stack[_stack_size - 2]; + boolean subtype2 = type2.is_assignable_from(top2, verifier()); + if (subtype1 && subtype2) { + _stack_size -= 2; + return; + } + } + pop_stack_ex(type1); + pop_stack_ex(type2); + } + + VerificationFrame(int max_locals, int max_stack, VerifierImpl verifier) { + _offset = 0; + _locals_size = 0; + _stack_size = 0; + _stack_mark = 0; + _max_locals = max_locals; + _max_stack = max_stack; + _flags = 0; + _verifier = verifier; + _locals = new VerificationType[max_locals]; + _stack = new VerificationType[max_stack]; + for (int i = 0; i < max_locals; i++) { + _locals[i] = VerificationType.bogus_type; + } + for (int i = 0; i < max_stack; i++) { + _stack[i] = VerificationType.bogus_type; + } + } + + VerificationFrame frame_in_exception_handler(int flags) { + return new VerificationFrame(_offset, flags, _locals_size, 0, + _max_locals, _max_stack, _locals, new VerificationType[1], + _verifier); + } + + void initialize_object(VerificationType old_object, VerificationType new_object) { + int i; + for (i = 0; i < _max_locals; i++) { + if (_locals[i].equals(old_object)) { + _locals[i] = new_object; + } + } + for (i = 0; i < _stack_size; i++) { + if (_stack[i].equals(old_object)) { + _stack[i] = new_object; + } + } + if (old_object.is_uninitialized_this(_verifier)) { + _flags = 0; + } + } + + VerificationType set_locals_from_arg(VerificationWrapper.MethodWrapper m, VerificationType thisKlass) { + var ss = new VerificationSignature(m.descriptor(), true, _verifier); + int init_local_num = 0; + if (!m.isStatic()) { + init_local_num++; + if (VerifierImpl.object_initializer_name.equals(m.name()) && !VerifierImpl.java_lang_Object.equals(thisKlass.name())) { + _locals[0] = VerificationType.uninitialized_this_type; + _flags |= FLAG_THIS_UNINIT; + } else { + _locals[0] = thisKlass; + } + } + while (!ss.atReturnType()) { + init_local_num += _verifier.change_sig_to_verificationType(ss, _locals, init_local_num); + ss.next(); + } + _locals_size = init_local_num; + switch (ss.type()) { + case T_OBJECT: + case T_ARRAY: + { + String sig = ss.asSymbol(); + return VerificationType.reference_type(sig); + } + case T_INT: return VerificationType.integer_type; + case T_BYTE: return VerificationType.byte_type; + case T_CHAR: return VerificationType.char_type; + case T_SHORT: return VerificationType.short_type; + case T_BOOLEAN: return VerificationType.boolean_type; + case T_FLOAT: return VerificationType.float_type; + case T_DOUBLE: return VerificationType.double_type; + case T_LONG: return VerificationType.long_type; + case T_VOID: return VerificationType.bogus_type; + default: + _verifier.verifyError("Should not reach here"); + return VerificationType.bogus_type; + } + } + + void copy_locals(VerificationFrame src) { + int len = src.locals_size() < _locals_size ? src.locals_size() : _locals_size; + if (len > 0) System.arraycopy(src.locals(), 0, _locals, 0, len); + } + + void copy_stack(VerificationFrame src) { + int len = src.stack_size() < _stack_size ? src.stack_size() : _stack_size; + if (len > 0) System.arraycopy(src.stack(), 0, _stack, 0, len); + } + + private int is_assignable_to(VerificationType[] from, VerificationType[] to, int len) { + int i = 0; + for (; i < len; i++) { + if (!to[i].is_assignable_from(from[i], verifier())) { + break; + } + } + return i; + } + + boolean is_assignable_to(VerificationFrame target) { + if (_max_locals != target.max_locals()) { + _verifier.verifyError("Locals size mismatch", this, target); + } + if (_stack_size != target.stack_size()) { + _verifier.verifyError("Stack size mismatch", this, target); + } + int mismatch_loc; + mismatch_loc = is_assignable_to(_locals, target.locals(), target.locals_size()); + if (mismatch_loc != target.locals_size()) { + _verifier.verifyError("Bad type", this, target); + } + mismatch_loc = is_assignable_to(_stack, target.stack(), _stack_size); + if (mismatch_loc != _stack_size) { + _verifier.verifyError("Bad type", this, target); + } + + if ((_flags | target.flags()) == target.flags()) { + return true; + } else { + _verifier.verifyError("Bad flags", this, target); + } + return false; + } + + VerificationType pop_stack_ex(VerificationType type) { + if (_stack_size <= 0) { + _verifier.verifyError("Operand stack underflow"); + } + VerificationType top = _stack[--_stack_size]; + boolean subtype = type.is_assignable_from(top, verifier()); + if (!subtype) { + _verifier.verifyError("Bad type on operand stack"); + } + return top; + } + + VerificationType get_local(int index, VerificationType type) { + if (index >= _max_locals) { + _verifier.verifyError("Local variable table overflow"); + } + boolean subtype = type.is_assignable_from(_locals[index], + verifier()); + if (!subtype) { + _verifier.verifyError("Bad local variable type"); + } + if(index >= _locals_size) { _locals_size = index + 1; } + return _locals[index]; + } + + void get_local_2(int index, VerificationType type1, VerificationType type2) { + if (!(type1.is_long() || type1.is_double())) _verifier.verifyError("must be long/double"); + if (!(type2.is_long2() || type2.is_double2())) _verifier.verifyError("must be long/double_2"); + if (index >= _locals_size - 1) { + _verifier.verifyError("get long/double overflows locals"); + } + boolean subtype = type1.is_assignable_from(_locals[index], verifier()); + if (!subtype) { + _verifier.verifyError("Bad local variable type"); + } else { + subtype = type2.is_assignable_from(_locals[index + 1], verifier()); + if (!subtype) { + _verifier.verifyError("Bad local variable type"); + } + } + } + + void set_local(int index, VerificationType type) { + if (type.is_check()) _verifier.verifyError("Must be a real type"); + if (index >= _max_locals) { + _verifier.verifyError("Local variable table overflow"); + } + if (_locals[index].is_double() || _locals[index].is_long()) { + if ((index + 1) >= _locals_size) _verifier.verifyError("Local variable table overflow"); + _locals[index + 1] = VerificationType.bogus_type; + } + if (_locals[index].is_double2() || _locals[index].is_long2()) { + if (index < 1) _verifier.verifyError("Local variable table underflow"); + _locals[index - 1] = VerificationType.bogus_type; + } + _locals[index] = type; + if (index >= _locals_size) { + for (int i=_locals_size; i= _max_locals - 1) { + _verifier.verifyError("Local variable table overflow"); + } + if (_locals[index+1].is_double() || _locals[index+1].is_long()) { + if ((index + 2) >= _locals_size) _verifier.verifyError("Local variable table overflow"); + _locals[index + 2] = VerificationType.bogus_type; + } + if (_locals[index].is_double2() || _locals[index].is_long2()) { + if (index < 1) _verifier.verifyError("Local variable table underflow"); + _locals[index - 1] = VerificationType.bogus_type; + } + _locals[index] = type1; + _locals[index+1] = type2; + if (index >= _locals_size - 1) { + for (int i=_locals_size; i + T_BOOLEAN; + case JVM_SIGNATURE_CHAR -> + T_CHAR; + case JVM_SIGNATURE_FLOAT -> + T_FLOAT; + case JVM_SIGNATURE_DOUBLE -> + T_DOUBLE; + case JVM_SIGNATURE_BYTE -> + T_BYTE; + case JVM_SIGNATURE_SHORT -> + T_SHORT; + case JVM_SIGNATURE_INT -> + T_INT; + case JVM_SIGNATURE_LONG -> + T_LONG; + case JVM_SIGNATURE_CLASS -> + T_OBJECT; + case JVM_SIGNATURE_ARRAY -> + T_ARRAY; + case JVM_SIGNATURE_VOID -> + T_VOID; + default -> + throw new IllegalArgumentException("Not a valid type: '" + ch + "'"); + }; + } + } + + static final char JVM_SIGNATURE_DOT = '.', + JVM_SIGNATURE_ARRAY = '[', + JVM_SIGNATURE_BYTE = 'B', + JVM_SIGNATURE_CHAR = 'C', + JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_ENDCLASS = ';', + JVM_SIGNATURE_FLOAT = 'F', + JVM_SIGNATURE_DOUBLE = 'D', + JVM_SIGNATURE_FUNC = '(', + JVM_SIGNATURE_ENDFUNC = ')', + JVM_SIGNATURE_INT = 'I', + JVM_SIGNATURE_LONG = 'J', + JVM_SIGNATURE_SHORT = 'S', + JVM_SIGNATURE_VOID = 'V', + JVM_SIGNATURE_BOOLEAN = 'Z'; + + static boolean isReferenceType(BasicType t) { + return t == BasicType.T_OBJECT || t == BasicType.T_ARRAY; + } + + private static final char TYPE2CHAR_TAB[] = new char[]{ + 0, 0, 0, 0, + JVM_SIGNATURE_BOOLEAN, JVM_SIGNATURE_CHAR, + JVM_SIGNATURE_FLOAT, JVM_SIGNATURE_DOUBLE, + JVM_SIGNATURE_BYTE, JVM_SIGNATURE_SHORT, + JVM_SIGNATURE_INT, JVM_SIGNATURE_LONG, + JVM_SIGNATURE_CLASS, JVM_SIGNATURE_ARRAY, + JVM_SIGNATURE_VOID, 0, + 0, 0, 0, 0 + }; + + static boolean hasEnvelope(char signature_char) { + return signature_char == JVM_SIGNATURE_CLASS; + } + + private BasicType type; + private final String signature; + private final int limit; + private int begin, end, arrayPrefix, state; + + private static final int S_FIELD = 0, S_METHOD = 1, S_METHOD_RETURN = 3; + + boolean atReturnType() { + return state == S_METHOD_RETURN; + } + + boolean isReference() { + return isReferenceType(type); + } + + BasicType type() { + return type; + } + + private int rawSymbolBegin() { + return begin + (hasEnvelope() ? 1 : 0); + } + + private int rawSymbolEnd() { + return end - (hasEnvelope() ? 1 : 0); + } + + private boolean hasEnvelope() { + return hasEnvelope(signature.charAt(begin)); + } + + String asSymbol() { + int begin = rawSymbolBegin(); + int end = rawSymbolEnd(); + return signature.substring(begin, end); + } + + int skipArrayPrefix(int max_skip_length) { + if (type != BasicType.T_ARRAY) { + return 0; + } + if (arrayPrefix > max_skip_length) { + // strip some but not all levels of T_ARRAY + arrayPrefix -= max_skip_length; + begin += max_skip_length; + return max_skip_length; + } + return skipWholeArrayPrefix(); + } + + static BasicType decodeSignatureChar(char ch) { + return BasicType.fromSignature(ch); + } + + private final VerifierImpl context; + + VerificationSignature(String signature, boolean is_method, VerifierImpl context) { + this.signature = signature; + this.limit = signature.length(); + int oz = is_method ? S_METHOD : S_FIELD; + this.state = oz; + this.begin = this.end = oz; + this.arrayPrefix = 0; + this.context = context; + next(); + } + + private int scanType(BasicType type) { + int e = end; + int tem; + switch (type) { + case T_OBJECT: + tem = signature.indexOf(JVM_SIGNATURE_ENDCLASS, e); + return tem < 0 ? limit : tem + 1; + case T_ARRAY: + while (e < limit && signature.charAt(e) == JVM_SIGNATURE_ARRAY) { + e++; + } + arrayPrefix = e - end; + if (hasEnvelope(signature.charAt(e))) { + tem = signature.indexOf(JVM_SIGNATURE_ENDCLASS, e); + return tem < 0 ? limit : tem + 1; + } + return e + 1; + default: + return e + 1; + } + } + + void next() { + final String sig = signature; + int len = limit; + testLen(len); + begin = end; + char ch = sig.charAt(begin); + if (ch == JVM_SIGNATURE_ENDFUNC) { + state = S_METHOD_RETURN; + begin = ++end; + testLen(len); + ch = sig.charAt(begin); + } + try { + BasicType bt = decodeSignatureChar(ch); + type = bt; + end = scanType(bt); + } catch (IllegalArgumentException iae) { + throw new IllegalArgumentException("Not a valid signature: '" + signature + "'", iae); + } + } + + private void testLen(int len) { + if (end >= len) { + if (context == null) { + throw new IllegalArgumentException("Invalid signature " + signature); + } else { + context.verifyError("Invalid signature " + signature); + } + } + } + + int skipWholeArrayPrefix() { + int whole_array_prefix = arrayPrefix; + int new_begin = begin + whole_array_prefix; + begin = new_begin; + char ch = signature.charAt(new_begin); + BasicType bt = decodeSignatureChar(ch); + type = bt; + return whole_array_prefix; + } + + @SuppressWarnings("fallthrough") + static int isValidType(String type, int limit) { + int index = 0; + + // Iterate over any number of array dimensions + while (index < limit && type.charAt(index) == JVM_SIGNATURE_ARRAY) { + ++index; + } + if (index >= limit) { + return -1; + } + switch (type.charAt(index)) { + case JVM_SIGNATURE_BYTE: + case JVM_SIGNATURE_CHAR: + case JVM_SIGNATURE_FLOAT: + case JVM_SIGNATURE_DOUBLE: + case JVM_SIGNATURE_INT: + case JVM_SIGNATURE_LONG: + case JVM_SIGNATURE_SHORT: + case JVM_SIGNATURE_BOOLEAN: + case JVM_SIGNATURE_VOID: + return index + 1; + case JVM_SIGNATURE_CLASS: + for (index = index + 1; index < limit; ++index) { + char c = type.charAt(index); + switch (c) { + case JVM_SIGNATURE_ENDCLASS: + return index + 1; + case '\0': + case JVM_SIGNATURE_DOT: + case JVM_SIGNATURE_ARRAY: + return -1; + default: ; // fall through + } + } + // fall through + default: ; // fall through + } + return -1; + } + + static boolean isValidMethodSignature(String method_sig) { + if (method_sig != null) { + int len = method_sig.length(); + int index = 0; + if (len > 1 && method_sig.charAt(index) == JVM_SIGNATURE_FUNC) { + ++index; + while (index < len && method_sig.charAt(index) != JVM_SIGNATURE_ENDFUNC) { + int res = isValidType(method_sig.substring(index), len - index); + if (res == -1) { + return false; + } else { + index += res; + } + } + if (index < len && method_sig.charAt(index) == JVM_SIGNATURE_ENDFUNC) { + // check the return type + ++index; + return (isValidType(method_sig.substring(index), len - index) == (len - index)); + } + } + } + return false; + } + + static boolean isValidTypeSignature(String sig) { + if (sig == null) return false; + int len = sig.length(); + return (len >= 1 && (isValidType(sig, len) == len)); + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable$StackMapReader.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable$StackMapReader.class new file mode 100644 index 00000000..1cec8a2e Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable$StackMapReader.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable$StackMapStream.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable$StackMapStream.class new file mode 100644 index 00000000..b599486c Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable$StackMapStream.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable.class new file mode 100644 index 00000000..91681415 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable.java b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable.java new file mode 100644 index 00000000..db07e0aa --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationTable.java @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl.verifier; + +import static jdk.internal.classfile.impl.verifier.VerificationType.*; + +/** + * @see hotspot/share/classfile/stackMapTable.hpp + * @see hotspot/share/classfile/stackMapTable.cpp + */ +class VerificationTable { + + private final int _code_length; + private final int _frame_count; + private final VerificationFrame[] _frame_array; + private final VerifierImpl _verifier; + + int get_frame_count() { + return _frame_count; + } + + int get_offset(int index) { + return _frame_array[index].offset(); + } + + static class StackMapStream { + + private final byte[] _data; + private int _index; + private final VerifierImpl _verifier; + + StackMapStream(byte[] ah, VerifierImpl context) { + _data = ah; + _index = 0; + _verifier = context; + } + + int get_u1() { + if (_data == null || _index >= _data.length) { + _verifier.classError("access beyond the end of attribute"); + } + return _data[_index++] & 0xff; + } + + int get_u2() { + int res = get_u1() << 8; + return res | get_u1(); + } + + boolean at_end() { + return (_data == null) || (_index == _data.length); + } + } + + VerificationTable(byte[] stackmap_data, VerificationFrame init_frame, int max_locals, int max_stack, byte[] code_data, int code_len, + VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl v) { + _verifier = v; + var reader = new StackMapReader(stackmap_data, code_data, code_len, cp, v); + _code_length = code_len; + _frame_count = reader.get_frame_count(); + _frame_array = new VerificationFrame[_frame_count]; + if (_frame_count > 0) { + VerificationFrame pre_frame = init_frame; + for (int i = 0; i < _frame_count; i++) { + VerificationFrame frame = reader.next(pre_frame, i == 0, max_locals, max_stack); + _frame_array[i] = frame; + int offset = frame.offset(); + if (offset >= code_len || code_data[offset] == 0) { + _verifier.verifyError("StackMapTable error: bad offset"); + } + pre_frame = frame; + } + } + reader.check_end(); + } + + int get_index_from_offset(int offset) { + int i = 0; + for (; i < _frame_count; i++) { + if (_frame_array[i].offset() == offset) { + return i; + } + } + return i; + } + + boolean match_stackmap(VerificationFrame frame, int target, boolean match, boolean update) { + int index = get_index_from_offset(target); + return match_stackmap(frame, target, index, match, update); + } + + boolean match_stackmap(VerificationFrame frame, int target, int frame_index, boolean match, boolean update) { + if (frame_index < 0 || frame_index >= _frame_count) { + _verifier.verifyError(String.format("Expecting a stackmap frame at branch target %d", target)); + } + VerificationFrame stackmap_frame = _frame_array[frame_index]; + boolean result = true; + if (match) { + result = frame.is_assignable_to(stackmap_frame); + } + if (update) { + int lsize = stackmap_frame.locals_size(); + int ssize = stackmap_frame.stack_size(); + if (frame.locals_size() > lsize || frame.stack_size() > ssize) { + frame.reset(); + } + frame.set_locals_size(lsize); + frame.copy_locals(stackmap_frame); + frame.set_stack_size(ssize); + frame.copy_stack(stackmap_frame); + frame.set_flags(stackmap_frame.flags()); + } + return result; + } + + void check_jump_target(VerificationFrame frame, int target) { + boolean match = match_stackmap(frame, target, true, false); + if (!match || (target < 0 || target >= _code_length)) { + _verifier.verifyError(String.format("Inconsistent stackmap frames at branch target %d", target)); + } + } + + static class StackMapReader { + + private final VerificationWrapper.ConstantPoolWrapper _cp; + private final StackMapStream _stream; + private final byte[] _code_data; + private final int _code_length; + private final int _frame_count; + + void check_verification_type_array_size(int size, int max_size) { + if (size < 0 || size > max_size) { + _verifier.classError("StackMapTable format error: bad type array size"); + } + } + + private static final int + SAME_LOCALS_1_STACK_ITEM_EXTENDED = 247, + SAME_EXTENDED = 251, + FULL = 255; + + public int get_frame_count() { + return _frame_count; + } + + public void check_end() { + if (!_stream.at_end()) { + _verifier.classError("wrong attribute size"); + } + } + + private final VerifierImpl _verifier; + + public StackMapReader(byte[] stackmapData, byte[] code_data, int code_len, VerificationWrapper.ConstantPoolWrapper cp, VerifierImpl context) { + this._verifier = context; + _stream = new StackMapStream(stackmapData, _verifier); + if (stackmapData != null) { + _frame_count = _stream.get_u2(); + } else { + _frame_count = 0; + } + _code_data = code_data; + _code_length = code_len; + _cp = cp; + } + + int chop(VerificationType[] locals, int length, int chops) { + if (locals == null) return -1; + int pos = length - 1; + for (int i=0; i= nconstants || _cp.tagAt(class_index) != VerifierImpl.JVM_CONSTANT_Class) { + _verifier.classError("bad class index"); + } + return VerificationType.reference_type(_cp.classNameAt(class_index)); + } + if (tag == ITEM_UninitializedThis) { + if (flags != null) { + flags[0] |= VerificationFrame.FLAG_THIS_UNINIT; + } + return VerificationType.uninitialized_this_type; + } + if (tag == ITEM_Uninitialized) { + int offset = _stream.get_u2(); + if (offset >= _code_length || _code_data[offset] != VerifierImpl.NEW_OFFSET) { + _verifier.classError("StackMapTable format error: bad offset for Uninitialized"); + } + return VerificationType.uninitialized_type(offset); + } + _verifier.classError("bad verification type"); + return VerificationType.bogus_type; + } + + public VerificationFrame next(VerificationFrame pre_frame, boolean first, int max_locals, int max_stack) { + VerificationFrame frame; + int offset; + VerificationType[] locals = null; + int frame_type = _stream.get_u1(); + if (frame_type < 64) { + if (first) { + offset = frame_type; + if (pre_frame.locals_size() > 0) { + locals = new VerificationType[pre_frame.locals_size()]; + } + } else { + offset = pre_frame.offset() + frame_type + 1; + locals = pre_frame.locals(); + } + frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), 0, max_locals, max_stack, locals, null, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } + if (frame_type < 128) { + if (first) { + offset = frame_type - 64; + if (pre_frame.locals_size() > 0) { + locals = new VerificationType[pre_frame.locals_size()]; + } + } else { + offset = pre_frame.offset() + frame_type - 63; + locals = pre_frame.locals(); + } + VerificationType[] stack = new VerificationType[2]; + int stack_size = 1; + stack[0] = parse_verification_type(null); + if (stack[0].is_category2()) { + stack[1] = stack[0].to_category2_2nd(_verifier); + stack_size = 2; + } + check_verification_type_array_size(stack_size, max_stack); + frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), stack_size, max_locals, max_stack, locals, stack, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } + int offset_delta = _stream.get_u2(); + if (frame_type < SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + _verifier.classError("reserved frame type"); + } + if (frame_type == SAME_LOCALS_1_STACK_ITEM_EXTENDED) { + if (first) { + offset = offset_delta; + if (pre_frame.locals_size() > 0) { + locals = new VerificationType[pre_frame.locals_size()]; + } + } else { + offset = pre_frame.offset() + offset_delta + 1; + locals = pre_frame.locals(); + } + VerificationType[] stack = new VerificationType[2]; + int stack_size = 1; + stack[0] = parse_verification_type(null); + if (stack[0].is_category2()) { + stack[1] = stack[0].to_category2_2nd(_verifier); + stack_size = 2; + } + check_verification_type_array_size(stack_size, max_stack); + frame = new VerificationFrame(offset, pre_frame.flags(), pre_frame.locals_size(), stack_size, max_locals, max_stack, locals, stack, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } + if (frame_type <= SAME_EXTENDED) { + locals = pre_frame.locals(); + int length = pre_frame.locals_size(); + int chops = SAME_EXTENDED - frame_type; + int new_length = length; + int flags = pre_frame.flags(); + if (chops != 0) { + new_length = chop(locals, length, chops); + check_verification_type_array_size(new_length, max_locals); + flags = 0; + for (int i=0; i 0) { + locals = new VerificationType[new_length]; + } else { + locals = null; + } + } else { + offset = pre_frame.offset() + offset_delta + 1; + } + frame = new VerificationFrame(offset, flags, new_length, 0, max_locals, max_stack, locals, null, _verifier); + if (first && locals != null) { + frame.copy_locals(pre_frame); + } + return frame; + } else if (frame_type < SAME_EXTENDED + 4) { + int appends = frame_type - SAME_EXTENDED; + int real_length = pre_frame.locals_size(); + int new_length = real_length + appends*2; + locals = new VerificationType[new_length]; + VerificationType[] pre_locals = pre_frame.locals(); + int i; + for (i=0; i 0) { + locals = new VerificationType[locals_size*2]; + } + int i; + for (i=0; i 0) { + stack = new VerificationType[stack_size*2]; + } + for (i=0; ihotspot/share/classfile/verificationType.hpp + * @see hotspot/share/classfile/verificationType.cpp + */ +class VerificationType { + + private static final int BitsPerByte = 8; + + static final int + ITEM_Top = 0, + ITEM_Integer = 1, + ITEM_Float = 2, + ITEM_Double = 3, + ITEM_Long = 4, + ITEM_Null = 5, + ITEM_UninitializedThis = 6, + ITEM_Object = 7, + ITEM_Uninitialized = 8, + ITEM_Bogus = -1; + + VerificationType(String sym) { + _data = 0x100; + _sym = sym; + } + public VerificationType(int data, String sym) { + _data = data; + _sym = sym; + } + private final int _data; + private final String _sym; + + @Override + public int hashCode() { + return _sym == null ? _data : _sym.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof VerificationType ? (_data == ((VerificationType)obj)._data) && Objects.equals(_sym, ((VerificationType)obj)._sym) : false; + } + + private static final Map _constantsMap = new IdentityHashMap<>(18); + + @Override + public String toString() { + if (_constantsMap.isEmpty()) { + for (Field f : VerificationType.class.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers()) && f.getType() == VerificationType.class) try { + _constantsMap.put((VerificationType)f.get(null), f.getName()); + } catch (IllegalAccessException ignore) {} + } + } + if (_sym != null) return _sym; + if ((_data & 0xff) == Uninitialized) return "uninit@" + (_data >> 8); + return _constantsMap.getOrDefault(this, java.lang.Integer.toHexString(_data)); + } + + String name() { + return _sym; + } + private static final int + ITEM_Boolean = 9, ITEM_Byte = 10, ITEM_Short = 11, ITEM_Char = 12, + ITEM_Long_2nd = 13, ITEM_Double_2nd = 14; + + private static final int + TypeMask = 0x00000003, + // Topmost types encoding + Reference = 0x0, // _sym contains the name + Primitive = 0x1, // see below for primitive list + Uninitialized = 0x2, // 0x00ffff00 contains bci + TypeQuery = 0x3, // Meta-types used for category testing + // Utility flags + ReferenceFlag = 0x00, // For reference query types + Category1Flag = 0x01, // One-word values + Category2Flag = 0x02, // First word of a two-word value + Category2_2ndFlag = 0x04, // Second word of a two-word value + // special reference values + Null = 0x00000000, // A reference with a 0 sym is null + // Primitives categories (the second byte determines the category) + Category1 = (Category1Flag << BitsPerByte) | Primitive, + Category2 = (Category2Flag << BitsPerByte) | Primitive, + Category2_2nd = (Category2_2ndFlag << BitsPerByte) | Primitive, + // Primitive values (type discriminator stored in most-significant bytes) + // Bogus needs the " | Primitive". Else, isReference(Bogus) returns TRUE. + Bogus = (ITEM_Bogus << 2 * BitsPerByte) | Primitive, + Boolean = (ITEM_Boolean << 2 * BitsPerByte) | Category1, + Byte = (ITEM_Byte << 2 * BitsPerByte) | Category1, + Short = (ITEM_Short << 2 * BitsPerByte) | Category1, + Char = (ITEM_Char << 2 * BitsPerByte) | Category1, + Integer = (ITEM_Integer << 2 * BitsPerByte) | Category1, + Float = (ITEM_Float << 2 * BitsPerByte) | Category1, + Long = (ITEM_Long << 2 * BitsPerByte) | Category2, + Double = (ITEM_Double << 2 * BitsPerByte) | Category2, + Long_2nd = (ITEM_Long_2nd << 2 * BitsPerByte) | Category2_2nd, + Double_2nd = (ITEM_Double_2nd << 2 * BitsPerByte) | Category2_2nd, + // Used by Uninitialized (second and third bytes hold the bci) + BciMask = 0xffff << BitsPerByte, + // A bci of -1 is an Uninitialized-This + BciForThis = 0xffff, + // Query values + ReferenceQuery = (ReferenceFlag << BitsPerByte) | TypeQuery, + Category1Query = (Category1Flag << BitsPerByte) | TypeQuery, + Category2Query = (Category2Flag << BitsPerByte) | TypeQuery, + Category2_2ndQuery = (Category2_2ndFlag << BitsPerByte) | TypeQuery; + + VerificationType(int raw_data) { + this._data = raw_data; + this._sym = null; + } + + static final VerificationType bogus_type = new VerificationType(Bogus), + null_type = new VerificationType(Null), + integer_type = new VerificationType(Integer), + float_type = new VerificationType(Float), + long_type = new VerificationType(Long), + long2_type = new VerificationType(Long_2nd), + double_type = new VerificationType(Double), + boolean_type = new VerificationType(Boolean), + byte_type = new VerificationType(Byte), + char_type = new VerificationType(Char), + short_type = new VerificationType(Short), + double2_type = new VerificationType(Double_2nd), + // "check" types are used for queries. A "check" type is not assignable + // to anything, but the specified types are assignable to a "check". For + // example, any category1 primitive is assignable to category1_check and + // any reference is assignable to reference_check. + reference_check = new VerificationType(ReferenceQuery), + category1_check = new VerificationType(Category1Query), + category2_check = new VerificationType(Category2Query); + + static VerificationType reference_type(String sh) { + return new VerificationType(sh); + } + + static VerificationType uninitialized_type(int bci) { + return new VerificationType(bci << 1 * BitsPerByte | Uninitialized); + } + + static final VerificationType uninitialized_this_type = uninitialized_type(BciForThis); + + boolean is_bogus() { + return (_data == Bogus); + } + + boolean is_null() { + return (_data == Null); + } + + boolean is_integer() { + return (_data == Integer); + } + + boolean is_long() { + return (_data == Long); + } + + boolean is_double() { + return (_data == Double); + } + + boolean is_long2() { + return (_data == Long_2nd ); + } + + boolean is_double2() { + return (_data == Double_2nd); + } + + boolean is_reference() { + return ((_data & TypeMask) == Reference); + } + + boolean is_category1(VerifierImpl context) { + // This should return true for all one-word types, which are category1 + // primitives, and references (including uninitialized refs). Though + // the 'query' types should technically return 'false' here, if we + // allow this to return true, we can perform the test using only + // 2 operations rather than 8 (3 masks, 3 compares and 2 logical 'ands'). + // Since no one should call this on a query type anyway, this is ok. + if(is_check()) context.verifyError("Must not be a check type (wrong value returned)"); + // should only return false if it's a primitive, and the category1 flag + // is not set. + return ((_data & Category1) != Primitive); + } + + boolean is_category2() { + return ((_data & Category2) == Category2); + } + + boolean is_category2_2nd() { + return ((_data & Category2_2nd) == Category2_2nd); + } + + boolean is_check() { + return (_data & TypeQuery) == TypeQuery; + } + + boolean is_x_array(char sig) { + return is_null() || (is_array() &&(name().charAt(1) == sig)); + } + + boolean is_int_array() { + return is_x_array(JVM_SIGNATURE_INT); + } + + boolean is_byte_array() { + return is_x_array(JVM_SIGNATURE_BYTE); + } + + boolean is_bool_array() { + return is_x_array(JVM_SIGNATURE_BOOLEAN); + } + + boolean is_char_array() { + return is_x_array(JVM_SIGNATURE_CHAR); + } + + boolean is_short_array() { + return is_x_array(JVM_SIGNATURE_SHORT); + } + + boolean is_long_array() { + return is_x_array(JVM_SIGNATURE_LONG); + } + + boolean is_float_array() { + return is_x_array(JVM_SIGNATURE_FLOAT); + } + + boolean is_double_array() { + return is_x_array(JVM_SIGNATURE_DOUBLE); + } + + boolean is_object_array() { + return is_x_array(JVM_SIGNATURE_CLASS); + } + + boolean is_array_array() { + return is_x_array(JVM_SIGNATURE_ARRAY); + } + + boolean is_reference_array() { + return is_object_array() || is_array_array(); + } + + boolean is_object() { + return (is_reference() && !is_null() && name().length() >= 1 && name().charAt(0) != JVM_SIGNATURE_ARRAY); + } + + boolean is_array() { + return (is_reference() && !is_null() && name().length() >= 2 && name().charAt(0) == JVM_SIGNATURE_ARRAY); + } + + boolean is_uninitialized() { + return ((_data & Uninitialized) == Uninitialized); + } + + boolean is_uninitialized_this(VerifierImpl context) { + return is_uninitialized() && bci(context) == BciForThis; + } + + VerificationType to_category2_2nd(VerifierImpl context) { + if (!(is_category2())) context.verifyError("Must be a double word"); + return is_long() ? long2_type : double2_type; + } + + int bci(VerifierImpl context) { + if (!(is_uninitialized())) context.verifyError("Must be uninitialized type"); + return ((_data & BciMask) >> 1 * BitsPerByte); + } + + boolean is_assignable_from(VerificationType from, VerifierImpl context) { + boolean ret = _is_assignable_from(from, context); + context.errorContext = ret ? "" : String.format("(%s is not assignable from %s)", this, from); + return ret; + } + + private boolean _is_assignable_from(VerificationType from, VerifierImpl context) { + if (equals(from) || is_bogus()) { + return true; + } else { + switch(_data) { + case Category1Query: + return from.is_category1(context); + case Category2Query: + return from.is_category2(); + case Category2_2ndQuery: + return from.is_category2_2nd(); + case ReferenceQuery: + return from.is_reference() || from.is_uninitialized(); + case Boolean: + case Byte: + case Char: + case Short: + return from.is_integer(); + default: + if (is_reference() && from.is_reference()) { + return is_reference_assignable_from(from, context); + } else { + return false; + } + } + } + } + + // Check to see if one array component type is assignable to another. + // Same as is_assignable_from() except int primitives must be identical. + boolean is_component_assignable_from(VerificationType from, VerifierImpl context) { + if (equals(from) || is_bogus()) { + return true; + } else { + switch (_data) { + case Boolean: + case Byte: + case Char: + case Short: + return false; + default: + return is_assignable_from(from, context); + } + } + } + + int dimensions(VerifierImpl context) { + if (!(is_array())) context.verifyError("Must be an array"); + int index = 0; + while (name().charAt(index) == JVM_SIGNATURE_ARRAY) index++; + return index; + } + + static VerificationType from_tag(int tag, VerifierImpl context) { + switch (tag) { + case ITEM_Top: return bogus_type; + case ITEM_Integer: return integer_type; + case ITEM_Float: return float_type; + case ITEM_Double: return double_type; + case ITEM_Long: return long_type; + case ITEM_Null: return null_type; + default: + context.verifyError("Should not reach here"); + return bogus_type; + } + } + + boolean resolve_and_check_assignability(ClassHierarchyImpl assignResolver, String name, String from_name, boolean from_is_array, boolean from_is_object) { + //let's delegate assignability to SPI + var desc = Util.toClassDesc(name); + if (assignResolver.isInterface(desc)) { + return !from_is_array || "java/lang/Cloneable".equals(name) || "java/io/Serializable".equals(name); + } else if (from_is_object) { + return assignResolver.isAssignableFrom(desc, Util.toClassDesc(from_name)); + } + return false; + } + + boolean is_reference_assignable_from(VerificationType from, VerifierImpl context) { + ClassHierarchyImpl clsTree = context.class_hierarchy(); + if (from.is_null()) { + return true; + } else if (is_null()) { + return false; + } else if (name().equals(from.name())) { + return true; + } else if (is_object()) { + if (VerifierImpl.java_lang_Object.equals(name())) { + return true; + } + return resolve_and_check_assignability(clsTree, name(), from.name(), from.is_array(), from.is_object()); + } else if (is_array() && from.is_array()) { + VerificationType comp_this = get_component(context); + VerificationType comp_from = from.get_component(context); + if (!comp_this.is_bogus() && !comp_from.is_bogus()) { + return comp_this.is_component_assignable_from(comp_from, context); + } + } + return false; + } + + VerificationType get_component(VerifierImpl context) { + if (!(is_array() && name().length() >= 2)) context.verifyError("Must be a valid array"); + var ss = new VerificationSignature(name(), false, context); + ss.skipArrayPrefix(1); + switch (ss.type()) { + case T_BOOLEAN: return VerificationType.boolean_type; + case T_BYTE: return VerificationType.byte_type; + case T_CHAR: return VerificationType.char_type; + case T_SHORT: return VerificationType.short_type; + case T_INT: return VerificationType.integer_type; + case T_LONG: return VerificationType.long_type; + case T_FLOAT: return VerificationType.float_type; + case T_DOUBLE: return VerificationType.double_type; + case T_ARRAY: + case T_OBJECT: { + if (!(ss.isReference())) context.verifyError("Unchecked verifier input"); + String component = ss.asSymbol(); + return VerificationType.reference_type(component); + } + default: + return VerificationType.bogus_type; + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper$ConstantPoolWrapper.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper$ConstantPoolWrapper.class new file mode 100644 index 00000000..057649cd Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper$ConstantPoolWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper$MethodWrapper.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper$MethodWrapper.class new file mode 100644 index 00000000..ed4c339a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper$MethodWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper.class new file mode 100644 index 00000000..3abca862 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper.java b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper.java new file mode 100644 index 00000000..d34f579b --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerificationWrapper.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.classfile.impl.verifier; + +import java.lang.constant.ClassDesc; +import java.util.LinkedList; +import java.util.List; + +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.DynamicConstantPoolEntry; +import java.lang.classfile.constantpool.MemberRefEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import java.lang.reflect.AccessFlag; +import java.util.stream.Collectors; +import java.lang.classfile.ClassModel; +import java.lang.classfile.constantpool.ConstantPool; +import java.lang.classfile.MethodModel; +import java.lang.classfile.attribute.LocalVariableInfo; +import java.lang.classfile.Attributes; +import jdk.internal.classfile.impl.BoundAttribute; +import jdk.internal.classfile.impl.CodeImpl; +import jdk.internal.classfile.impl.Util; + +public final class VerificationWrapper { + private final ClassModel clm; + private final ConstantPoolWrapper cp; + + public VerificationWrapper(ClassModel clm) { + this.clm = clm; + this.cp = new ConstantPoolWrapper(clm.constantPool()); + } + + String thisClassName() { + return clm.thisClass().asInternalName(); + } + + int majorVersion() { + return clm.majorVersion(); + } + + String superclassName() { + return clm.superclass().map(ClassEntry::asInternalName).orElse(null); + } + + Iterable interfaceNames() { + return Util.mappedList(clm.interfaces(), ClassEntry::asInternalName); + } + + Iterable methods() { + return clm.methods().stream().map(m -> new MethodWrapper(m)).toList(); + } + + boolean findField(String name, String sig) { + for (var f : clm.fields()) + if (f.fieldName().stringValue().equals(name) && f.fieldType().stringValue().equals(sig)) + return true; + return false; + } + + class MethodWrapper { + + final MethodModel m; + private final CodeImpl c; + private final List exc; + + MethodWrapper(MethodModel m) { + this.m = m; + this.c = (CodeImpl)m.code().orElse(null); + exc = new LinkedList<>(); + if (c != null) c.iterateExceptionHandlers((start, end, handler, catchType) -> { + exc.add(new int[] {start, end, handler, catchType}); + }); + } + + ConstantPoolWrapper constantPool() { + return cp; + } + + boolean isNative() { + return m.flags().has(AccessFlag.NATIVE); + } + + boolean isAbstract() { + return m.flags().has(AccessFlag.ABSTRACT); + } + + boolean isBridge() { + return m.flags().has(AccessFlag.BRIDGE); + } + + boolean isStatic() { + return m.flags().has(AccessFlag.STATIC); + } + + String name() { + return m.methodName().stringValue(); + } + + int maxStack() { + return c == null ? 0 : c.maxStack(); + } + + int maxLocals() { + return c == null ? 0 : c.maxLocals(); + } + + String descriptor() { + return m.methodType().stringValue(); + } + + String parameters() { + return m.methodTypeSymbol().parameterList().stream().map(ClassDesc::displayName).collect(Collectors.joining(",")); + } + + int codeLength() { + return c == null ? 0 : c.codeLength(); + } + + byte[] codeArray() { + return c == null ? null : c.codeArray(); + } + + List exceptionTable() { + return exc; + } + + List localVariableTable() { + var attro = c.findAttribute(Attributes.localVariableTable()); + return attro.map(lvta -> lvta.localVariables()).orElse(List.of()); + } + + byte[] stackMapTableRawData() { + var attro = c.findAttribute(Attributes.stackMapTable()); + return attro.map(attr -> ((BoundAttribute) attr).contents()).orElse(null); + } + + } + + static class ConstantPoolWrapper { + + private final ConstantPool cp; + + ConstantPoolWrapper(ConstantPool cp) { + this.cp = cp; + } + + int entryCount() { + return cp.size(); + } + + String classNameAt(int index) { + return cp.entryByIndex(index, ClassEntry.class).asInternalName(); + } + + String dynamicConstantSignatureAt(int index) { + return cp.entryByIndex(index, DynamicConstantPoolEntry.class).type().stringValue(); + } + + int tagAt(int index) { + return cp.entryByIndex(index).tag(); + } + + private NameAndTypeEntry _refNameType(int index) { + var e = cp.entryByIndex(index); + return (e instanceof DynamicConstantPoolEntry de) ? de.nameAndType() : + e != null ? ((MemberRefEntry)e).nameAndType() : null; + } + + String refNameAt(int index) { + return _refNameType(index).name().stringValue(); + } + + String refSignatureAt(int index) { + return _refNameType(index).type().stringValue(); + } + + int refClassIndexAt(int index) { + return cp.entryByIndex(index, MemberRefEntry.class).owner().index(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl$1.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl$1.class new file mode 100644 index 00000000..0f8584c2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl$sig_as_verification_types.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl$sig_as_verification_types.class new file mode 100644 index 00000000..88c5be3a Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl$sig_as_verification_types.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl.class b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl.class new file mode 100644 index 00000000..81fa76d6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl.class differ diff --git a/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl.java b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl.java new file mode 100644 index 00000000..11e72567 --- /dev/null +++ b/tests/test_data/std/jdk/internal/classfile/impl/verifier/VerifierImpl.java @@ -0,0 +1,1843 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.classfile.impl.verifier; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.lang.classfile.ClassHierarchyResolver; +import java.lang.classfile.ClassModel; +import java.lang.classfile.components.ClassPrinter; +import java.lang.classfile.ClassFile; +import jdk.internal.classfile.impl.ClassHierarchyImpl; +import jdk.internal.classfile.impl.RawBytecodeHelper; +import static jdk.internal.classfile.impl.RawBytecodeHelper.ILLEGAL; +import jdk.internal.classfile.impl.verifier.VerificationWrapper.ConstantPoolWrapper; +import static jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType.*; +import jdk.internal.classfile.impl.verifier.VerificationSignature.BasicType; +import static jdk.internal.classfile.impl.verifier.VerificationFrame.FLAG_THIS_UNINIT; + +/** + * VerifierImpl performs selected checks and verifications of the class file + * format according to {@jvms 4.8 Format Checking}, + * {@jvms 4.9 Constraints on Java Virtual Machine code}, + * {@jvms 4.10 Verification of class Files} and {@jvms 6.5 Instructions} + * + * @see java.base/share/native/include/classfile_constants.h.template + * @see hotspot/share/classfile/verifier.hpp + * @see hotspot/share/classfile/verifier.cpp + */ +public final class VerifierImpl { + static final int + JVM_CONSTANT_Utf8 = 1, + JVM_CONSTANT_Unicode = 2, + JVM_CONSTANT_Integer = 3, + JVM_CONSTANT_Float = 4, + JVM_CONSTANT_Long = 5, + JVM_CONSTANT_Double = 6, + JVM_CONSTANT_Class = 7, + JVM_CONSTANT_String = 8, + JVM_CONSTANT_Fieldref = 9, + JVM_CONSTANT_Methodref = 10, + JVM_CONSTANT_InterfaceMethodref = 11, + JVM_CONSTANT_NameAndType = 12, + JVM_CONSTANT_MethodHandle = 15, + JVM_CONSTANT_MethodType = 16, + JVM_CONSTANT_Dynamic = 17, + JVM_CONSTANT_InvokeDynamic = 18, + JVM_CONSTANT_Module = 19, + JVM_CONSTANT_Package = 20, + JVM_CONSTANT_ExternalMax = 20; + + static final char JVM_SIGNATURE_SPECIAL = '<', + JVM_SIGNATURE_ARRAY = '[', + JVM_SIGNATURE_BYTE = 'B', + JVM_SIGNATURE_CHAR = 'C', + JVM_SIGNATURE_CLASS = 'L', + JVM_SIGNATURE_FLOAT = 'F', + JVM_SIGNATURE_DOUBLE = 'D', + JVM_SIGNATURE_INT = 'I', + JVM_SIGNATURE_LONG = 'J', + JVM_SIGNATURE_SHORT = 'S', + JVM_SIGNATURE_BOOLEAN = 'Z'; + + static final String java_lang_String = "java/lang/String"; + static final String object_initializer_name = ""; + static final String java_lang_invoke_MethodHandle = "java/lang/invoke/MethodHandle"; + static final String java_lang_Object = "java/lang/Object"; + static final String java_lang_invoke_MethodType = "java/lang/invoke/MethodType"; + static final String java_lang_Throwable = "java/lang/Throwable"; + static final String java_lang_Class = "java/lang/Class"; + + String errorContext = ""; + private int bci; + + static void log_info(Consumer logger, String messageFormat, Object... args) { + if (logger != null) logger.accept(String.format(messageFormat + "%n", args)); + } + private final Consumer _logger; + void log_info(String messageFormat, Object... args) { + log_info(_logger, messageFormat, args); + } + + + static final int STACKMAP_ATTRIBUTE_MAJOR_VERSION = 50; + static final int INVOKEDYNAMIC_MAJOR_VERSION = 51; + static final int NOFAILOVER_MAJOR_VERSION = 51; + static final int MAX_CODE_SIZE = 65535; + + public static List verify(ClassModel classModel, Consumer logger) { + return verify(classModel, ClassHierarchyResolver.defaultResolver(), logger); + } + + public static List verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer logger) { + var klass = new VerificationWrapper(classModel); + log_info(logger, "Start class verification for: %s", klass.thisClassName()); + try { + var errors = new ArrayList(); + errors.addAll(new ParserVerifier(classModel).verify()); + if (is_eligible_for_verification(klass)) { + if (klass.majorVersion() >= STACKMAP_ATTRIBUTE_MAJOR_VERSION) { + var verifierErrors = new VerifierImpl(klass, classHierarchyResolver, logger).verify_class(); + if (!verifierErrors.isEmpty() && klass.majorVersion() < NOFAILOVER_MAJOR_VERSION) { + log_info(logger, "Fail over class verification to old verifier for: %s", klass.thisClassName()); + errors.addAll(inference_verify(klass)); + } else { + errors.addAll(verifierErrors); + } + } else { + errors.addAll(inference_verify(klass)); + } + } + return errors; + } finally { + log_info(logger, "End class verification for: %s", klass.thisClassName()); + } + } + + public static boolean is_eligible_for_verification(VerificationWrapper klass) { + String name = klass.thisClassName(); + return !java_lang_Object.equals(name) && + !java_lang_Class.equals(name) && + !java_lang_String.equals(name) && + !java_lang_Throwable.equals(name); + } + + static List inference_verify(VerificationWrapper klass) { + return List.of(new VerifyError("Inference verification is not supported")); + } + + static class sig_as_verification_types { + private int _num_args; + private ArrayList _sig_verif_types; + + sig_as_verification_types(ArrayList sig_verif_types) { + this._sig_verif_types = sig_verif_types; + this._num_args = 0; + } + + int num_args() { + return _num_args; + } + + void set_num_args(int num_args) { + _num_args = num_args; + } + + ArrayList sig_verif_types() { + return _sig_verif_types; + } + } + + VerificationType cp_ref_index_to_type(int index, ConstantPoolWrapper cp) { + return cp_index_to_type(cp.refClassIndexAt(index), cp); + } + + final VerificationWrapper _klass; + final ClassHierarchyImpl _class_hierarchy; + VerificationWrapper.MethodWrapper _method; + VerificationType _this_type; + + static final int BYTECODE_OFFSET = 1, NEW_OFFSET = 2; + + VerificationWrapper current_class() { + return _klass; + } + + ClassHierarchyImpl class_hierarchy() { + return _class_hierarchy; + } + + VerificationType current_type() { + return _this_type; + } + + VerificationType cp_index_to_type(int index, ConstantPoolWrapper cp) { + return VerificationType.reference_type(cp.classNameAt(index)); + } + + int change_sig_to_verificationType(VerificationSignature sig_type, VerificationType inference_types[], int inference_type_index) { + BasicType bt = sig_type.type(); + switch (bt) { + case T_OBJECT: + case T_ARRAY: + String name = sig_type.asSymbol(); + inference_types[inference_type_index] = VerificationType.reference_type(name); + return 1; + case T_LONG: + inference_types[inference_type_index] = VerificationType.long_type; + inference_types[++inference_type_index] = VerificationType.long2_type; + return 2; + case T_DOUBLE: + inference_types[inference_type_index] = VerificationType.double_type; + inference_types[++inference_type_index] = VerificationType.double2_type; + return 2; + case T_INT: + case T_BOOLEAN: + case T_BYTE: + case T_CHAR: + case T_SHORT: + inference_types[inference_type_index] = VerificationType.integer_type; + return 1; + case T_FLOAT: + inference_types[inference_type_index] = VerificationType.float_type; + return 1; + default: + verifyError("Should not reach here"); + return 1; + } + } + + private static final int NONZERO_PADDING_BYTES_IN_SWITCH_MAJOR_VERSION = 51; + private static final int STATIC_METHOD_IN_INTERFACE_MAJOR_VERSION = 52; + private static final int MAX_ARRAY_DIMENSIONS = 255; + + VerifierImpl(VerificationWrapper klass, ClassHierarchyResolver classHierarchyResolver, Consumer logger) { + _klass = klass; + _class_hierarchy = new ClassHierarchyImpl(classHierarchyResolver); + _this_type = VerificationType.reference_type(klass.thisClassName()); + _logger = logger; + } + + private VerificationType object_type() { + return VerificationType.reference_type(java_lang_Object); + } + + List verify_class() { + log_info("Verifying class %s with new format", _klass.thisClassName()); + var errors = new ArrayList(); + for (VerificationWrapper.MethodWrapper m : _klass.methods()) { + if (m.isNative() || m.isAbstract() || m.isBridge()) { + continue; + } + verify_method(m, errors); + } + return errors; + } + + void translate_signature(String method_sig, sig_as_verification_types sig_verif_types) { + var sig_stream = new VerificationSignature(method_sig, true, this); + VerificationType[] sig_type = new VerificationType[2]; + int sig_i = 0; + ArrayList verif_types = sig_verif_types.sig_verif_types(); + while (!sig_stream.atReturnType()) { + int n = change_sig_to_verificationType(sig_stream, sig_type, 0); + if (n > 2) verifyError("Unexpected signature type"); + for (int x = 0; x < n; x++) { + verif_types.add(sig_type[x]); + } + sig_i += n; + sig_stream.next(); + } + sig_verif_types.set_num_args(sig_i); + if (sig_stream.type() != BasicType.T_VOID) { + int n = change_sig_to_verificationType(sig_stream, sig_type, 0); + if (n > 2) verifyError("Unexpected signature return type"); + for (int y = 0; y < n; y++) { + verif_types.add(sig_type[y]); + } + } + } + + void create_method_sig_entry(sig_as_verification_types sig_verif_types, String method_sig) { + translate_signature(method_sig, sig_verif_types); + } + + void verify_method(VerificationWrapper.MethodWrapper m, List errorsCollector) { + try { + verify_method(m); + } catch (VerifyError err) { + errorsCollector.add(err); + } catch (Error | Exception e) { + errorsCollector.add(new VerifyError(e.toString())); + } + } + + @SuppressWarnings("fallthrough") + void verify_method(VerificationWrapper.MethodWrapper m) { + _method = m; + log_info(_logger, "Verifying method %s%s", m.name(), m.descriptor()); + byte[] codeArray = m.codeArray(); + if (codeArray == null) verifyError("Missing Code attribute"); + int max_locals = m.maxLocals(); + int max_stack = m.maxStack(); + byte[] stackmap_data = m.stackMapTableRawData(); + var cp = m.constantPool(); + if (!VerificationSignature.isValidMethodSignature(m.descriptor())) verifyError("Invalid method signature"); + VerificationFrame current_frame = new VerificationFrame(max_locals, max_stack, this); + VerificationType return_type = current_frame.set_locals_from_arg(m, current_type()); + int stackmap_index = 0; + int code_length = m.codeLength(); + if (code_length < 1 || code_length > MAX_CODE_SIZE) { + verifyError(String.format("Invalid method Code length %d", code_length)); + } + var code = ByteBuffer.wrap(codeArray, 0, _method.codeLength()); + byte[] code_data = generate_code_data(code, code_length); + int ex_minmax[] = new int[] {code_length, -1}; + verify_exception_handler_table(code_length, code_data, ex_minmax); + verify_local_variable_table(code_length, code_data); + + VerificationTable stackmap_table = new VerificationTable(stackmap_data, current_frame, max_locals, max_stack, code_data, code_length, cp, this); + + var bcs = new RawBytecodeHelper(code); + boolean no_control_flow = false; + int opcode; + while (!bcs.isLastBytecode()) { + opcode = bcs.rawNext(); + bci = bcs.bci; + current_frame.set_offset(bci); + current_frame.set_mark(); + stackmap_index = verify_stackmap_table(stackmap_index, bci, current_frame, stackmap_table, no_control_flow); + boolean this_uninit = false; + boolean verified_exc_handlers = false; + { + int index; + int target; + VerificationType type, type2 = null; + VerificationType atype; + if (bcs.isWide) { + if (opcode != ClassFile.IINC && opcode != ClassFile.ILOAD + && opcode != ClassFile.ALOAD && opcode != ClassFile.LLOAD + && opcode != ClassFile.ISTORE && opcode != ClassFile.ASTORE + && opcode != ClassFile.LSTORE && opcode != ClassFile.FLOAD + && opcode != ClassFile.DLOAD && opcode != ClassFile.FSTORE + && opcode != ClassFile.DSTORE) { + verifyError("Bad wide instruction"); + } + } + if (VerificationBytecodes.is_store_into_local(opcode) && bci >= ex_minmax[0] && bci < ex_minmax[1]) { + verify_exception_handler_targets(bci, this_uninit, current_frame, stackmap_table); + verified_exc_handlers = true; + } + switch (opcode) { + case ClassFile.NOP : + no_control_flow = false; break; + case ClassFile.ACONST_NULL : + current_frame.push_stack( + VerificationType.null_type); + no_control_flow = false; break; + case ClassFile.ICONST_M1 : + case ClassFile.ICONST_0 : + case ClassFile.ICONST_1 : + case ClassFile.ICONST_2 : + case ClassFile.ICONST_3 : + case ClassFile.ICONST_4 : + case ClassFile.ICONST_5 : + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.LCONST_0 : + case ClassFile.LCONST_1 : + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.FCONST_0 : + case ClassFile.FCONST_1 : + case ClassFile.FCONST_2 : + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case ClassFile.DCONST_0 : + case ClassFile.DCONST_1 : + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case ClassFile.SIPUSH : + case ClassFile.BIPUSH : + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.LDC : + verify_ldc( + opcode, bcs.getIndexU1(), current_frame, + cp, bci); + no_control_flow = false; break; + case ClassFile.LDC_W : + case ClassFile.LDC2_W : + verify_ldc( + opcode, bcs.getIndexU2(), current_frame, + cp, bci); + no_control_flow = false; break; + case ClassFile.ILOAD : + verify_iload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.ILOAD_0 : + case ClassFile.ILOAD_1 : + case ClassFile.ILOAD_2 : + case ClassFile.ILOAD_3 : + index = opcode - ClassFile.ILOAD_0; + verify_iload(index, current_frame); + no_control_flow = false; break; + case ClassFile.LLOAD : + verify_lload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.LLOAD_0 : + case ClassFile.LLOAD_1 : + case ClassFile.LLOAD_2 : + case ClassFile.LLOAD_3 : + index = opcode - ClassFile.LLOAD_0; + verify_lload(index, current_frame); + no_control_flow = false; break; + case ClassFile.FLOAD : + verify_fload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.FLOAD_0 : + case ClassFile.FLOAD_1 : + case ClassFile.FLOAD_2 : + case ClassFile.FLOAD_3 : + index = opcode - ClassFile.FLOAD_0; + verify_fload(index, current_frame); + no_control_flow = false; break; + case ClassFile.DLOAD : + verify_dload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.DLOAD_0 : + case ClassFile.DLOAD_1 : + case ClassFile.DLOAD_2 : + case ClassFile.DLOAD_3 : + index = opcode - ClassFile.DLOAD_0; + verify_dload(index, current_frame); + no_control_flow = false; break; + case ClassFile.ALOAD : + verify_aload(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.ALOAD_0 : + case ClassFile.ALOAD_1 : + case ClassFile.ALOAD_2 : + case ClassFile.ALOAD_3 : + index = opcode - ClassFile.ALOAD_0; + verify_aload(index, current_frame); + no_control_flow = false; break; + case ClassFile.IALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_int_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.BALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_bool_array() && !atype.is_byte_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.CALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_char_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.SALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_short_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.LALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_long_array()) { + verifyError("Bad type"); + } + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.FALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_float_array()) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case ClassFile.DALOAD : + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_double_array()) { + verifyError("Bad type"); + } + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case ClassFile.AALOAD : { + type = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_reference_array()) { + verifyError("Bad type"); + } + if (atype.is_null()) { + current_frame.push_stack( + VerificationType.null_type); + } else { + VerificationType component = + atype.get_component(this); + current_frame.push_stack(component); + } + no_control_flow = false; break; + } + case ClassFile.ISTORE : + verify_istore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.ISTORE_0 : + case ClassFile.ISTORE_1 : + case ClassFile.ISTORE_2 : + case ClassFile.ISTORE_3 : + index = opcode - ClassFile.ISTORE_0; + verify_istore(index, current_frame); + no_control_flow = false; break; + case ClassFile.LSTORE : + verify_lstore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.LSTORE_0 : + case ClassFile.LSTORE_1 : + case ClassFile.LSTORE_2 : + case ClassFile.LSTORE_3 : + index = opcode - ClassFile.LSTORE_0; + verify_lstore(index, current_frame); + no_control_flow = false; break; + case ClassFile.FSTORE : + verify_fstore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.FSTORE_0 : + case ClassFile.FSTORE_1 : + case ClassFile.FSTORE_2 : + case ClassFile.FSTORE_3 : + index = opcode - ClassFile.FSTORE_0; + verify_fstore(index, current_frame); + no_control_flow = false; break; + case ClassFile.DSTORE : + verify_dstore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.DSTORE_0 : + case ClassFile.DSTORE_1 : + case ClassFile.DSTORE_2 : + case ClassFile.DSTORE_3 : + index = opcode - ClassFile.DSTORE_0; + verify_dstore(index, current_frame); + no_control_flow = false; break; + case ClassFile.ASTORE : + verify_astore(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.ASTORE_0 : + case ClassFile.ASTORE_1 : + case ClassFile.ASTORE_2 : + case ClassFile.ASTORE_3 : + index = opcode - ClassFile.ASTORE_0; + verify_astore(index, current_frame); + no_control_flow = false; break; + case ClassFile.IASTORE : + type = current_frame.pop_stack( + VerificationType.integer_type); + type2 = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_int_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.BASTORE : + type = current_frame.pop_stack( + VerificationType.integer_type); + type2 = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_bool_array() && !atype.is_byte_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.CASTORE : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_char_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.SASTORE : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_short_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.LASTORE : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_long_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.FASTORE : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.pop_stack + (VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_float_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.DASTORE : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + if (!atype.is_double_array()) { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.AASTORE : + type = current_frame.pop_stack(object_type()); + type2 = current_frame.pop_stack( + VerificationType.integer_type); + atype = current_frame.pop_stack( + VerificationType.reference_check); + // more type-checking is done at runtime + if (!atype.is_reference_array()) { + verifyError("Bad type"); + } + // 4938384: relaxed constraint in JVMS 3nd edition. + no_control_flow = false; break; + case ClassFile.POP : + current_frame.pop_stack( + VerificationType.category1_check); + no_control_flow = false; break; + case ClassFile.POP2 : + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + no_control_flow = false; break; + case ClassFile.DUP : + type = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type); + current_frame.push_stack(type); + no_control_flow = false; break; + case ClassFile.DUP_X1 : + type = current_frame.pop_stack( + VerificationType.category1_check); + type2 = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + case ClassFile.DUP_X2 : + { + VerificationType type3 = null; + type = current_frame.pop_stack( + VerificationType.category1_check); + type2 = current_frame.pop_stack(); + if (type2.is_category1(this)) { + type3 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type2.is_category2_2nd()) { + type3 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + current_frame.push_stack(type); + current_frame.push_stack(type3); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + } + case ClassFile.DUP2 : + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + type2 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + type2 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + current_frame.push_stack(type2); + current_frame.push_stack(type); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + case ClassFile.DUP2_X1 : + { + VerificationType type3; + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + type2 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + type2 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + type3 = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type2); + current_frame.push_stack(type); + current_frame.push_stack(type3); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + } + case ClassFile.DUP2_X2 : + VerificationType type3, type4 = null; + type = current_frame.pop_stack(); + if (type.is_category1(this)) { + type2 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type.is_category2_2nd()) { + type2 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + type3 = current_frame.pop_stack(); + if (type3.is_category1(this)) { + type4 = current_frame.pop_stack( + VerificationType.category1_check); + } else if (type3.is_category2_2nd()) { + type4 = current_frame.pop_stack( + VerificationType.category2_check); + } else { + verifyError("Bad type"); + } + current_frame.push_stack(type2); + current_frame.push_stack(type); + current_frame.push_stack(type4); + current_frame.push_stack(type3); + current_frame.push_stack(type2); + current_frame.push_stack(type); + no_control_flow = false; break; + case ClassFile.SWAP : + type = current_frame.pop_stack( + VerificationType.category1_check); + type2 = current_frame.pop_stack( + VerificationType.category1_check); + current_frame.push_stack(type); + current_frame.push_stack(type2); + no_control_flow = false; break; + case ClassFile.IADD : + case ClassFile.ISUB : + case ClassFile.IMUL : + case ClassFile.IDIV : + case ClassFile.IREM : + case ClassFile.ISHL : + case ClassFile.ISHR : + case ClassFile.IUSHR : + case ClassFile.IOR : + case ClassFile.IXOR : + case ClassFile.IAND : + current_frame.pop_stack( + VerificationType.integer_type); + // fall through + case ClassFile.INEG : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.LADD : + case ClassFile.LSUB : + case ClassFile.LMUL : + case ClassFile.LDIV : + case ClassFile.LREM : + case ClassFile.LAND : + case ClassFile.LOR : + case ClassFile.LXOR : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + // fall through + case ClassFile.LNEG : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.LSHL : + case ClassFile.LSHR : + case ClassFile.LUSHR : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.FADD : + case ClassFile.FSUB : + case ClassFile.FMUL : + case ClassFile.FDIV : + case ClassFile.FREM : + current_frame.pop_stack( + VerificationType.float_type); + // fall through + case ClassFile.FNEG : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case ClassFile.DADD : + case ClassFile.DSUB : + case ClassFile.DMUL : + case ClassFile.DDIV : + case ClassFile.DREM : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + // fall through + case ClassFile.DNEG : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case ClassFile.IINC : + verify_iinc(bcs.getIndex(), current_frame); + no_control_flow = false; break; + case ClassFile.I2L : + type = current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.L2I : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.I2F : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case ClassFile.I2D : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case ClassFile.L2F : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case ClassFile.L2D : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case ClassFile.F2I : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.F2L : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.F2D : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + no_control_flow = false; break; + case ClassFile.D2I : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.D2L : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + no_control_flow = false; break; + case ClassFile.D2F : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack( + VerificationType.float_type); + no_control_flow = false; break; + case ClassFile.I2B : + case ClassFile.I2C : + case ClassFile.I2S : + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.LCMP : + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.FCMPL : + case ClassFile.FCMPG : + current_frame.pop_stack( + VerificationType.float_type); + current_frame.pop_stack( + VerificationType.float_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.DCMPL : + case ClassFile.DCMPG : + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.IF_ICMPEQ: + case ClassFile.IF_ICMPNE: + case ClassFile.IF_ICMPLT: + case ClassFile.IF_ICMPGE: + case ClassFile.IF_ICMPGT: + case ClassFile.IF_ICMPLE: + current_frame.pop_stack( + VerificationType.integer_type); + // fall through + case ClassFile.IFEQ: + case ClassFile.IFNE: + case ClassFile.IFLT: + case ClassFile.IFGE: + case ClassFile.IFGT: + case ClassFile.IFLE: + current_frame.pop_stack( + VerificationType.integer_type); + target = bcs.dest(); + stackmap_table.check_jump_target( + current_frame, target); + no_control_flow = false; break; + case ClassFile.IF_ACMPEQ : + case ClassFile.IF_ACMPNE : + current_frame.pop_stack( + VerificationType.reference_check); + // fall through + case ClassFile.IFNULL : + case ClassFile.IFNONNULL : + current_frame.pop_stack( + VerificationType.reference_check); + target = bcs.dest(); + stackmap_table.check_jump_target + (current_frame, target); + no_control_flow = false; break; + case ClassFile.GOTO : + target = bcs.dest(); + stackmap_table.check_jump_target( + current_frame, target); + no_control_flow = true; break; + case ClassFile.GOTO_W : + target = bcs.destW(); + stackmap_table.check_jump_target( + current_frame, target); + no_control_flow = true; break; + case ClassFile.TABLESWITCH : + case ClassFile.LOOKUPSWITCH : + verify_switch( + bcs, code_length, code_data, current_frame, + stackmap_table); + no_control_flow = true; break; + case ClassFile.IRETURN : + type = current_frame.pop_stack( + VerificationType.integer_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case ClassFile.LRETURN : + type2 = current_frame.pop_stack( + VerificationType.long2_type); + type = current_frame.pop_stack( + VerificationType.long_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case ClassFile.FRETURN : + type = current_frame.pop_stack( + VerificationType.float_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case ClassFile.DRETURN : + type2 = current_frame.pop_stack( + VerificationType.double2_type); + type = current_frame.pop_stack( + VerificationType.double_type); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case ClassFile.ARETURN : + type = current_frame.pop_stack( + VerificationType.reference_check); + verify_return_value(return_type, type, bci, + current_frame); + no_control_flow = true; break; + case ClassFile.RETURN: + if (!return_type.is_bogus()) { + verifyError("Method expects a return value"); + } + if (object_initializer_name.equals(_method.name()) && + current_frame.flag_this_uninit()) { + verifyError("Constructor must call super() or this() before return"); + } + no_control_flow = true; break; + case ClassFile.GETSTATIC : + case ClassFile.PUTSTATIC : + verify_field_instructions(bcs, current_frame, cp, true); + no_control_flow = false; break; + case ClassFile.GETFIELD : + case ClassFile.PUTFIELD : + verify_field_instructions(bcs, current_frame, cp, false); + no_control_flow = false; break; + case ClassFile.INVOKEVIRTUAL : + case ClassFile.INVOKESPECIAL : + case ClassFile.INVOKESTATIC : + this_uninit = verify_invoke_instructions(bcs, code_length, current_frame, (bci >= ex_minmax[0] && bci < ex_minmax[1]), this_uninit, return_type, cp, stackmap_table); + no_control_flow = false; break; + case ClassFile.INVOKEINTERFACE : + case ClassFile.INVOKEDYNAMIC : + this_uninit = verify_invoke_instructions(bcs, code_length, current_frame, (bci >= ex_minmax[0] && bci < ex_minmax[1]), this_uninit, return_type, cp, stackmap_table); + no_control_flow = false; break; + case ClassFile.NEW : + { + index = bcs.getIndexU2(); + verify_cp_class_type(bci, index, cp); + VerificationType new_class_type = + cp_index_to_type(index, cp); + if (!new_class_type.is_object()) { + verifyError("Illegal new instruction"); + } + type = VerificationType.uninitialized_type(bci); + current_frame.push_stack(type); + no_control_flow = false; break; + } + case ClassFile.NEWARRAY : + type = get_newarray_type(bcs.getIndex(), bci); + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.push_stack(type); + no_control_flow = false; break; + case ClassFile.ANEWARRAY : + verify_anewarray(bci, bcs.getIndexU2(), cp, current_frame); + no_control_flow = false; break; + case ClassFile.ARRAYLENGTH : + type = current_frame.pop_stack( + VerificationType.reference_check); + if (!(type.is_null() || type.is_array())) { + verifyError("Bad type"); + } + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + case ClassFile.CHECKCAST : + { + index = bcs.getIndexU2(); + verify_cp_class_type(bci, index, cp); + current_frame.pop_stack(object_type()); + VerificationType klass_type = cp_index_to_type( + index, cp); + current_frame.push_stack(klass_type); + no_control_flow = false; break; + } + case ClassFile.INSTANCEOF : { + index = bcs.getIndexU2(); + verify_cp_class_type(bci, index, cp); + current_frame.pop_stack(object_type()); + current_frame.push_stack( + VerificationType.integer_type); + no_control_flow = false; break; + } + case ClassFile.MONITORENTER : + case ClassFile.MONITOREXIT : + current_frame.pop_stack( + VerificationType.reference_check); + no_control_flow = false; break; + case ClassFile.MULTIANEWARRAY : + { + index = bcs.getIndexU2(); + int dim = _method.codeArray()[bcs.bci+3] & 0xff; + verify_cp_class_type(bci, index, cp); + VerificationType new_array_type = + cp_index_to_type(index, cp); + if (!new_array_type.is_array()) { + verifyError("Illegal constant pool index in multianewarray instruction"); + } + if (dim < 1 || new_array_type.dimensions(this) < dim) { + verifyError(String.format("Illegal dimension in multianewarray instruction: %d", dim)); + } + for (int i = 0; i < dim; i++) { + current_frame.pop_stack( + VerificationType.integer_type); + } + current_frame.push_stack(new_array_type); + no_control_flow = false; break; + } + case ClassFile.ATHROW : + type = VerificationType.reference_type(java_lang_Throwable); + current_frame.pop_stack(type); + no_control_flow = true; break; + default: + verifyError(String.format("Bad instruction: %02x", opcode)); + } + } + if (verified_exc_handlers && this_uninit) verifyError("Exception handler targets got verified before this_uninit got set"); + if (!verified_exc_handlers && bci >= ex_minmax[0] && bci < ex_minmax[1]) { + verify_exception_handler_targets(bci, this_uninit, current_frame, stackmap_table); + } + } + if (!no_control_flow) { + verifyError("Control flow falls through code end"); + } + } + + private byte[] generate_code_data(ByteBuffer code, int code_length) { + byte code_data[] = new byte[code_length]; + var bcs = new RawBytecodeHelper(code); + while (!bcs.isLastBytecode()) { + if (bcs.rawNext() != ILLEGAL) { + int bci = bcs.bci; + if (bcs.rawCode == ClassFile.NEW) { + code_data[bci] = NEW_OFFSET; + } else { + code_data[bci] = BYTECODE_OFFSET; + } + } else { + verifyError("Bad instruction"); + } + } + return code_data; + } + + void verify_exception_handler_table(int code_length, byte[] code_data, int[] minmax) { + var cp = _method.constantPool(); + for (var exhandler : _method.exceptionTable()) { + int start_pc = exhandler[0]; + int end_pc = exhandler[1]; + int handler_pc = exhandler[2]; + if (start_pc >= code_length || code_data[start_pc] == 0) { + classError(String.format("Illegal exception table start_pc %d", start_pc)); + } + if (end_pc != code_length) { + if (end_pc > code_length || code_data[end_pc] == 0) { + classError(String.format("Illegal exception table end_pc %d", end_pc)); + } + } + if (handler_pc >= code_length || code_data[handler_pc] == 0) { + classError(String.format("Illegal exception table handler_pc %d", handler_pc)); + } + int catch_type_index = exhandler[3]; + if (catch_type_index != 0) { + VerificationType catch_type = cp_index_to_type(catch_type_index, cp); + VerificationType throwable = VerificationType.reference_type(java_lang_Throwable); + boolean is_subclass = throwable.is_assignable_from(catch_type, this); + if (!is_subclass) { + verifyError(String.format("Catch type is not a subclass of Throwable in exception handler %d", handler_pc)); + } + } + if (start_pc < minmax[0]) minmax[0] = start_pc; + if (end_pc > minmax[1]) minmax[1] = end_pc; + } + } + + void verify_local_variable_table(int code_length, byte[] code_data) { + for (var lvte : _method.localVariableTable()) { + int start_bci = lvte.startPc(); + int length = lvte.length(); + if (start_bci >= code_length || code_data[start_bci] == 0) { + classError(String.format("Illegal local variable table start_pc %d", start_bci)); + } + int end_bci = start_bci + length; + if (end_bci != code_length) { + if (end_bci >= code_length || code_data[end_bci] == 0) { + classError(String.format("Illegal local variable table length %d", length)); + } + } + } + } + + int verify_stackmap_table(int stackmap_index, int bci, VerificationFrame current_frame, VerificationTable stackmap_table, boolean no_control_flow) { + if (stackmap_index < stackmap_table.get_frame_count()) { + int this_offset = stackmap_table.get_offset(stackmap_index); + if (no_control_flow && this_offset > bci) { + verifyError("Expecting a stack map frame"); + } + if (this_offset == bci) { + boolean matches = stackmap_table.match_stackmap(current_frame, this_offset, stackmap_index, !no_control_flow, true); + if (!matches) { + verifyError("Instruction type does not match stack map"); + } + stackmap_index++; + } else if (this_offset < bci) { + classError(String.format("Bad stack map offset %d", this_offset)); + } + } else if (no_control_flow) { + verifyError("Expecting a stack map frame"); + } + return stackmap_index; + } + + void verify_exception_handler_targets(int bci, boolean this_uninit, VerificationFrame current_frame, VerificationTable stackmap_table) { + var cp = _method.constantPool(); + for(var exhandler : _method.exceptionTable()) { + int start_pc = exhandler[0]; + int end_pc = exhandler[1]; + int handler_pc = exhandler[2]; + int catch_type_index = exhandler[3]; + if(bci >= start_pc && bci < end_pc) { + int flags = current_frame.flags(); + if (this_uninit) { flags |= FLAG_THIS_UNINIT; } + VerificationFrame new_frame = current_frame.frame_in_exception_handler(flags); + if (catch_type_index != 0) { + VerificationType catch_type = cp_index_to_type(catch_type_index, cp); + new_frame.push_stack(catch_type); + } else { + VerificationType throwable = VerificationType.reference_type(java_lang_Throwable); + new_frame.push_stack(throwable); + } + boolean matches = stackmap_table.match_stackmap(new_frame, handler_pc, true, false); + if (!matches) { + verifyError(String.format("Stack map does not match the one at exception handler %d", handler_pc)); + } + } + } + } + + void verify_cp_index(int bci, ConstantPoolWrapper cp, int index) { + int nconstants = cp.entryCount(); + if ((index <= 0) || (index >= nconstants)) { + verifyError(String.format("Illegal constant pool index %d", index)); + } + } + + void verify_cp_type(int bci, int index, ConstantPoolWrapper cp, int types) { + verify_cp_index(bci, cp, index); + int tag = cp.tagAt(index); + if ((types & (1 << tag))== 0) { + verifyError(String.format("Illegal type at constant pool entry %d", index)); + } + } + + void verify_cp_class_type(int bci, int index, ConstantPoolWrapper cp) { + verify_cp_index(bci, cp, index); + int tag = cp.tagAt(index); + if (tag != JVM_CONSTANT_Class) { + verifyError(String.format("Illegal type at constant pool entry %d", index)); + } + } + + void verify_ldc(int opcode, int index, VerificationFrame current_frame, ConstantPoolWrapper cp, int bci) { + verify_cp_index(bci, cp, index); + int tag = cp.tagAt(index); + int types = 0; + if (opcode == ClassFile.LDC || opcode == ClassFile.LDC_W) { + types = (1 << JVM_CONSTANT_Integer) | (1 << JVM_CONSTANT_Float) + | (1 << JVM_CONSTANT_String) | (1 << JVM_CONSTANT_Class) + | (1 << JVM_CONSTANT_MethodHandle) | (1 << JVM_CONSTANT_MethodType) + | (1 << JVM_CONSTANT_Dynamic); + verify_cp_type(bci, index, cp, types); + } else { + if (opcode != ClassFile.LDC2_W) verifyError("must be ldc2_w"); + types = (1 << JVM_CONSTANT_Double) | (1 << JVM_CONSTANT_Long) | (1 << JVM_CONSTANT_Dynamic); + verify_cp_type(bci, index, cp, types); + } + switch (tag) { + case JVM_CONSTANT_Utf8 -> current_frame.push_stack(object_type()); + case JVM_CONSTANT_String -> current_frame.push_stack(VerificationType.reference_type(java_lang_String)); + case JVM_CONSTANT_Class -> current_frame.push_stack(VerificationType.reference_type(java_lang_Class)); + case JVM_CONSTANT_Integer -> current_frame.push_stack(VerificationType.integer_type); + case JVM_CONSTANT_Float -> current_frame.push_stack(VerificationType.float_type); + case JVM_CONSTANT_Double -> current_frame.push_stack_2(VerificationType.double_type, VerificationType.double2_type); + case JVM_CONSTANT_Long -> current_frame.push_stack_2(VerificationType.long_type, VerificationType.long2_type); + case JVM_CONSTANT_MethodHandle -> current_frame.push_stack(VerificationType.reference_type(java_lang_invoke_MethodHandle)); + case JVM_CONSTANT_MethodType -> current_frame.push_stack(VerificationType.reference_type(java_lang_invoke_MethodType)); + case JVM_CONSTANT_Dynamic -> { + String constant_type = cp.dynamicConstantSignatureAt(index); + if (!VerificationSignature.isValidTypeSignature(constant_type)) verifyError("Invalid type for dynamic constant"); + VerificationType[] v_constant_type = new VerificationType[2]; + var sig_stream = new VerificationSignature(constant_type, false, this); + int n = change_sig_to_verificationType(sig_stream, v_constant_type, 0); + int opcode_n = (opcode == ClassFile.LDC2_W ? 2 : 1); + if (n != opcode_n) { + types &= ~(1 << JVM_CONSTANT_Dynamic); + verify_cp_type(bci, index, cp, types); + } + for (int i = 0; i < n; i++) { + current_frame.push_stack(v_constant_type[i]); + } + } + default -> verifyError("Invalid index in ldc"); + } + } + + void verify_switch(RawBytecodeHelper bcs, int code_length, byte[] code_data, VerificationFrame current_frame, VerificationTable stackmap_table) { + int bci = bcs.bci; + int aligned_bci = VerificationBytecodes.align(bci + 1); + // 4639449 & 4647081: padding bytes must be 0 + if (_klass.majorVersion() < NONZERO_PADDING_BYTES_IN_SWITCH_MAJOR_VERSION) { + int padding_offset = 1; + while ((bci + padding_offset) < aligned_bci) { + if (_method.codeArray()[bci + padding_offset] != 0) { + verifyError("Nonzero padding byte in lookupswitch or tableswitch"); + } + padding_offset++; + } + } + int default_ofset = bcs.getInt(aligned_bci); + int keys, delta; + current_frame.pop_stack(VerificationType.integer_type); + if (bcs.rawCode == ClassFile.TABLESWITCH) { + int low = bcs.getInt(aligned_bci + 4); + int high = bcs.getInt(aligned_bci + 2*4); + if (low > high) { + verifyError("low must be less than or equal to high in tableswitch"); + } + keys = high - low + 1; + if (keys < 0) { + verifyError("too many keys in tableswitch"); + } + delta = 1; + } else { + // Make sure that the lookupswitch items are sorted + keys = bcs.getInt(aligned_bci + 4); + if (keys < 0) { + verifyError("number of keys in lookupswitch less than 0"); + } + delta = 2; + for (int i = 0; i < (keys - 1); i++) { + int this_key = bcs.getInt(aligned_bci + (2+2*i)*4); + int next_key = bcs.getInt(aligned_bci + (2+2*i+2)*4); + if (this_key >= next_key) { + verifyError("Bad lookupswitch instruction"); + } + } + } + int target = bci + default_ofset; + stackmap_table.check_jump_target(current_frame, target); + for (int i = 0; i < keys; i++) { + aligned_bci = VerificationBytecodes.align(bcs.bci + 1); + target = bci + bcs.getInt(aligned_bci + (3+i*delta)*4); + stackmap_table.check_jump_target(current_frame, target); + } + } + + void verify_field_instructions(RawBytecodeHelper bcs, VerificationFrame current_frame, ConstantPoolWrapper cp, boolean allow_arrays) { + int index = bcs.getIndexU2(); + verify_cp_type(bcs.bci, index, cp, 1 << JVM_CONSTANT_Fieldref); + String field_name = cp.refNameAt(index); + String field_sig = cp.refSignatureAt(index); + if (!VerificationSignature.isValidTypeSignature(field_sig)) verifyError("Invalid field signature"); + VerificationType ref_class_type = cp_ref_index_to_type(index, cp); + if (!ref_class_type.is_object() && + (!allow_arrays || !ref_class_type.is_array())) { + verifyError(String.format("Expecting reference to class in class %s at constant pool index %d", _klass.thisClassName(), index)); + } + VerificationType target_class_type = ref_class_type; + VerificationType[] field_type = new VerificationType[2]; + var sig_stream = new VerificationSignature(field_sig, false, this); + VerificationType stack_object_type = null; + int n = change_sig_to_verificationType(sig_stream, field_type, 0); + boolean is_assignable; + switch (bcs.rawCode) { + case ClassFile.GETSTATIC -> { + for (int i = 0; i < n; i++) { + current_frame.push_stack(field_type[i]); + } + } + case ClassFile.PUTSTATIC -> { + for (int i = n - 1; i >= 0; i--) { + current_frame.pop_stack(field_type[i]); + } + } + case ClassFile.GETFIELD -> { + stack_object_type = current_frame.pop_stack( + target_class_type); + for (int i = 0; i < n; i++) { + current_frame.push_stack(field_type[i]); + } + } + case ClassFile.PUTFIELD -> { + for (int i = n - 1; i >= 0; i--) { + current_frame.pop_stack(field_type[i]); + } + stack_object_type = current_frame.pop_stack(); + if (stack_object_type.is_uninitialized_this(this) && + target_class_type.equals(current_type()) && + _klass.findField(field_name, field_sig)) { + stack_object_type = current_type(); + } + is_assignable = target_class_type.is_assignable_from(stack_object_type, this); + if (!is_assignable) { + verifyError("Bad type on operand stack in putfield"); + } + } + default -> verifyError("Should not reach here"); + } + } + + // Return TRUE if all code paths starting with start_bc_offset end in + // bytecode athrow or loop. + boolean ends_in_athrow(int start_bc_offset) { + log_info("unimplemented VerifierImpl.ends_in_athrow"); + return true; + } + + boolean verify_invoke_init(RawBytecodeHelper bcs, int ref_class_index, VerificationType ref_class_type, + VerificationFrame current_frame, int code_length, boolean in_try_block, + boolean this_uninit, ConstantPoolWrapper cp, VerificationTable stackmap_table) { + int bci = bcs.bci; + VerificationType type = current_frame.pop_stack(VerificationType.reference_check); + if (type.is_uninitialized_this(this)) { + String superk_name = current_class().superclassName(); + if (!current_class().thisClassName().equals(ref_class_type.name()) && + !superk_name.equals(ref_class_type.name())) { + verifyError("Bad method call"); + } + if (in_try_block) { + for(var exhandler : _method.exceptionTable()) { + int start_pc = exhandler[0]; + int end_pc = exhandler[1]; + + if (bci >= start_pc && bci < end_pc) { + if (!ends_in_athrow(exhandler[2])) { + verifyError("Bad method call from after the start of a try block"); + } + } + } + verify_exception_handler_targets(bci, true, current_frame, stackmap_table); + } + current_frame.initialize_object(type, current_type()); + this_uninit = true; + } else if (type.is_uninitialized()) { + int new_offset = type.bci(this); + if (new_offset > (code_length - 3) || (_method.codeArray()[new_offset] & 0xff) != ClassFile.NEW) { + verifyError("Expecting new instruction"); + } + int new_class_index = bcs.getIndexU2Raw(new_offset + 1); + verify_cp_class_type(bci, new_class_index, cp); + VerificationType new_class_type = cp_index_to_type( + new_class_index, cp); + if (!new_class_type.equals(ref_class_type)) { + verifyError("Call to wrong method"); + } + if (in_try_block) { + verify_exception_handler_targets(bci, this_uninit, current_frame, + stackmap_table); + } + current_frame.initialize_object(type, new_class_type); + } else { + verifyError("Bad operand type when invoking "); + } + return this_uninit; + } + + static boolean is_same_or_direct_interface(VerificationWrapper klass, VerificationType klass_type, VerificationType ref_class_type) { + if (ref_class_type.equals(klass_type)) return true; + for (String k_name : klass.interfaceNames()) { + if (ref_class_type.equals(VerificationType.reference_type(k_name))) { + return true; + } + } + return false; + } + + boolean verify_invoke_instructions(RawBytecodeHelper bcs, int code_length, VerificationFrame current_frame, boolean in_try_block, boolean this_uninit, VerificationType return_type, ConstantPoolWrapper cp, VerificationTable stackmap_table) { + // Make sure the constant pool item is the right type + int index = bcs.getIndexU2(); + int opcode = bcs.rawCode; + int types = 0; + switch (opcode) { + case ClassFile.INVOKEINTERFACE: + types = 1 << JVM_CONSTANT_InterfaceMethodref; + break; + case ClassFile.INVOKEDYNAMIC: + types = 1 << JVM_CONSTANT_InvokeDynamic; + break; + case ClassFile.INVOKESPECIAL: + case ClassFile.INVOKESTATIC: + types = (_klass.majorVersion() < STATIC_METHOD_IN_INTERFACE_MAJOR_VERSION) ? + (1 << JVM_CONSTANT_Methodref) : + ((1 << JVM_CONSTANT_InterfaceMethodref) | (1 << JVM_CONSTANT_Methodref)); + break; + default: + types = 1 << JVM_CONSTANT_Methodref; + } + verify_cp_type(bcs.bci, index, cp, types); + String method_name = cp.refNameAt(index); + String method_sig = cp.refSignatureAt(index); + if (!VerificationSignature.isValidMethodSignature(method_sig)) verifyError("Invalid method signature"); + VerificationType ref_class_type = null; + if (opcode == ClassFile.INVOKEDYNAMIC) { + if (_klass.majorVersion() < INVOKEDYNAMIC_MAJOR_VERSION) { + classError(String.format("invokedynamic instructions not supported by this class file version (%d), class %s", _klass.majorVersion(), _klass.thisClassName())); + } + } else { + ref_class_type = cp_ref_index_to_type(index, cp); + } + String sig = cp.refSignatureAt(index); + sig_as_verification_types mth_sig_verif_types; + ArrayList verif_types = new ArrayList<>(10); + mth_sig_verif_types = new sig_as_verification_types(verif_types); + create_method_sig_entry(mth_sig_verif_types, sig); + int nargs = mth_sig_verif_types.num_args(); + int bci = bcs.bci; + if (opcode == ClassFile.INVOKEINTERFACE) { + if ((_method.codeArray()[bci+3] & 0xff) != (nargs+1)) { + verifyError("Inconsistent args count operand in invokeinterface"); + } + if ((_method.codeArray()[bci+4] & 0xff) != 0) { + verifyError("Fourth operand byte of invokeinterface must be zero"); + } + } + if (opcode == ClassFile.INVOKEDYNAMIC) { + if ((_method.codeArray()[bci+3] & 0xff) != 0 || (_method.codeArray()[bci+4] & 0xff) != 0) { + verifyError("Third and fourth operand bytes of invokedynamic must be zero"); + } + } + if (method_name.charAt(0) == JVM_SIGNATURE_SPECIAL) { + if (opcode != ClassFile.INVOKESPECIAL || + !object_initializer_name.equals(method_name)) { + verifyError("Illegal call to internal method"); + } + } else if (opcode == ClassFile.INVOKESPECIAL + && !is_same_or_direct_interface(current_class(), current_type(), ref_class_type) + && !ref_class_type.equals(VerificationType.reference_type( + current_class().superclassName()))) { + boolean have_imr_indirect = cp.tagAt(index) == JVM_CONSTANT_InterfaceMethodref; + boolean subtype = ref_class_type.is_assignable_from(current_type(), this); + if (!subtype) { + verifyError("Bad invokespecial instruction: current class isn't assignable to reference class."); + } else if (have_imr_indirect) { + verifyError("Bad invokespecial instruction: interface method reference is in an indirect superinterface."); + } + + } + ArrayList sig_verif_types = mth_sig_verif_types.sig_verif_types(); + if (sig_verif_types == null) verifyError("Missing signature's array of verification types"); + for (int i = nargs - 1; i >= 0; i--) { // Run backwards + current_frame.pop_stack(sig_verif_types.get(i)); + } + if (opcode != ClassFile.INVOKESTATIC && + opcode != ClassFile.INVOKEDYNAMIC) { + if (object_initializer_name.equals(method_name)) { // method + this_uninit = verify_invoke_init(bcs, index, ref_class_type, current_frame, + code_length, in_try_block, this_uninit, cp, stackmap_table); + } else { + switch (opcode) { + case ClassFile.INVOKESPECIAL -> + current_frame.pop_stack(current_type()); + case ClassFile.INVOKEVIRTUAL -> { + VerificationType stack_object_type = + current_frame.pop_stack(ref_class_type); + if (current_type() != stack_object_type) { + cp.classNameAt(cp.refClassIndexAt(index)); + } + } + default -> { + if (opcode != ClassFile.INVOKEINTERFACE) + verifyError("Unexpected opcode encountered"); + current_frame.pop_stack(ref_class_type); + } + } + } + } + int sig_verif_types_len = sig_verif_types.size(); + if (sig_verif_types_len > nargs) { // There's a return type + if (object_initializer_name.equals(method_name)) { + verifyError("Return type must be void in method"); + } + + if (sig_verif_types_len > nargs + 2) verifyError("Signature verification types array return type is bogus"); + for (int i = nargs; i < sig_verif_types_len; i++) { + if (!(i == nargs || sig_verif_types.get(i).is_long2() || sig_verif_types.get(i).is_double2())) verifyError("Unexpected return verificationType"); + current_frame.push_stack(sig_verif_types.get(i)); + } + } + return this_uninit; + } + + VerificationType get_newarray_type(int index, int bci) { + String[] from_bt = new String[] { + null, null, null, null, "[Z", "[C", "[F", "[D", "[B", "[S", "[I", "[J", + }; + if (index < T_BOOLEAN.type || index > T_LONG.type) { + verifyError("Illegal newarray instruction"); + } + String sig = from_bt[index]; + return VerificationType.reference_type(sig); + } + + void verify_anewarray(int bci, int index, ConstantPoolWrapper cp, VerificationFrame current_frame) { + verify_cp_class_type(bci, index, cp); + current_frame.pop_stack(VerificationType.integer_type); + VerificationType component_type = cp_index_to_type(index, cp); + int length; + String arr_sig_str; + if (component_type.is_array()) { // it's an array + String component_name = component_type.name(); + length = component_name.length(); + if (length > MAX_ARRAY_DIMENSIONS && + component_name.charAt(MAX_ARRAY_DIMENSIONS - 1) == JVM_SIGNATURE_ARRAY) { + verifyError("Illegal anewarray instruction, array has more than 255 dimensions"); + } + length++; + arr_sig_str = String.format("%c%s", JVM_SIGNATURE_ARRAY, component_name); + if (arr_sig_str.length() != length) verifyError("Unexpected number of characters in string"); + } else { // it's an object or interface + String component_name = component_type.name(); + length = component_name.length() + 3; + arr_sig_str = String.format("%c%c%s;", JVM_SIGNATURE_ARRAY, JVM_SIGNATURE_CLASS, component_name); + if (arr_sig_str.length() != length) verifyError("Unexpected number of characters in string"); + } + VerificationType new_array_type = VerificationType.reference_type(arr_sig_str); + current_frame.push_stack(new_array_type); + } + + void verify_iload(int index, VerificationFrame current_frame) { + current_frame.get_local( + index, VerificationType.integer_type); + current_frame.push_stack( + VerificationType.integer_type); + } + + void verify_lload(int index, VerificationFrame current_frame) { + current_frame.get_local_2( + index, VerificationType.long_type, + VerificationType.long2_type); + current_frame.push_stack_2( + VerificationType.long_type, + VerificationType.long2_type); + } + + void verify_fload(int index, VerificationFrame current_frame) { + current_frame.get_local( + index, VerificationType.float_type); + current_frame.push_stack( + VerificationType.float_type); + } + + void verify_dload(int index, VerificationFrame current_frame) { + current_frame.get_local_2( + index, VerificationType.double_type, + VerificationType.double2_type); + current_frame.push_stack_2( + VerificationType.double_type, + VerificationType.double2_type); + } + + void verify_aload(int index, VerificationFrame current_frame) { + VerificationType type = current_frame.get_local( + index, VerificationType.reference_check); + current_frame.push_stack(type); + } + + void verify_istore(int index, VerificationFrame current_frame) { + current_frame.pop_stack( + VerificationType.integer_type); + current_frame.set_local( + index, VerificationType.integer_type); + } + + void verify_lstore(int index, VerificationFrame current_frame) { + current_frame.pop_stack_2( + VerificationType.long2_type, + VerificationType.long_type); + current_frame.set_local_2( + index, VerificationType.long_type, + VerificationType.long2_type); + } + + void verify_fstore(int index, VerificationFrame current_frame) { + current_frame.pop_stack( + VerificationType.float_type); + current_frame.set_local( + index, VerificationType.float_type); + } + + void verify_dstore(int index, VerificationFrame current_frame) { + current_frame.pop_stack_2( + VerificationType.double2_type, + VerificationType.double_type); + current_frame.set_local_2( + index, VerificationType.double_type, + VerificationType.double2_type); + } + + void verify_astore(int index, VerificationFrame current_frame) { + VerificationType type = current_frame.pop_stack( + VerificationType.reference_check); + current_frame.set_local(index, type); + } + + void verify_iinc(int index, VerificationFrame current_frame) { + VerificationType type = current_frame.get_local( + index, VerificationType.integer_type); + current_frame.set_local(index, type); + } + + void verify_return_value(VerificationType return_type, VerificationType type, int bci, VerificationFrame current_frame) { + if (return_type.is_bogus()) { + verifyError("Method expects a return value"); + } + boolean match = return_type.is_assignable_from(type, this); + if (!match) { + verifyError("Bad return type"); + } + } + + private void dumpMethod() { + if (_logger != null) ClassPrinter.toTree(_method.m, ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES).toYaml(_logger); + } + + void verifyError(String msg) { + dumpMethod(); + throw new VerifyError(String.format("%s in %s::%s(%s) @%d %s", msg, _klass.thisClassName(), _method.name(), _method.parameters(), bci, errorContext).trim()); + } + + void verifyError(String msg, VerificationFrame from, VerificationFrame target) { + dumpMethod(); + throw new VerifyError(String.format("%s in %s::%s(%s) @%d %s%n while assigning %s%n to %s", msg, _klass.thisClassName(), _method.name(), _method.parameters(), bci, errorContext, from, target)); + } + + void classError(String msg) { + dumpMethod(); + throw new VerifyError(String.format("%s in %s::%s(%s)", msg, _klass.thisClassName(), _method.name(), _method.parameters())); + } +} diff --git a/tests/test_data/std/jdk/internal/constant/ConstantUtils.class b/tests/test_data/std/jdk/internal/constant/ConstantUtils.class new file mode 100644 index 00000000..798c24a5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/ConstantUtils.class differ diff --git a/tests/test_data/std/jdk/internal/constant/ConstantUtils.java b/tests/test_data/std/jdk/internal/constant/ConstantUtils.java new file mode 100644 index 00000000..7c676a44 --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/ConstantUtils.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import sun.invoke.util.Wrapper; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import java.lang.constant.ConstantDescs; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Helper methods for the implementation of {@code java.lang.constant}. + */ +public final class ConstantUtils { + /** an empty constant descriptor */ + public static final ConstantDesc[] EMPTY_CONSTANTDESC = new ConstantDesc[0]; + public static final ClassDesc[] EMPTY_CLASSDESC = new ClassDesc[0]; + public static final int MAX_ARRAY_TYPE_DESC_DIMENSIONS = 255; + + private static final Set pointyNames = Set.of(ConstantDescs.INIT_NAME, ConstantDescs.CLASS_INIT_NAME); + + /** No instantiation */ + private ConstantUtils() {} + + /** + * Validates the correctness of a binary class name. In particular checks for the presence of + * invalid characters in the name. + * + * @param name the class name + * @return the class name passed if valid + * @throws IllegalArgumentException if the class name is invalid + * @throws NullPointerException if class name is {@code null} + */ + public static String validateBinaryClassName(String name) { + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch == ';' || ch == '[' || ch == '/') + throw new IllegalArgumentException("Invalid class name: " + name); + } + return name; + } + + /** + * Validates the correctness of an internal class name. + * In particular checks for the presence of invalid characters in the name. + * + * @param name the class name + * @return the class name passed if valid + * @throws IllegalArgumentException if the class name is invalid + * @throws NullPointerException if class name is {@code null} + */ + public static String validateInternalClassName(String name) { + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch == ';' || ch == '[' || ch == '.') + throw new IllegalArgumentException("Invalid class name: " + name); + } + return name; + } + + /** + * Validates the correctness of a binary package name. + * In particular checks for the presence of invalid characters in the name. + * Empty package name is allowed. + * + * @param name the package name + * @return the package name passed if valid + * @throws IllegalArgumentException if the package name is invalid + * @throws NullPointerException if the package name is {@code null} + */ + public static String validateBinaryPackageName(String name) { + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch == ';' || ch == '[' || ch == '/') + throw new IllegalArgumentException("Invalid package name: " + name); + } + return name; + } + + /** + * Validates the correctness of an internal package name. + * In particular checks for the presence of invalid characters in the name. + * Empty package name is allowed. + * + * @param name the package name + * @return the package name passed if valid + * @throws IllegalArgumentException if the package name is invalid + * @throws NullPointerException if the package name is {@code null} + */ + public static String validateInternalPackageName(String name) { + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (ch == ';' || ch == '[' || ch == '.') + throw new IllegalArgumentException("Invalid package name: " + name); + } + return name; + } + + /** + * Validates the correctness of a module name. + * In particular checks for the presence of invalid characters in the name. + * Empty module name is allowed. + * + * {@jvms 4.2.3} Module and Package Names + * + * @param name the module name + * @return the module name passed if valid + * @throws IllegalArgumentException if the module name is invalid + * @throws NullPointerException if the module name is {@code null} + */ + public static String validateModuleName(String name) { + for (int i = name.length() - 1; i >= 0; i--) { + char ch = name.charAt(i); + if ((ch >= '\u0000' && ch <= '\u001F') + || ((ch == '\\' || ch == ':' || ch =='@') && (i == 0 || name.charAt(--i) != '\\'))) + throw new IllegalArgumentException("Invalid module name: " + name); + } + return name; + } + + /** + * Validates a member name + * + * @param name the name of the member + * @return the name passed if valid + * @throws IllegalArgumentException if the member name is invalid + * @throws NullPointerException if the member name is {@code null} + */ + public static String validateMemberName(String name, boolean method) { + int len = name.length(); + if (len == 0) + throw new IllegalArgumentException("zero-length member name"); + for (int i = 0; i < len; i++) { + char ch = name.charAt(i); + // common case fast-path + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + continue; + if (ch == '.' || ch == ';' || ch == '[' || ch == '/') + throw new IllegalArgumentException("Invalid member name: " + name); + if (method && (ch == '<' || ch == '>')) { + if (!pointyNames.contains(name)) + throw new IllegalArgumentException("Invalid member name: " + name); + } + } + return name; + } + + public static void validateClassOrInterface(ClassDesc classDesc) { + if (!classDesc.isClassOrInterface()) + throw new IllegalArgumentException("not a class or interface type: " + classDesc); + } + + public static int arrayDepth(String descriptorString) { + int depth = 0; + while (descriptorString.charAt(depth) == '[') + depth++; + return depth; + } + + public static String binaryToInternal(String name) { + return name.replace('.', '/'); + } + + public static String internalToBinary(String name) { + return name.replace('/', '.'); + } + + public static String dropFirstAndLastChar(String s) { + return s.substring(1, s.length() - 1); + } + + /** + * Parses a method descriptor string, and return a list of field descriptor + * strings, return type first, then parameter types + * + * @param descriptor the descriptor string + * @return the list of types + * @throws IllegalArgumentException if the descriptor string is not valid + */ + public static List parseMethodDescriptor(String descriptor) { + int cur = 0, end = descriptor.length(); + ArrayList ptypes = new ArrayList<>(); + ptypes.add(null); // placeholder for return type + + if (cur >= end || descriptor.charAt(cur) != '(') + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + + ++cur; // skip '(' + while (cur < end && descriptor.charAt(cur) != ')') { + int len = skipOverFieldSignature(descriptor, cur, end, false); + if (len == 0) + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + ptypes.add(resolveClassDesc(descriptor, cur, len)); + cur += len; + } + if (cur >= end) + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + ++cur; // skip ')' + + int rLen = skipOverFieldSignature(descriptor, cur, end, true); + if (rLen == 0 || cur + rLen != end) + throw new IllegalArgumentException("Bad method descriptor: " + descriptor); + ptypes.set(0, resolveClassDesc(descriptor, cur, rLen)); + return ptypes; + } + + private static ClassDesc resolveClassDesc(String descriptor, int start, int len) { + if (len == 1) { + return Wrapper.forPrimitiveType(descriptor.charAt(start)).classDescriptor(); + } + // Pre-verified in parseMethodDescriptor; avoid redundant verification + return ReferenceClassDescImpl.ofValidated(descriptor.substring(start, start + len)); + } + + private static final char JVM_SIGNATURE_ARRAY = '['; + private static final char JVM_SIGNATURE_BYTE = 'B'; + private static final char JVM_SIGNATURE_CHAR = 'C'; + private static final char JVM_SIGNATURE_CLASS = 'L'; + private static final char JVM_SIGNATURE_ENDCLASS = ';'; + private static final char JVM_SIGNATURE_ENUM = 'E'; + private static final char JVM_SIGNATURE_FLOAT = 'F'; + private static final char JVM_SIGNATURE_DOUBLE = 'D'; + private static final char JVM_SIGNATURE_FUNC = '('; + private static final char JVM_SIGNATURE_ENDFUNC = ')'; + private static final char JVM_SIGNATURE_INT = 'I'; + private static final char JVM_SIGNATURE_LONG = 'J'; + private static final char JVM_SIGNATURE_SHORT = 'S'; + private static final char JVM_SIGNATURE_VOID = 'V'; + private static final char JVM_SIGNATURE_BOOLEAN = 'Z'; + + /** + * Validates that the characters at [start, end) within the provided string + * describe a valid field type descriptor. + * @param descriptor the descriptor string + * @param start the starting index into the string + * @param end the ending index within the string + * @param voidOK is void acceptable? + * @return the length of the descriptor, or 0 if it is not a descriptor + * @throws IllegalArgumentException if the descriptor string is not valid + */ + @SuppressWarnings("fallthrough") + static int skipOverFieldSignature(String descriptor, int start, int end, boolean voidOK) { + int arrayDim = 0; + int index = start; + while (index < end) { + switch (descriptor.charAt(index)) { + case JVM_SIGNATURE_VOID: if (!voidOK) { return 0; } + case JVM_SIGNATURE_BOOLEAN: + case JVM_SIGNATURE_BYTE: + case JVM_SIGNATURE_CHAR: + case JVM_SIGNATURE_SHORT: + case JVM_SIGNATURE_INT: + case JVM_SIGNATURE_FLOAT: + case JVM_SIGNATURE_LONG: + case JVM_SIGNATURE_DOUBLE: + return index - start + 1; + case JVM_SIGNATURE_CLASS: + // state variable for detection of illegal states, such as: + // empty unqualified name, '//', leading '/', or trailing '/' + boolean legal = false; + while (++index < end) { + switch (descriptor.charAt(index)) { + case ';' -> { + // illegal state on parser exit indicates empty unqualified name or trailing '/' + return legal ? index - start + 1 : 0; + } + case '.', '[' -> { + // do not permit '.' or '[' + return 0; + } + case '/' -> { + // illegal state when received '/' indicates '//' or leading '/' + if (!legal) return 0; + legal = false; + } + default -> + legal = true; + } + } + return 0; + case JVM_SIGNATURE_ARRAY: + arrayDim++; + if (arrayDim > MAX_ARRAY_TYPE_DESC_DIMENSIONS) { + throw new IllegalArgumentException(String.format("Cannot create an array type descriptor with more than %d dimensions", + ConstantUtils.MAX_ARRAY_TYPE_DESC_DIMENSIONS)); + } + // The rest of what's there better be a legal descriptor + index++; + voidOK = false; + break; + default: + return 0; + } + } + return 0; + } +} diff --git a/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl$1.class b/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl$1.class new file mode 100644 index 00000000..59841e1e Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl.class b/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl.class new file mode 100644 index 00000000..df50669a Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl.class differ diff --git a/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl.java b/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl.java new file mode 100644 index 00000000..0097ed6b --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/DirectMethodHandleDescImpl.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import java.lang.constant.ClassDesc; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Objects; + +import static java.lang.constant.ConstantDescs.CD_void; +import static java.lang.constant.DirectMethodHandleDesc.Kind.CONSTRUCTOR; +import static java.util.Objects.requireNonNull; +import static jdk.internal.constant.ConstantUtils.validateClassOrInterface; +import static jdk.internal.constant.ConstantUtils.validateMemberName; + +/** + * A nominal descriptor for a direct + * {@link MethodHandle}. A {@linkplain DirectMethodHandleDescImpl} corresponds to + * a {@code Constant_MethodHandle_info} entry in the constant pool of a classfile. + */ +public final class DirectMethodHandleDescImpl implements DirectMethodHandleDesc { + + private final Kind kind; + private final ClassDesc owner; + private final String name; + private final MethodTypeDesc invocationType; + + /** + * Constructs a {@linkplain DirectMethodHandleDescImpl} for a method or field + * from a kind, owner, name, and type + * + * @param kind the kind of the method handle + * @param owner the declaring class or interface for the method + * @param name the unqualified name of the method (ignored if {@code kind} is {@code CONSTRUCTOR}) + * @param type the lookup type of the method + * @throws NullPointerException if any non-ignored argument is null + * @throws IllegalArgumentException if {@code kind} describes a field accessor, + * and {@code type} is not consistent with that kind of field accessor, or if + * {@code kind} describes a constructor, and the return type of {@code type} + * is not {@code void} + * @jvms 4.2.2 Unqualified Names + */ + public DirectMethodHandleDescImpl(Kind kind, ClassDesc owner, String name, MethodTypeDesc type) { + if (kind == CONSTRUCTOR) + name = ""; + + requireNonNull(kind); + validateClassOrInterface(owner); + validateMemberName(name, true); + requireNonNull(type); + + switch (kind) { + case CONSTRUCTOR -> validateConstructor(type); + case GETTER -> validateFieldType(type, false, true); + case SETTER -> validateFieldType(type, true, true); + case STATIC_GETTER -> validateFieldType(type, false, false); + case STATIC_SETTER -> validateFieldType(type, true, false); + } + + this.kind = kind; + this.owner = owner; + this.name = name; + this.invocationType = switch (kind) { + case VIRTUAL, SPECIAL, INTERFACE_VIRTUAL, INTERFACE_SPECIAL -> type.insertParameterTypes(0, owner); + case CONSTRUCTOR -> type.changeReturnType(owner); + default -> type; + }; + } + + private static void validateFieldType(MethodTypeDesc type, boolean isSetter, boolean isVirtual) { + boolean isVoid = type.returnType().descriptorString().equals("V"); + int expectedParams = (isSetter ? 1 : 0) + (isVirtual ? 1 : 0); + if (isVoid != isSetter + || type.parameterCount() != expectedParams + || (isVirtual && type.parameterType(0).isPrimitive())) { + String expectedType = String.format("(%s%s)%s", (isVirtual ? "R" : ""), + (isSetter ? "T" : ""), (isSetter ? "V" : "T")); + throw new IllegalArgumentException(String.format("Expected type of %s for getter, found %s", expectedType, type)); + } + } + + private static void validateConstructor(MethodTypeDesc type) { + if (!type.returnType().descriptorString().equals("V")) { + throw new IllegalArgumentException(String.format("Expected type of (T*)V for constructor, found %s", type)); + } + } + + @Override + public Kind kind() { return kind; } + + @Override + public int refKind() { return kind.refKind; } + + @Override + public boolean isOwnerInterface() { return kind.isInterface; } + + @Override + public ClassDesc owner() { + return owner; + } + + @Override + public String methodName() { + return name; + } + + @Override + public MethodTypeDesc invocationType() { + return invocationType; + } + + @Override + public String lookupDescriptor() { + return switch (kind) { + case VIRTUAL, + SPECIAL, + INTERFACE_VIRTUAL, + INTERFACE_SPECIAL -> invocationType.dropParameterTypes(0, 1).descriptorString(); + case STATIC, + INTERFACE_STATIC -> invocationType.descriptorString(); + case CONSTRUCTOR -> invocationType.changeReturnType(CD_void).descriptorString(); + case GETTER, + STATIC_GETTER -> invocationType.returnType().descriptorString(); + case SETTER -> invocationType.parameterType(1).descriptorString(); + case STATIC_SETTER -> invocationType.parameterType(0).descriptorString(); + default -> throw new IllegalStateException(kind.toString()); + }; + } + + public MethodHandle resolveConstantDesc(MethodHandles.Lookup lookup) + throws ReflectiveOperationException { + Class resolvedOwner = owner.resolveConstantDesc(lookup); + MethodType invocationType = this.invocationType().resolveConstantDesc(lookup); + return switch (kind) { + case STATIC, + INTERFACE_STATIC -> lookup.findStatic(resolvedOwner, name, invocationType); + case VIRTUAL, + INTERFACE_VIRTUAL -> lookup.findVirtual(resolvedOwner, name, invocationType.dropParameterTypes(0, 1)); + case SPECIAL, + INTERFACE_SPECIAL -> lookup.findSpecial(resolvedOwner, name, invocationType.dropParameterTypes(0, 1), lookup.lookupClass()); + case CONSTRUCTOR -> lookup.findConstructor(resolvedOwner, invocationType.changeReturnType(void.class)); + case GETTER -> lookup.findGetter(resolvedOwner, name, invocationType.returnType()); + case STATIC_GETTER -> lookup.findStaticGetter(resolvedOwner, name, invocationType.returnType()); + case SETTER -> lookup.findSetter(resolvedOwner, name, invocationType.parameterType(1)); + case STATIC_SETTER -> lookup.findStaticSetter(resolvedOwner, name, invocationType.parameterType(0)); + default -> throw new IllegalStateException(kind.name()); + }; + } + + /** + * Returns {@code true} if this {@linkplain DirectMethodHandleDescImpl} is + * equal to another {@linkplain DirectMethodHandleDescImpl}. Equality is + * determined by the two descriptors having equal kind, owner, name, and type + * descriptor. + * @param o a {@code DirectMethodHandleDescImpl} to compare to this + * {@code DirectMethodHandleDescImpl} + * @return {@code true} if the specified {@code DirectMethodHandleDescImpl} + * is equal to this {@code DirectMethodHandleDescImpl}. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DirectMethodHandleDescImpl desc = (DirectMethodHandleDescImpl) o; + return kind == desc.kind && + Objects.equals(owner, desc.owner) && + Objects.equals(name, desc.name) && + Objects.equals(invocationType, desc.invocationType); + } + + @Override + public int hashCode() { + return Objects.hash(kind, owner, name, invocationType); + } + + @Override + public String toString() { + return String.format("MethodHandleDesc[%s/%s::%s%s]", kind, owner.displayName(), name, invocationType.displayDescriptor()); + } +} diff --git a/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl$1.class b/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl$1.class new file mode 100644 index 00000000..67199a47 Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl.class b/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl.class new file mode 100644 index 00000000..a0e6349d Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl.class differ diff --git a/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl.java b/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl.java new file mode 100644 index 00000000..0a75416f --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/MethodTypeDescImpl.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import jdk.internal.vm.annotation.Stable; + +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import static java.util.Objects.requireNonNull; + +/** + * A nominal descriptor for a + * {@link MethodType}. A {@linkplain MethodTypeDescImpl} corresponds to a + * {@code Constant_MethodType_info} entry in the constant pool of a classfile. + */ +public final class MethodTypeDescImpl implements MethodTypeDesc { + private final ClassDesc returnType; + private final @Stable ClassDesc[] argTypes; + private @Stable String cachedDescriptorString; + + /** + * Constructs a {@linkplain MethodTypeDesc} with the specified return type + * and a trusted and already-validated parameter types array. + * + * @param returnType a {@link ClassDesc} describing the return type + * @param validatedArgTypes {@link ClassDesc}s describing the trusted and validated parameter types + */ + private MethodTypeDescImpl(ClassDesc returnType, ClassDesc[] validatedArgTypes) { + this.returnType = returnType; + this.argTypes = validatedArgTypes; + } + + /** + * Constructs a {@linkplain MethodTypeDesc} with the specified return type + * and a trusted parameter types array, which will be validated. + * + * @param returnType a {@link ClassDesc} describing the return type + * @param trustedArgTypes {@link ClassDesc}s describing the trusted parameter types + */ + public static MethodTypeDescImpl ofTrusted(ClassDesc returnType, ClassDesc[] trustedArgTypes) { + requireNonNull(returnType); + // implicit null checks of trustedArgTypes and all elements + for (ClassDesc cd : trustedArgTypes) { + validateArgument(cd); + } + return ofValidated(returnType, trustedArgTypes); + } + + private static ClassDesc validateArgument(ClassDesc arg) { + if (arg.descriptorString().charAt(0) == 'V') // implicit null check + throw new IllegalArgumentException("Void parameters not permitted"); + return arg; + } + + /** + * Constructs a {@linkplain MethodTypeDesc} with the specified pre-validated return type + * and a pre-validated trusted parameter types array. + * + * @param returnType a {@link ClassDesc} describing the return type + * @param trustedArgTypes {@link ClassDesc}s describing the trusted parameter types + */ + public static MethodTypeDescImpl ofValidated(ClassDesc returnType, ClassDesc... trustedArgTypes) { + if (trustedArgTypes.length == 0) + return new MethodTypeDescImpl(returnType, ConstantUtils.EMPTY_CLASSDESC); + return new MethodTypeDescImpl(returnType, trustedArgTypes); + } + + /** + * Creates a {@linkplain MethodTypeDescImpl} given a method descriptor string. + * + * @param descriptor the method descriptor string + * @return a {@linkplain MethodTypeDescImpl} describing the desired method type + * @throws IllegalArgumentException if the descriptor string is not a valid + * method descriptor + * @jvms 4.3.3 Method Descriptors + */ + public static MethodTypeDescImpl ofDescriptor(String descriptor) { + // Implicit null-check of descriptor + List ptypes = ConstantUtils.parseMethodDescriptor(descriptor); + int args = ptypes.size() - 1; + ClassDesc[] paramTypes = args > 0 + ? ptypes.subList(1, args + 1).toArray(ConstantUtils.EMPTY_CLASSDESC) + : ConstantUtils.EMPTY_CLASSDESC; + + MethodTypeDescImpl result = ofValidated(ptypes.get(0), paramTypes); + result.cachedDescriptorString = descriptor; + return result; + } + + + @Override + public ClassDesc returnType() { + return returnType; + } + + @Override + public int parameterCount() { + return argTypes.length; + } + + @Override + public ClassDesc parameterType(int index) { + return argTypes[index]; + } + + @Override + public List parameterList() { + return List.of(argTypes); + } + + @Override + public ClassDesc[] parameterArray() { + return argTypes.clone(); + } + + @Override + public MethodTypeDesc changeReturnType(ClassDesc returnType) { + return ofValidated(requireNonNull(returnType), argTypes); + } + + @Override + public MethodTypeDesc changeParameterType(int index, ClassDesc paramType) { + ClassDesc[] newArgs = argTypes.clone(); + newArgs[index] = validateArgument(paramType); + return ofValidated(returnType, newArgs); + } + + @Override + public MethodTypeDesc dropParameterTypes(int start, int end) { + Objects.checkIndex(start, argTypes.length); + Objects.checkFromToIndex(start, end, argTypes.length); + + ClassDesc[] newArgs = new ClassDesc[argTypes.length - (end - start)]; + if (start > 0) { + System.arraycopy(argTypes, 0, newArgs, 0, start); + } + if (end < argTypes.length) { + System.arraycopy(argTypes, end, newArgs, start, argTypes.length - end); + } + return ofValidated(returnType, newArgs); + } + + @Override + public MethodTypeDesc insertParameterTypes(int pos, ClassDesc... paramTypes) { + if (pos < 0 || pos > argTypes.length) + throw new IndexOutOfBoundsException(pos); + + ClassDesc[] newArgs = new ClassDesc[argTypes.length + paramTypes.length]; + if (pos > 0) { + System.arraycopy(argTypes, 0, newArgs, 0, pos); + } + System.arraycopy(paramTypes, 0, newArgs, pos, paramTypes.length); + int destPos = pos + paramTypes.length; + if (pos < argTypes.length) { + System.arraycopy(argTypes, pos, newArgs, destPos, argTypes.length - pos); + } + // Validate after copying to avoid TOCTOU + for (int i = pos; i < destPos; i++) { + validateArgument(newArgs[i]); + } + + return ofValidated(returnType, newArgs); + } + + @Override + public String descriptorString() { + var desc = this.cachedDescriptorString; + if (desc != null) + return desc; + + int len = 2 + returnType.descriptorString().length(); + for (ClassDesc argType : argTypes) { + len += argType.descriptorString().length(); + } + StringBuilder sb = new StringBuilder(len).append('('); + for (ClassDesc argType : argTypes) { + sb.append(argType.descriptorString()); + } + desc = sb.append(')').append(returnType.descriptorString()).toString(); + cachedDescriptorString = desc; + return desc; + } + + @Override + public MethodType resolveConstantDesc(MethodHandles.Lookup lookup) throws ReflectiveOperationException { + @SuppressWarnings("removal") + MethodType mtype = AccessController.doPrivileged(new PrivilegedAction<>() { + @Override + public MethodType run() { + return MethodType.fromMethodDescriptorString(descriptorString(), + lookup.lookupClass().getClassLoader()); + } + }); + + // let's check that the lookup has access to all the types in the method type + lookup.accessClass(mtype.returnType()); + for (Class paramType: mtype.parameterArray()) { + lookup.accessClass(paramType); + } + return mtype; + } + + /** + * Returns {@code true} if this {@linkplain MethodTypeDescImpl} is + * equal to another {@linkplain MethodTypeDescImpl}. Equality is + * determined by the two descriptors having equal return types and argument + * types. + * + * @param o the {@code MethodTypeDescImpl} to compare to this + * {@code MethodTypeDescImpl} + * @return {@code true} if the specified {@code MethodTypeDescImpl} + * is equal to this {@code MethodTypeDescImpl}. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MethodTypeDescImpl constant = (MethodTypeDescImpl) o; + + return returnType.equals(constant.returnType) + && Arrays.equals(argTypes, constant.argTypes); + } + + @Override + public int hashCode() { + int result = returnType.hashCode(); + result = 31 * result + Arrays.hashCode(argTypes); + return result; + } + + @Override + public String toString() { + return String.format("MethodTypeDesc[%s]", displayDescriptor()); + } +} diff --git a/tests/test_data/std/jdk/internal/constant/ModuleDescImpl.class b/tests/test_data/std/jdk/internal/constant/ModuleDescImpl.class new file mode 100644 index 00000000..7a8346b4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/ModuleDescImpl.class differ diff --git a/tests/test_data/std/jdk/internal/constant/ModuleDescImpl.java b/tests/test_data/std/jdk/internal/constant/ModuleDescImpl.java new file mode 100644 index 00000000..89310c13 --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/ModuleDescImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import java.lang.constant.ModuleDesc; + +/* + * Implementation of {@code ModuleDesc} + * @param name must have been validated + */ +public record ModuleDescImpl(String name) implements ModuleDesc { + + @Override + public String toString() { + return String.format("ModuleDesc[%s]", name()); + } +} diff --git a/tests/test_data/std/jdk/internal/constant/PackageDescImpl.class b/tests/test_data/std/jdk/internal/constant/PackageDescImpl.class new file mode 100644 index 00000000..e12bea51 Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/PackageDescImpl.class differ diff --git a/tests/test_data/std/jdk/internal/constant/PackageDescImpl.java b/tests/test_data/std/jdk/internal/constant/PackageDescImpl.java new file mode 100644 index 00000000..3ef65b4e --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/PackageDescImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import java.lang.constant.PackageDesc; + +/* + * Implementation of {@code PackageDesc} + * @param internalName must have been validated + */ +public record PackageDescImpl(String internalName) implements PackageDesc { + + @Override + public String toString() { + return String.format("PackageDesc[%s]", name()); + } +} diff --git a/tests/test_data/std/jdk/internal/constant/PrimitiveClassDescImpl.class b/tests/test_data/std/jdk/internal/constant/PrimitiveClassDescImpl.class new file mode 100644 index 00000000..d650f489 Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/PrimitiveClassDescImpl.class differ diff --git a/tests/test_data/std/jdk/internal/constant/PrimitiveClassDescImpl.java b/tests/test_data/std/jdk/internal/constant/PrimitiveClassDescImpl.java new file mode 100644 index 00000000..123c76a1 --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/PrimitiveClassDescImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.DynamicConstantDesc; +import java.lang.invoke.MethodHandles; + +import sun.invoke.util.Wrapper; + +import static java.util.Objects.requireNonNull; + +/** + * A nominal descriptor for the class + * constant corresponding to a primitive type (e.g., {@code int.class}). + */ +public final class PrimitiveClassDescImpl + extends DynamicConstantDesc> implements ClassDesc { + + private final String descriptor; + + /** + * Creates a {@linkplain ClassDesc} given a descriptor string for a primitive + * type. + * + * @param descriptor the descriptor string, which must be a one-character + * string corresponding to one of the nine base types + * @throws IllegalArgumentException if the descriptor string does not + * describe a valid primitive type + * @jvms 4.3 Descriptors + */ + public PrimitiveClassDescImpl(String descriptor) { + super(ConstantDescs.BSM_PRIMITIVE_CLASS, requireNonNull(descriptor), ConstantDescs.CD_Class); + if (descriptor.length() != 1 + || "VIJCSBFDZ".indexOf(descriptor.charAt(0)) < 0) + throw new IllegalArgumentException(String.format("not a valid primitive type descriptor: %s", descriptor)); + this.descriptor = descriptor; + } + + @Override + public String descriptorString() { + return descriptor; + } + + @Override + public Class resolveConstantDesc(MethodHandles.Lookup lookup) { + return Wrapper.forBasicType(descriptorString().charAt(0)).primitiveType(); + } + + @Override + public String toString() { + return String.format("PrimitiveClassDesc[%s]", displayName()); + } +} diff --git a/tests/test_data/std/jdk/internal/constant/ReferenceClassDescImpl.class b/tests/test_data/std/jdk/internal/constant/ReferenceClassDescImpl.class new file mode 100644 index 00000000..3816499c Binary files /dev/null and b/tests/test_data/std/jdk/internal/constant/ReferenceClassDescImpl.class differ diff --git a/tests/test_data/std/jdk/internal/constant/ReferenceClassDescImpl.java b/tests/test_data/std/jdk/internal/constant/ReferenceClassDescImpl.java new file mode 100644 index 00000000..b4ec9c13 --- /dev/null +++ b/tests/test_data/std/jdk/internal/constant/ReferenceClassDescImpl.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.constant; + +import java.lang.constant.ClassDesc; +import java.lang.invoke.MethodHandles; + +import static jdk.internal.constant.ConstantUtils.*; + +/** + * A nominal descriptor for a class, + * interface, or array type. A {@linkplain ReferenceClassDescImpl} corresponds to a + * {@code Constant_Class_info} entry in the constant pool of a classfile. + */ +public final class ReferenceClassDescImpl implements ClassDesc { + private final String descriptor; + + private ReferenceClassDescImpl(String descriptor) { + this.descriptor = descriptor; + } + + /** + * Creates a {@linkplain ClassDesc} from a descriptor string for a class or + * interface type or an array type. + * + * @param descriptor a field descriptor string for a class or interface type + * @throws IllegalArgumentException if the descriptor string is not a valid + * field descriptor string, or does not describe a class or interface type + * @jvms 4.3.2 Field Descriptors + */ + public static ReferenceClassDescImpl of(String descriptor) { + int dLen = descriptor.length(); + int len = ConstantUtils.skipOverFieldSignature(descriptor, 0, dLen, false); + if (len <= 1 || len != dLen) + throw new IllegalArgumentException(String.format("not a valid reference type descriptor: %s", descriptor)); + return new ReferenceClassDescImpl(descriptor); + } + + /** + * Creates a {@linkplain ClassDesc} from a pre-validated descriptor string + * for a class or interface type or an array type. + * + * @param descriptor a field descriptor string for a class or interface type + * @jvms 4.3.2 Field Descriptors + */ + public static ReferenceClassDescImpl ofValidated(String descriptor) { + return new ReferenceClassDescImpl(descriptor); + } + + /** + * Creates a {@linkplain ClassDesc} from a pre-validated descriptor string + * for a class or interface type or an array type. + * + * @param descriptor a field descriptor string for a class or interface type + * @jvms 4.3.2 Field Descriptors + */ + public static ClassDesc ofValidatedBinaryName(String typeSwitchClassName) { + return ofValidated("L" + binaryToInternal(typeSwitchClassName) + ";"); + } + + @Override + public String descriptorString() { + return descriptor; + } + + @Override + public Class resolveConstantDesc(MethodHandles.Lookup lookup) + throws ReflectiveOperationException { + if (isArray()) { + if (isPrimitiveArray()) { + return lookup.findClass(descriptor); + } + // Class.forName is slow on class or interface arrays + int depth = ConstantUtils.arrayDepth(descriptor); + Class clazz = lookup.findClass(internalToBinary(descriptor.substring(depth + 1, descriptor.length() - 1))); + for (int i = 0; i < depth; i++) + clazz = clazz.arrayType(); + return clazz; + } + return lookup.findClass(internalToBinary(dropFirstAndLastChar(descriptor))); + } + + /** + * Whether the descriptor is one of a primitive array, given this is + * already a valid reference type descriptor. + */ + private boolean isPrimitiveArray() { + // All L-type descriptors must end with a semicolon; same for reference + // arrays, leaving primitive arrays the only ones without a final semicolon + return descriptor.charAt(descriptor.length() - 1) != ';'; + } + + /** + * Returns {@code true} if this {@linkplain ReferenceClassDescImpl} is + * equal to another {@linkplain ReferenceClassDescImpl}. Equality is + * determined by the two class descriptors having equal class descriptor + * strings. + * + * @param o the {@code ClassDesc} to compare to this + * {@code ClassDesc} + * @return {@code true} if the specified {@code ClassDesc} + * is equal to this {@code ClassDesc}. + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof ReferenceClassDescImpl constant) { + return descriptor.equals(constant.descriptor); + } + return false; + } + + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + @Override + public String toString() { + return String.format("ClassDesc[%s]", displayName()); + } +} diff --git a/tests/test_data/std/jdk/internal/event/DeserializationEvent.class b/tests/test_data/std/jdk/internal/event/DeserializationEvent.class new file mode 100644 index 00000000..1f8b52c1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/DeserializationEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/DeserializationEvent.java b/tests/test_data/std/jdk/internal/event/DeserializationEvent.java new file mode 100644 index 00000000..0caf6131 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/DeserializationEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event details relating to deserialization. + */ + +public final class DeserializationEvent extends Event { + public boolean filterConfigured; + public String filterStatus; + public Class type; + public int arrayLength; + public long objectReferences; + public long depth; + public long bytesRead; + public Class exceptionType; + public String exceptionMessage; +} diff --git a/tests/test_data/std/jdk/internal/event/ErrorThrownEvent.class b/tests/test_data/std/jdk/internal/event/ErrorThrownEvent.class new file mode 100644 index 00000000..8f2ceec9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/ErrorThrownEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/ErrorThrownEvent.java b/tests/test_data/std/jdk/internal/event/ErrorThrownEvent.java new file mode 100644 index 00000000..c9f4ff3b --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/ErrorThrownEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording error exceptions. + */ +public final class ErrorThrownEvent extends Event { + public String message; + public Class thrownClass; + + public static void commit(long start, String message, Class thrownClass) { + // Generated by JFR + } + + public static boolean enabled() { + // Generated by JFR + return false; + } + + public static long timestamp() { + // Generated by JFR + return 0; + } +} diff --git a/tests/test_data/std/jdk/internal/event/Event.class b/tests/test_data/std/jdk/internal/event/Event.class new file mode 100644 index 00000000..e34dd018 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/Event.class differ diff --git a/tests/test_data/std/jdk/internal/event/Event.java b/tests/test_data/std/jdk/internal/event/Event.java new file mode 100644 index 00000000..b01bb289 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/Event.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Base class for events, to be subclassed in order to define events and their + * fields. + * + * @hidden + */ +public abstract class Event { + /** + * Sole constructor, for invocation by subclass constructors, typically + * implicit. + */ + protected Event() { + } + + /** + * Starts the timing of this event. + */ + public void begin() { + } + + /** + * Ends the timing of this event. + * + * The {@code end} method must be invoked after the {@code begin} method. + */ + public void end() { + } + + /** + * Writes the field values, time stamp, and event duration. + *

+ * If the event starts with an invocation of the {@code begin} method, but does + * not end with an explicit invocation of the {@code end} method, then the event + * ends when the {@code commit} method is invoked. + */ + public void commit() { + } + + /** + * Returns {@code true} if the event is enabled, {@code false} otherwise + * + * @return {@code true} if event is enabled, {@code false} otherwise + */ + public boolean isEnabled() { + return false; + } + + /** + * Returns {@code true} if the event is enabled and if the duration is within + * the threshold for the event, {@code false} otherwise. + * + * @return {@code true} if the event can be written, {@code false} otherwise + */ + public boolean shouldCommit() { + return false; + } + + /** + * Sets a field value. + * + * @param index the index of the field to set + * @param value value to set, can be {@code null} + * @throws UnsupportedOperationException if functionality is not supported + * @throws IndexOutOfBoundsException if {@code index} is less than {@code 0} or + * greater than or equal to the number of fields specified for the event + */ + public void set(int index, Object value) { + } +} diff --git a/tests/test_data/std/jdk/internal/event/EventHelper$ThreadTrackHolder.class b/tests/test_data/std/jdk/internal/event/EventHelper$ThreadTrackHolder.class new file mode 100644 index 00000000..3fca626f Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/EventHelper$ThreadTrackHolder.class differ diff --git a/tests/test_data/std/jdk/internal/event/EventHelper.class b/tests/test_data/std/jdk/internal/event/EventHelper.class new file mode 100644 index 00000000..7f2abf88 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/EventHelper.class differ diff --git a/tests/test_data/std/jdk/internal/event/EventHelper.java b/tests/test_data/std/jdk/internal/event/EventHelper.java new file mode 100644 index 00000000..4da2d585 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/EventHelper.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +import jdk.internal.access.JavaUtilJarAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.ThreadTracker; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.time.Duration; +import java.time.Instant; +import java.util.Date; +import java.util.stream.Collectors; +import java.util.stream.LongStream; + +/** + * A helper class to have events logged to a JDK Event Logger. + */ + +public final class EventHelper { + + private static final JavaUtilJarAccess JUJA = SharedSecrets.javaUtilJarAccess(); + private static volatile boolean loggingSecurity; + private static volatile System.Logger securityLogger; + private static final VarHandle LOGGER_HANDLE; + static { + try { + LOGGER_HANDLE = + MethodHandles.lookup().findStaticVarHandle( + EventHelper.class, "securityLogger", System.Logger.class); + } catch (ReflectiveOperationException e) { + throw new Error(e); + } + } + private static final System.Logger.Level LOG_LEVEL = System.Logger.Level.DEBUG; + + // helper class used for logging security related events for now + private static final String SECURITY_LOGGER_NAME = "jdk.event.security"; + + + public static void logTLSHandshakeEvent(Instant start, + String peerHost, + int peerPort, + String cipherSuite, + String protocolVersion, + long peerCertId) { + assert securityLogger != null; + String prepend = getDurationString(start); + securityLogger.log(LOG_LEVEL, prepend + + " TLSHandshake: {0}:{1,number,#}, {2}, {3}, {4,number,#}", + peerHost, peerPort, protocolVersion, cipherSuite, peerCertId); + } + + public static void logSecurityPropertyEvent(String key, + String value) { + + assert securityLogger != null; + securityLogger.log(LOG_LEVEL, + "SecurityPropertyModification: key:{0}, value:{1}", key, value); + } + + public static void logX509ValidationEvent(long anchorCertId, + long[] certIds) { + assert securityLogger != null; + String codes = LongStream.of(certIds) + .mapToObj(Long::toString) + .collect(Collectors.joining(", ")); + securityLogger.log(LOG_LEVEL, + "ValidationChain: {0,number,#}, {1}", anchorCertId, codes); + } + + public static void logX509CertificateEvent(String algId, + String serialNum, + String subject, + String issuer, + String keyType, + int length, + long certId, + long beginDate, + long endDate) { + assert securityLogger != null; + securityLogger.log(LOG_LEVEL, "X509Certificate: Alg:{0}, Serial:{1}" + + ", Subject:{2}, Issuer:{3}, Key type:{4}, Length:{5,number,#}" + + ", Cert Id:{6,number,#}, Valid from:{7}, Valid until:{8}", + algId, serialNum, subject, issuer, keyType, length, + certId, new Date(beginDate), new Date(endDate)); + } + + /** + * Method to calculate a duration timestamp for events which measure + * the start and end times of certain operations. + * @param start Instant indicating when event started recording + * @return A string representing duraction from start time to + * time of this method call. Empty string is start is null. + */ + private static String getDurationString(Instant start) { + if (start != null) { + if (start.equals(Instant.MIN)) { + return "N/A"; + } + Duration duration = Duration.between(start, Instant.now()); + long micros = duration.toNanos() / 1_000; + if (micros < 1_000_000) { + return "duration = " + (micros / 1_000.0) + " ms:"; + } else { + return "duration = " + ((micros / 1_000) / 1_000.0) + " s:"; + } + } else { + return ""; + } + } + + private static class ThreadTrackHolder { + static final ThreadTracker TRACKER = new ThreadTracker(); + } + + private static Object tryBeginLookup() { + return ThreadTrackHolder.TRACKER.tryBegin(); + } + + private static void endLookup(Object key) { + ThreadTrackHolder.TRACKER.end(key); + } + + /** + * Helper to determine if security events are being logged + * at a preconfigured logging level. The configuration value + * is read once at class initialization. + * + * @return boolean indicating whether an event should be logged + */ + public static boolean isLoggingSecurity() { + Object key; + // Avoid bootstrap issues where + // * commitEvent triggers early loading of System Logger but where + // the verification process still has JarFiles locked + // * the loading of the logging libraries involves recursive + // calls to security libraries triggering recursion + if (securityLogger == null && !JUJA.isInitializing() && (key = tryBeginLookup()) != null) { + try { + LOGGER_HANDLE.compareAndSet(null, System.getLogger(SECURITY_LOGGER_NAME)); + loggingSecurity = securityLogger.isLoggable(LOG_LEVEL); + } finally { + endLookup(key); + } + } + return loggingSecurity; + } +} diff --git a/tests/test_data/std/jdk/internal/event/ExceptionStatisticsEvent.class b/tests/test_data/std/jdk/internal/event/ExceptionStatisticsEvent.class new file mode 100644 index 00000000..8dcee1c6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/ExceptionStatisticsEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/ExceptionStatisticsEvent.java b/tests/test_data/std/jdk/internal/event/ExceptionStatisticsEvent.java new file mode 100644 index 00000000..4ab58b7c --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/ExceptionStatisticsEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording number of exceptions that has been created. + */ +public class ExceptionStatisticsEvent extends Event { + + public long throwables; + + public static void commit(long timestamp, long throwables) { + // Generated by JFR + } + + public static boolean enabled() { + // Generated by JFR + return false; + } + + public static long timestamp() { + // Generated by JFR + return 0; + } +} diff --git a/tests/test_data/std/jdk/internal/event/ExceptionThrownEvent.class b/tests/test_data/std/jdk/internal/event/ExceptionThrownEvent.class new file mode 100644 index 00000000..85269edb Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/ExceptionThrownEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/ExceptionThrownEvent.java b/tests/test_data/std/jdk/internal/event/ExceptionThrownEvent.java new file mode 100644 index 00000000..45c13e30 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/ExceptionThrownEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording all exception. + */ +public final class ExceptionThrownEvent extends Event { + public String message; + public Class thrownClass; + + public static void commit(long start, String message, Class thrownClass) { + // Generated by JFR + } + + public static boolean enabled() { + // Generated by JFR + return false; + } + + public static long timestamp() { + // Generated by JFR + return 0; + } +} diff --git a/tests/test_data/std/jdk/internal/event/FileForceEvent.class b/tests/test_data/std/jdk/internal/event/FileForceEvent.class new file mode 100644 index 00000000..244d8567 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/FileForceEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/FileForceEvent.java b/tests/test_data/std/jdk/internal/event/FileForceEvent.java new file mode 100644 index 00000000..f6dec6c8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/FileForceEvent.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * A JFR event for forced updates written to files. This event is mirrored in + * {@code jdk.jfr.events.FileForceEvent } where the event annotations are + * provided. Some of the methods are replaced by generated + * methods when jfr is enabled. Note that the order of the arguments of the + * {@link #commit(long, long, String, boolean)} method + * must be the same as the order of the fields. + */ +public class FileForceEvent extends Event { + + // THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT! + // The order must match the argument order of the generated commit method. + public String path; + public boolean metaData; + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@link #commit(long, long, String, boolean)}. + * + * @param start timestamp of the start of the operation + * @param path the full pathname of the file + * @param metaData true if the file metadata is updated + */ + public static void offer(long start, String path, boolean metaData) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + commit(start, duration, path, metaData); + } + } + + /** + * Actually commit an event. The implementation is generated automatically. + * The order of the fields must be the same as the parameters in this method. + * + * @param start timestamp of the start of the operation + * @param duration time in nanoseconds to complete the operation + * @param path the full pathname of the file + * @param metaData true if the file metadata is updated + */ + public static void commit(long start, long duration, String path, boolean metaData) { + // Generated by JFR + } + + /** + * Determine if an event should be emitted. The duration of the operation + * must exceed some threshold in order to commit the event. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @param duration time in nanoseconds to complete the operation + * @return true if the event should be commited + */ + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + /** + * Determine if this kind of event is enabled. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return true if this type of event is enabled, false otherwise + */ + public static boolean enabled() { + // Generated by JFR + return false; + } + + /** + * Fetch the current timestamp in nanoseconds. This method is used + * to determine the start and end of an operation. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return the current timestamp value + */ + public static long timestamp() { + // Generated by JFR + return 0L; + } +} diff --git a/tests/test_data/std/jdk/internal/event/FileReadEvent.class b/tests/test_data/std/jdk/internal/event/FileReadEvent.class new file mode 100644 index 00000000..24eb6b85 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/FileReadEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/FileReadEvent.java b/tests/test_data/std/jdk/internal/event/FileReadEvent.java new file mode 100644 index 00000000..34ff4e9d --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/FileReadEvent.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording file reads. + */ +public final class FileReadEvent extends Event { + + // The order of these fields must be the same as the parameters in + // commit(..., String, long, boolean) + public String path; + public long bytesRead; + public boolean endOfFile; + + public static boolean enabled() { + // Generated by JFR + return false; + } + + public static long timestamp() { + // Generated by JFR + return 0L; + } + + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + public static void commit(long start, long duration, String path, long bytesRead, boolean endOfFile) { + // Generated by JFR + } +} diff --git a/tests/test_data/std/jdk/internal/event/FileWriteEvent.class b/tests/test_data/std/jdk/internal/event/FileWriteEvent.class new file mode 100644 index 00000000..2be7d1d0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/FileWriteEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/FileWriteEvent.java b/tests/test_data/std/jdk/internal/event/FileWriteEvent.java new file mode 100644 index 00000000..7a39f93b --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/FileWriteEvent.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording file writes. + */ +public final class FileWriteEvent extends Event { + + // The order of these fields must be the same as the parameters in + // commit(..., String, long) + public String path; + public long bytesWritten; + + public static boolean enabled() { + // Generated by JFR + return false; + } + + public static long timestamp() { + // Generated by JFR + return 0L; + } + + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + public static void commit(long start, long duration, String path, long bytesWritten) { + // Generated by JFR + } +} diff --git a/tests/test_data/std/jdk/internal/event/JFRTracing.class b/tests/test_data/std/jdk/internal/event/JFRTracing.class new file mode 100644 index 00000000..6a974714 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/JFRTracing.class differ diff --git a/tests/test_data/std/jdk/internal/event/JFRTracing.java b/tests/test_data/std/jdk/internal/event/JFRTracing.java new file mode 100644 index 00000000..5882b2dd --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/JFRTracing.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +import sun.nio.ch.FileChannelImpl; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; +import java.lang.Throwable; +import java.lang.reflect.Field; + +/** + * Helper class to enable JFR tracing. + */ +public final class JFRTracing { + + public static void enable() throws NoSuchFieldException, IllegalAccessException { + enable(Throwable.class); + enable(FileInputStream.class); + enable(FileOutputStream.class); + enable(FileChannelImpl.class); + enable(RandomAccessFile.class); + } + + private static void enable(Class clazz) throws NoSuchFieldException, IllegalAccessException { + Field field = clazz.getDeclaredField("jfrTracing"); + field.setAccessible(true); + field.setBoolean(null, true); + } +} diff --git a/tests/test_data/std/jdk/internal/event/ProcessStartEvent.class b/tests/test_data/std/jdk/internal/event/ProcessStartEvent.class new file mode 100644 index 00000000..4ba52d51 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/ProcessStartEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/ProcessStartEvent.java b/tests/test_data/std/jdk/internal/event/ProcessStartEvent.java new file mode 100644 index 00000000..ca9264fe --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/ProcessStartEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event for the start of an OS procsss + */ + +public final class ProcessStartEvent extends Event { + public long pid; + public String directory; + public String command; +} diff --git a/tests/test_data/std/jdk/internal/event/SecurityPropertyModificationEvent.class b/tests/test_data/std/jdk/internal/event/SecurityPropertyModificationEvent.class new file mode 100644 index 00000000..21ce36ff Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/SecurityPropertyModificationEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/SecurityPropertyModificationEvent.java b/tests/test_data/std/jdk/internal/event/SecurityPropertyModificationEvent.java new file mode 100644 index 00000000..294ff902 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/SecurityPropertyModificationEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event details relating to the modification of a Security property. + */ + +public final class SecurityPropertyModificationEvent extends Event { + public String key; + public String value; +} diff --git a/tests/test_data/std/jdk/internal/event/SecurityProviderServiceEvent.class b/tests/test_data/std/jdk/internal/event/SecurityProviderServiceEvent.class new file mode 100644 index 00000000..de214d76 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/SecurityProviderServiceEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/SecurityProviderServiceEvent.java b/tests/test_data/std/jdk/internal/event/SecurityProviderServiceEvent.java new file mode 100644 index 00000000..8dca527c --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/SecurityProviderServiceEvent.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event recording details of Provider.getService(String type, String algorithm) calls + */ + +public final class SecurityProviderServiceEvent extends Event { + private static final SecurityProviderServiceEvent EVENT = new SecurityProviderServiceEvent(); + + /** + * Returns {@code true} if event is enabled, {@code false} otherwise. + */ + public static boolean isTurnedOn() { + return EVENT.isEnabled(); + } + + public String type; + public String algorithm; + public String provider; +} diff --git a/tests/test_data/std/jdk/internal/event/SerializationMisdeclarationEvent.class b/tests/test_data/std/jdk/internal/event/SerializationMisdeclarationEvent.class new file mode 100644 index 00000000..d3bd1921 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/SerializationMisdeclarationEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/SerializationMisdeclarationEvent.java b/tests/test_data/std/jdk/internal/event/SerializationMisdeclarationEvent.java new file mode 100644 index 00000000..3ffdf485 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/SerializationMisdeclarationEvent.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * A JFR event for serialization misdeclarations. + * This event is mirrored in {@code jdk.jfr.events.SerializationMisdeclarationEvent} + * where the metadata for the event is provided with annotations. + * Some of the methods are replaced by generated methods when jfr is enabled. + * Note that the order of the arguments of the {@link #commit(long,Class,String)} + * method must be the same as the order of the fields. + */ +public class SerializationMisdeclarationEvent extends Event { + + public Class misdeclaredClass; + public String message; + + /** + * Commit a serialization misdeclaration event. + * The implementation of this method is generated automatically if jfr is enabled. + * The order of the fields must be the same as the parameters in this method. + * {@code commit(long,Class,String)} + * + * @param start timestamp of the start of the operation + * @param misdeclaredClass the affected class + * @param message the specific event message + */ + public static void commit(long start, Class misdeclaredClass, String message) { + // Generated by JFR + } + + /** + * Determine if an event should be emitted. The duration of the operation + * must exceed some threshold in order to commit the event. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @param duration time in nanoseconds to complete the operation + * @return true if the event should be commited + */ + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + /** + * Determine if this kind of event is enabled. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return whether serialization misdeclaration events are enabled + */ + public static boolean enabled() { + // Generated by JFR + return false; + } + + /** + * Fetch the current timestamp in nanoseconds. This method is used + * to determine the start and end of an operation. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return the current timestamp value + */ + public static long timestamp() { + // Generated by JFR + return 0L; + } + +} diff --git a/tests/test_data/std/jdk/internal/event/SocketReadEvent.class b/tests/test_data/std/jdk/internal/event/SocketReadEvent.class new file mode 100644 index 00000000..a4b2c235 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/SocketReadEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/SocketReadEvent.java b/tests/test_data/std/jdk/internal/event/SocketReadEvent.java new file mode 100644 index 00000000..d5f6c324 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/SocketReadEvent.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnixDomainSocketAddress; + +/** + * A JFR event for socket read operations. This event is mirrored in + * {@code jdk.jfr.events.SocketReadEvent } where the metadata for the event is + * provided with annotations. Some of the methods are replaced by generated + * methods when jfr is enabled. Note that the order of the arguments of the + * {@link #commit(long, long, String, String, int, long, long, boolean)} method + * must be the same as the order of the fields. + */ +public class SocketReadEvent extends Event { + + // THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT! + // The order must match the argument order of the generated commit method. + public String host; + public String address; + public int port; + public long timeout; + public long bytesRead; + public boolean endOfStream; + + /** + * Actually commit a socket read event. The implementation + * of this method is generated automatically if jfr is enabled. + * The order of the fields must be the same as the parameters in this method. + * {@code commit(..., String, String, int, long, long, boolean)} + * + * @param start timestamp of the start of the operation + * @param duration time in nanoseconds to complete the operation + * @param host remote host of the transfer + * @param address remote address of the transfer + * @param port remote port of the transfer + * @param timeout timeout setting for the read + * @param bytes number of bytes that were transferred + * @param endOfStream has the end of the stream been reached + */ + public static void commit(long start, long duration, String host, String address, int port, long timeout, long bytes, boolean endOfStream) { + // Generated by JFR + } + + /** + * Determine if an event should be emitted. The duration of the operation + * must exceed some threshold in order to commit the event. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @param duration time in nanoseconds to complete the operation + * @return true if the event should be commited + */ + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + /** + * Determine if this kind of event is enabled. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return true if socket read events are enabled, false otherwise + */ + public static boolean enabled() { + // Generated by JFR + return false; + } + + /** + * Fetch the current timestamp in nanoseconds. This method is used + * to determine the start and end of an operation. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return the current timestamp value + */ + public static long timestamp() { + // Generated by JFR + return 0L; + } + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@link #emit(long, long, long, SocketAddress, long)} + * + * @param start the start time + * @param nbytes how many bytes were transferred + * @param remote the address of the remote socket + * @param timeout maximum time to wait + */ + public static void offer(long start, long nbytes, SocketAddress remote, long timeout) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + emit(start, duration, nbytes, remote, timeout); + } + } + + /** + * Helper method to perform a common task of getting event data ready and + * then emitting the event by calling + * {@link #commit(long, long, String, String, int, long, long, boolean)}. + * + * @param start the start time + * @param duration the duration + * @param nbytes how many bytes were transferred + * @param remote the address of the remote socket + * @param timeout maximum time to wait + */ + public static void emit(long start, long duration, long nbytes, SocketAddress remote, long timeout) { + boolean eof = nbytes < 0 ? true : false; + nbytes = nbytes < 0 ? 0 : nbytes; + if (remote instanceof InetSocketAddress isa) { + commit(start, duration, isa.getHostString(), isa.getAddress().getHostAddress(), isa.getPort(), timeout, nbytes, eof); + } else if (remote instanceof UnixDomainSocketAddress udsa) { + String path = "[" + udsa.getPath().toString() + "]"; + commit(start, duration, "Unix domain socket", path, 0, timeout, nbytes, eof); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/event/SocketWriteEvent.class b/tests/test_data/std/jdk/internal/event/SocketWriteEvent.class new file mode 100644 index 00000000..9962c789 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/SocketWriteEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/SocketWriteEvent.java b/tests/test_data/std/jdk/internal/event/SocketWriteEvent.java new file mode 100644 index 00000000..7c56ef82 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/SocketWriteEvent.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.UnixDomainSocketAddress; + +/** + * A JFR event for socket write operations. This event is mirrored in + * {@code jdk.jfr.events.SocketWriteEvent } where the metadata for the event is + * provided with annotations. Some of the methods are replaced by generated + * methods when jfr is enabled. Note that the order of the arguments of the + * {@link #commit(long, long, String, String, int, long)} method + * must be the same as the order of the fields. + */ +public class SocketWriteEvent extends Event { + + // THE ORDER OF THE FOLLOWING FIELDS IS IMPORTANT! + // The order must match the argument order of the generated commit method. + public String host; + public String address; + public int port; + public long bytesWritten; + + /** + * Actually commit a socket write event. This is generated automatically. + * The order of the fields must be the same as the parameters in this method. + * {@code commit(..., String, String, int, long)} + * + * @param start timestamp of the start of the operation + * @param duration time in nanoseconds to complete the operation + * @param host remote host of the transfer + * @param address remote address of the transfer + * @param port remote port of the transfer + * @param bytes number of bytes that were transferred + */ + public static void commit(long start, long duration, String host, String address, int port, long bytes) { + // Generated by JFR + } + + /** + * Determine if an event should be emitted. The duration of the operation + * must exceed some threshold in order to commit the event. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @param duration time in nanoseconds to complete the operation + * @return true if the event should be commited + */ + public static boolean shouldCommit(long duration) { + // Generated by JFR + return false; + } + + /** + * Determine if this kind of event is enabled. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return true if socket write events are enabled, false otherwise + */ + public static boolean enabled() { + // Generated by JFR + return false; + } + + /** + * Fetch the current timestamp in nanoseconds. This method is used + * to determine the start and end of an operation. The implementation + * of this method is generated automatically if jfr is enabled. + * + * @return the current timestamp value + */ + public static long timestamp() { + // Generated by JFR + return 0L; + } + + /** + * Helper method to offer the data needed to potentially commit an event. + * The duration of the operation is computed using the current + * timestamp and the given start time. If the duration is meets + * or exceeds the configured value (determined by calling the generated method + * {@link #shouldCommit(long)}), an event will be emitted by calling + * {@link #emit(long, long, long, SocketAddress)}. + * + * @param start the start time + * @param bytesWritten how many bytes were sent + * @param remote the address of the remote socket being written to + */ + public static void offer(long start, long bytesWritten, SocketAddress remote) { + long duration = timestamp() - start; + if (shouldCommit(duration)) { + emit(start, duration, bytesWritten, remote); + } + } + + /** + * Helper method to perform a common task of getting event data ready and + * then emitting the event by calling + * {@link #commit(long, long, String, String, int, long)}. + * + * @param start the start time + * @param duration the duration + * @param bytesWritten how many bytes were sent + * @param remote the address of the remote socket being written to + */ + public static void emit(long start, long duration, long bytesWritten, SocketAddress remote) { + long bytes = bytesWritten < 0 ? 0 : bytesWritten; + if (remote instanceof InetSocketAddress isa) { + commit(start, duration, isa.getHostString(), isa.getAddress().getHostAddress(), isa.getPort(), bytes); + } else if (remote instanceof UnixDomainSocketAddress udsa) { + String path = "[" + udsa.getPath().toString() + "]"; + commit(start, duration, "Unix domain socket", path, 0, bytes); + } + } +} diff --git a/tests/test_data/std/jdk/internal/event/TLSHandshakeEvent.class b/tests/test_data/std/jdk/internal/event/TLSHandshakeEvent.class new file mode 100644 index 00000000..0b2f7be2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/TLSHandshakeEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/TLSHandshakeEvent.java b/tests/test_data/std/jdk/internal/event/TLSHandshakeEvent.java new file mode 100644 index 00000000..f763a0e4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/TLSHandshakeEvent.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event recording details of successful TLS handshakes. + */ + +public final class TLSHandshakeEvent extends Event { + public String peerHost; + public int peerPort; + public String protocolVersion; + public String cipherSuite; + public long certificateId; +} diff --git a/tests/test_data/std/jdk/internal/event/ThreadSleepEvent.class b/tests/test_data/std/jdk/internal/event/ThreadSleepEvent.class new file mode 100644 index 00000000..7dad7373 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/ThreadSleepEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/ThreadSleepEvent.java b/tests/test_data/std/jdk/internal/event/ThreadSleepEvent.java new file mode 100644 index 00000000..ab9b377c --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/ThreadSleepEvent.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event recording thread sleeping. + */ + +public final class ThreadSleepEvent extends Event { + private static final ThreadSleepEvent EVENT = new ThreadSleepEvent(); + + /** + * Returns {@code true} if event is enabled, {@code false} otherwise. + */ + public static boolean isTurnedOn() { + return EVENT.isEnabled(); + } + + public long time; +} diff --git a/tests/test_data/std/jdk/internal/event/ThrowableTracer.class b/tests/test_data/std/jdk/internal/event/ThrowableTracer.class new file mode 100644 index 00000000..5a4f10ef Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/ThrowableTracer.class differ diff --git a/tests/test_data/std/jdk/internal/event/ThrowableTracer.java b/tests/test_data/std/jdk/internal/event/ThrowableTracer.java new file mode 100644 index 00000000..f7076b44 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/ThrowableTracer.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * Helper class for exception events. + */ +public final class ThrowableTracer { + + private static final AtomicLong numThrowables = new AtomicLong(); + + public static void traceError(Class clazz, String message) { + if (OutOfMemoryError.class.isAssignableFrom(clazz)) { + return; + } + + if (ErrorThrownEvent.enabled()) { + long timestamp = ErrorThrownEvent.timestamp(); + ErrorThrownEvent.commit(timestamp, message, clazz); + } + if (ExceptionThrownEvent.enabled()) { + long timestamp = ExceptionThrownEvent.timestamp(); + ExceptionThrownEvent.commit(timestamp, message, clazz); + } + numThrowables.incrementAndGet(); + } + + public static void traceThrowable(Class clazz, String message) { + if (ExceptionThrownEvent.enabled()) { + long timestamp = ExceptionThrownEvent.timestamp(); + ExceptionThrownEvent.commit(timestamp, message, clazz); + } + numThrowables.incrementAndGet(); + } + + public static void emitStatistics() { + long timestamp = ExceptionStatisticsEvent.timestamp(); + ExceptionStatisticsEvent.commit(timestamp, numThrowables.get()); + } +} diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadEndEvent.class b/tests/test_data/std/jdk/internal/event/VirtualThreadEndEvent.class new file mode 100644 index 00000000..06e53cce Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/VirtualThreadEndEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadEndEvent.java b/tests/test_data/std/jdk/internal/event/VirtualThreadEndEvent.java new file mode 100644 index 00000000..6920eb03 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/VirtualThreadEndEvent.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording that a virtual thread has terminated. + */ +public class VirtualThreadEndEvent extends Event { + private static final VirtualThreadEndEvent EVENT = new VirtualThreadEndEvent(); + + /** + * Returns {@code true} if event is enabled, {@code false} otherwise. + */ + public static boolean isTurnedOn() { + return EVENT.isEnabled(); + } + + public long javaThreadId; +} diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadPinnedEvent.class b/tests/test_data/std/jdk/internal/event/VirtualThreadPinnedEvent.class new file mode 100644 index 00000000..440c1b58 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/VirtualThreadPinnedEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadPinnedEvent.java b/tests/test_data/std/jdk/internal/event/VirtualThreadPinnedEvent.java new file mode 100644 index 00000000..73746dc4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/VirtualThreadPinnedEvent.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording that a virtual thread has parked on its carrier thread. + */ +public class VirtualThreadPinnedEvent extends Event { +} diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadStartEvent.class b/tests/test_data/std/jdk/internal/event/VirtualThreadStartEvent.class new file mode 100644 index 00000000..a189b825 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/VirtualThreadStartEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadStartEvent.java b/tests/test_data/std/jdk/internal/event/VirtualThreadStartEvent.java new file mode 100644 index 00000000..5e80e8ac --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/VirtualThreadStartEvent.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording that a virtual thread has been started. + */ +public class VirtualThreadStartEvent extends Event { + private static final VirtualThreadStartEvent EVENT = new VirtualThreadStartEvent(); + + /** + * Returns {@code true} if event is enabled, {@code false} otherwise. + */ + public static boolean isTurnedOn() { + return EVENT.isEnabled(); + } + + public long javaThreadId; +} diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadSubmitFailedEvent.class b/tests/test_data/std/jdk/internal/event/VirtualThreadSubmitFailedEvent.class new file mode 100644 index 00000000..0dcb7c90 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/VirtualThreadSubmitFailedEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/VirtualThreadSubmitFailedEvent.java b/tests/test_data/std/jdk/internal/event/VirtualThreadSubmitFailedEvent.java new file mode 100644 index 00000000..e12e4008 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/VirtualThreadSubmitFailedEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.event; + +/** + * Event recording when an attempt to submit the task for a virtual thread failed. + */ +public class VirtualThreadSubmitFailedEvent extends Event { + public long javaThreadId; + public String exceptionMessage; +} diff --git a/tests/test_data/std/jdk/internal/event/X509CertificateEvent.class b/tests/test_data/std/jdk/internal/event/X509CertificateEvent.class new file mode 100644 index 00000000..37529650 Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/X509CertificateEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/X509CertificateEvent.java b/tests/test_data/std/jdk/internal/event/X509CertificateEvent.java new file mode 100644 index 00000000..5b668ca2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/X509CertificateEvent.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + + +/** + * Event recording details of X.509 Certificate. + */ + +public final class X509CertificateEvent extends Event { + private static final X509CertificateEvent EVENT = new X509CertificateEvent(); + + /** + * Returns {@code true} if event is enabled, {@code false} otherwise. + */ + public static boolean isTurnedOn() { + return EVENT.isEnabled(); + } + + public String algorithm; + public String serialNumber; + public String subject; + public String issuer; + public String keyType; + public int keyLength; + public long certificateId; + public long validFrom; + public long validUntil; +} diff --git a/tests/test_data/std/jdk/internal/event/X509ValidationEvent.class b/tests/test_data/std/jdk/internal/event/X509ValidationEvent.class new file mode 100644 index 00000000..c18abcbf Binary files /dev/null and b/tests/test_data/std/jdk/internal/event/X509ValidationEvent.class differ diff --git a/tests/test_data/std/jdk/internal/event/X509ValidationEvent.java b/tests/test_data/std/jdk/internal/event/X509ValidationEvent.java new file mode 100644 index 00000000..854b1a05 --- /dev/null +++ b/tests/test_data/std/jdk/internal/event/X509ValidationEvent.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.event; + +/** + * Event recording details of X.509 Certificate serial numbers + * used in X509 cert path validation. + */ + +public final class X509ValidationEvent extends Event { + public long certificateId; + public int certificatePosition; + public long validationCounter; +} diff --git a/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl$SegmentSplitter.class b/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl$SegmentSplitter.class new file mode 100644 index 00000000..eb836181 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl$SegmentSplitter.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl.class b/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl.class new file mode 100644 index 00000000..40346211 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl.java new file mode 100644 index 00000000..325dbe10 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/AbstractMemorySegmentImpl.java @@ -0,0 +1,985 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.lang.foreign.*; +import java.lang.reflect.Array; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.DoubleBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.nio.ShortBuffer; +import java.nio.charset.Charset; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import jdk.internal.access.JavaNioAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.access.foreign.UnmapperProxy; +import jdk.internal.misc.ScopedMemoryAccess; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; +import jdk.internal.util.ArraysSupport; +import jdk.internal.util.Preconditions; +import jdk.internal.vm.annotation.ForceInline; +import sun.nio.ch.DirectBuffer; + +import static java.lang.foreign.ValueLayout.JAVA_BYTE; + +/** + * This abstract class provides an immutable implementation for the {@code MemorySegment} interface. This class contains information + * about the segment's spatial and temporal bounds; each memory segment implementation is associated with an owner thread which is set at creation time. + * Access to certain sensitive operations on the memory segment will fail with {@code IllegalStateException} if the + * segment is either in an invalid state (e.g. it has already been closed) or if access occurs from a thread other + * than the owner thread. See {@link MemorySessionImpl} for more details on management of temporal bounds. Subclasses + * are defined for each memory segment kind, see {@link NativeMemorySegmentImpl}, {@link HeapMemorySegmentImpl} and + * {@link MappedMemorySegmentImpl}. + */ +public abstract sealed class AbstractMemorySegmentImpl + implements MemorySegment, SegmentAllocator, BiFunction, RuntimeException> + permits HeapMemorySegmentImpl, NativeMemorySegmentImpl { + + private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess(); + + static final JavaNioAccess NIO_ACCESS = SharedSecrets.getJavaNioAccess(); + + final long length; + final boolean readOnly; + final MemorySessionImpl scope; + + @ForceInline + AbstractMemorySegmentImpl(long length, boolean readOnly, MemorySessionImpl scope) { + this.length = length; + this.readOnly = readOnly; + this.scope = scope; + } + + abstract AbstractMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope); + + abstract ByteBuffer makeByteBuffer(); + + @Override + public AbstractMemorySegmentImpl asReadOnly() { + return dup(0, length, true, scope); + } + + @Override + public boolean isReadOnly() { + return readOnly; + } + + @Override + public AbstractMemorySegmentImpl asSlice(long offset, long newSize) { + checkBounds(offset, newSize); + return asSliceNoCheck(offset, newSize); + } + + @Override + public AbstractMemorySegmentImpl asSlice(long offset) { + checkBounds(offset, 0); + return asSliceNoCheck(offset, length - offset); + } + + @Override + public MemorySegment asSlice(long offset, long newSize, long byteAlignment) { + checkBounds(offset, newSize); + Utils.checkAlign(byteAlignment); + + if (!isAlignedForElement(offset, byteAlignment)) { + throw new IllegalArgumentException("Target offset incompatible with alignment constraints"); + } + return asSliceNoCheck(offset, newSize); + } + + @Override + public MemorySegment asSlice(long offset, MemoryLayout layout) { + Objects.requireNonNull(layout); + return asSlice(offset, layout.byteSize(), layout.byteAlignment()); + } + + @Override + @CallerSensitive + public final MemorySegment reinterpret(long newSize, Arena arena, Consumer cleanup) { + Objects.requireNonNull(arena); + return reinterpretInternal(Reflection.getCallerClass(), newSize, + MemorySessionImpl.toMemorySession(arena), cleanup); + } + + @Override + @CallerSensitive + public final MemorySegment reinterpret(long newSize) { + return reinterpretInternal(Reflection.getCallerClass(), newSize, scope, null); + } + + @Override + @CallerSensitive + public final MemorySegment reinterpret(Arena arena, Consumer cleanup) { + Objects.requireNonNull(arena); + return reinterpretInternal(Reflection.getCallerClass(), byteSize(), + MemorySessionImpl.toMemorySession(arena), cleanup); + } + + public MemorySegment reinterpretInternal(Class callerClass, long newSize, Scope scope, Consumer cleanup) { + Reflection.ensureNativeAccess(callerClass, MemorySegment.class, "reinterpret"); + Utils.checkNonNegativeArgument(newSize, "newSize"); + if (!isNative()) throw new UnsupportedOperationException("Not a native segment"); + Runnable action = cleanup != null ? + () -> cleanup.accept(SegmentFactories.makeNativeSegmentUnchecked(address(), newSize)) : + null; + return SegmentFactories.makeNativeSegmentUnchecked(address(), newSize, + (MemorySessionImpl)scope, readOnly, action); + } + + private AbstractMemorySegmentImpl asSliceNoCheck(long offset, long newSize) { + return dup(offset, newSize, readOnly, scope); + } + + @Override + public Spliterator spliterator(MemoryLayout elementLayout) { + Objects.requireNonNull(elementLayout); + if (elementLayout.byteSize() == 0) { + throw new IllegalArgumentException("Element layout size cannot be zero"); + } + Utils.checkElementAlignment(elementLayout, "Element layout size is not multiple of alignment"); + if (!isAlignedForElement(0, elementLayout)) { + throw new IllegalArgumentException("Incompatible alignment constraints"); + } + if ((byteSize() % elementLayout.byteSize()) != 0) { + throw new IllegalArgumentException("Segment size is not a multiple of layout size"); + } + return new SegmentSplitter(elementLayout.byteSize(), byteSize() / elementLayout.byteSize(), + this); + } + + @Override + public Stream elements(MemoryLayout elementLayout) { + return StreamSupport.stream(spliterator(elementLayout), false); + } + + @Override + public final MemorySegment fill(byte value){ + checkAccess(0, length, false); + SCOPED_MEMORY_ACCESS.setMemory(sessionImpl(), unsafeGetBase(), unsafeGetOffset(), length, value); + return this; + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + Utils.checkAllocationSizeAndAlign(byteSize, byteAlignment); + return asSlice(0, byteSize, byteAlignment); + } + + /** + * Mismatch over long lengths. + */ + public static long vectorizedMismatchLargeForBytes(MemorySessionImpl aSession, MemorySessionImpl bSession, + Object a, long aOffset, + Object b, long bOffset, + long length) { + long off = 0; + long remaining = length; + int i, size; + boolean lastSubRange = false; + while (remaining > 7 && !lastSubRange) { + if (remaining > Integer.MAX_VALUE) { + size = Integer.MAX_VALUE; + } else { + size = (int) remaining; + lastSubRange = true; + } + i = SCOPED_MEMORY_ACCESS.vectorizedMismatch(aSession, bSession, + a, aOffset + off, + b, bOffset + off, + size, ArraysSupport.LOG2_ARRAY_BYTE_INDEX_SCALE); + if (i >= 0) + return off + i; + + i = size - ~i; + off += i; + remaining -= i; + } + return ~remaining; + } + + @Override + public final ByteBuffer asByteBuffer() { + checkArraySize("ByteBuffer", 1); + ByteBuffer _bb = makeByteBuffer(); + if (readOnly) { + //session is IMMUTABLE - obtain a RO byte buffer + _bb = _bb.asReadOnlyBuffer(); + } + return _bb; + } + + @Override + public final long byteSize() { + return length; + } + + @Override + public boolean isMapped() { + return false; + } + + @Override + public boolean isNative() { + return false; + } + + @Override + public final Optional asOverlappingSlice(MemorySegment other) { + AbstractMemorySegmentImpl that = (AbstractMemorySegmentImpl)Objects.requireNonNull(other); + if (unsafeGetBase() == that.unsafeGetBase()) { // both either native or heap + final long thisStart = this.unsafeGetOffset(); + final long thatStart = that.unsafeGetOffset(); + final long thisEnd = thisStart + this.byteSize(); + final long thatEnd = thatStart + that.byteSize(); + + if (thisStart < thatEnd && thisEnd > thatStart) { //overlap occurs + long offsetToThat = that.address() - this.address(); + long newOffset = offsetToThat >= 0 ? offsetToThat : 0; + return Optional.of(asSlice(newOffset, Math.min(this.byteSize() - newOffset, that.byteSize() + offsetToThat))); + } + } + return Optional.empty(); + } + + @Override + public MemorySegment copyFrom(MemorySegment src) { + MemorySegment.copy(src, 0, this, 0, src.byteSize()); + return this; + } + + @Override + public long mismatch(MemorySegment other) { + Objects.requireNonNull(other); + return MemorySegment.mismatch(this, 0, byteSize(), other, 0, other.byteSize()); + } + + @Override + public void load() { + throw notAMappedSegment(); + } + + @Override + public void unload() { + throw notAMappedSegment(); + } + + @Override + public boolean isLoaded() { + throw notAMappedSegment(); + } + + @Override + public void force() { + throw notAMappedSegment(); + } + + private static UnsupportedOperationException notAMappedSegment() { + throw new UnsupportedOperationException("Not a mapped segment"); + } + + @Override + public final byte[] toArray(ValueLayout.OfByte elementLayout) { + return toArray(byte[].class, elementLayout, byte[]::new, MemorySegment::ofArray); + } + + @Override + public final short[] toArray(ValueLayout.OfShort elementLayout) { + return toArray(short[].class, elementLayout, short[]::new, MemorySegment::ofArray); + } + + @Override + public final char[] toArray(ValueLayout.OfChar elementLayout) { + return toArray(char[].class, elementLayout, char[]::new, MemorySegment::ofArray); + } + + @Override + public final int[] toArray(ValueLayout.OfInt elementLayout) { + return toArray(int[].class, elementLayout, int[]::new, MemorySegment::ofArray); + } + + @Override + public final float[] toArray(ValueLayout.OfFloat elementLayout) { + return toArray(float[].class, elementLayout, float[]::new, MemorySegment::ofArray); + } + + @Override + public final long[] toArray(ValueLayout.OfLong elementLayout) { + return toArray(long[].class, elementLayout, long[]::new, MemorySegment::ofArray); + } + + @Override + public final double[] toArray(ValueLayout.OfDouble elementLayout) { + return toArray(double[].class, elementLayout, double[]::new, MemorySegment::ofArray); + } + + private Z toArray(Class arrayClass, ValueLayout elemLayout, IntFunction arrayFactory, Function segmentFactory) { + int size = checkArraySize(arrayClass.getSimpleName(), (int)elemLayout.byteSize()); + Z arr = arrayFactory.apply(size); + MemorySegment arrSegment = segmentFactory.apply(arr); + MemorySegment.copy(this, elemLayout, 0, arrSegment, elemLayout.withOrder(ByteOrder.nativeOrder()), 0, size); + return arr; + } + + @ForceInline + public void checkReadOnly(boolean readOnly) { + if (!readOnly && this.readOnly) { + throw new IllegalArgumentException("Attempt to write a read-only segment"); + } + } + + @ForceInline + public void checkAccess(long offset, long length, boolean readOnly) { + checkReadOnly(readOnly); + checkBounds(offset, length); + } + + public void checkValidState() { + sessionImpl().checkValidState(); + } + + public abstract long unsafeGetOffset(); + + public abstract Object unsafeGetBase(); + + // Helper methods + + public abstract long maxAlignMask(); + + @ForceInline + public final boolean isAlignedForElement(long offset, MemoryLayout layout) { + return isAlignedForElement(offset, layout.byteAlignment()); + } + + @ForceInline + public final boolean isAlignedForElement(long offset, long byteAlignment) { + return (((unsafeGetOffset() + offset) | maxAlignMask()) & (byteAlignment - 1)) == 0; + } + + private int checkArraySize(String typeName, int elemSize) { + // elemSize is guaranteed to be a power of two, so we can use an alignment check + if (!Utils.isAligned(length, elemSize)) { + throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, length)); + } + long arraySize = length / elemSize; + if (arraySize > (Integer.MAX_VALUE - 8)) { //conservative check + throw new IllegalStateException(String.format("Segment is too large to wrap as %s. Size: %d", typeName, length)); + } + return (int)arraySize; + } + + @ForceInline + void checkBounds(long offset, long length) { + if (length > 0) { + Preconditions.checkIndex(offset, this.length - length + 1, this); + } else if (length < 0 || offset < 0 || + offset > this.length - length) { + throw outOfBoundException(offset, length); + } + } + + @Override + public RuntimeException apply(String s, List numbers) { + long offset = numbers.get(0).longValue(); + long length = byteSize() - numbers.get(1).longValue() + 1; + return outOfBoundException(offset, length); + } + + @Override + public Scope scope() { + return scope; + } + + @Override + public boolean isAccessibleBy(Thread thread) { + return sessionImpl().isAccessibleBy(thread); + } + + @ForceInline + public final MemorySessionImpl sessionImpl() { + return scope; + } + + private IndexOutOfBoundsException outOfBoundException(long offset, long length) { + return new IndexOutOfBoundsException(String.format("Out of bound access on segment %s; new offset = %d; new length = %d", + this, offset, length)); + } + + static class SegmentSplitter implements Spliterator { + AbstractMemorySegmentImpl segment; + long elemCount; + final long elementSize; + long currentIndex; + + SegmentSplitter(long elementSize, long elemCount, AbstractMemorySegmentImpl segment) { + this.segment = segment; + this.elementSize = elementSize; + this.elemCount = elemCount; + } + + @Override + public SegmentSplitter trySplit() { + if (currentIndex == 0 && elemCount > 1) { + AbstractMemorySegmentImpl parent = segment; + long rem = elemCount % 2; + long split = elemCount / 2; + long lobound = split * elementSize; + long hibound = lobound + (rem * elementSize); + elemCount = split + rem; + segment = parent.asSliceNoCheck(lobound, hibound); + return new SegmentSplitter(elementSize, split, parent.asSliceNoCheck(0, lobound)); + } else { + return null; + } + } + + @Override + public boolean tryAdvance(Consumer action) { + Objects.requireNonNull(action); + if (currentIndex < elemCount) { + AbstractMemorySegmentImpl acquired = segment; + try { + action.accept(acquired.asSliceNoCheck(currentIndex * elementSize, elementSize)); + } finally { + currentIndex++; + if (currentIndex == elemCount) { + segment = null; + } + } + return true; + } else { + return false; + } + } + + @Override + public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); + if (currentIndex < elemCount) { + AbstractMemorySegmentImpl acquired = segment; + try { + for (long i = currentIndex ; i < elemCount ; i++) { + action.accept(acquired.asSliceNoCheck(i * elementSize, elementSize)); + } + } finally { + currentIndex = elemCount; + segment = null; + } + } + } + + @Override + public long estimateSize() { + return elemCount; + } + + @Override + public int characteristics() { + return NONNULL | SUBSIZED | SIZED | IMMUTABLE | ORDERED; + } + } + + // Object methods + + @Override + public String toString() { + return "MemorySegment{ " + + heapBase().map(hb -> "heapBase: " + hb + ", ").orElse("") + + "address: " + Utils.toHexString(address()) + + ", byteSize: " + length + + " }"; + } + + @Override + public boolean equals(Object o) { + return o instanceof AbstractMemorySegmentImpl that && + unsafeGetBase() == that.unsafeGetBase() && + unsafeGetOffset() == that.unsafeGetOffset(); + } + + @Override + public int hashCode() { + return Objects.hash( + unsafeGetOffset(), + unsafeGetBase()); + } + + public static AbstractMemorySegmentImpl ofBuffer(Buffer bb) { + Objects.requireNonNull(bb); + Object base = NIO_ACCESS.getBufferBase(bb); + if (!bb.isDirect() && base == null) { + throw new IllegalArgumentException("The provided heap buffer is not backed by an array."); + } + long bbAddress = NIO_ACCESS.getBufferAddress(bb); + UnmapperProxy unmapper = NIO_ACCESS.unmapper(bb); + + int pos = bb.position(); + int limit = bb.limit(); + int size = limit - pos; + + AbstractMemorySegmentImpl bufferSegment = (AbstractMemorySegmentImpl) NIO_ACCESS.bufferSegment(bb); + boolean readOnly = bb.isReadOnly(); + int scaleFactor = getScaleFactor(bb); + final MemorySessionImpl bufferScope; + if (bufferSegment != null) { + bufferScope = bufferSegment.scope; + } else { + bufferScope = MemorySessionImpl.createHeap(bufferRef(bb)); + } + long off = bbAddress + ((long)pos << scaleFactor); + long len = (long)size << scaleFactor; + if (base != null) { + return switch (base) { + case byte[] _ -> new HeapMemorySegmentImpl.OfByte(off, base, len, readOnly, bufferScope); + case short[] _ -> new HeapMemorySegmentImpl.OfShort(off, base, len, readOnly, bufferScope); + case char[] _ -> new HeapMemorySegmentImpl.OfChar(off, base, len, readOnly, bufferScope); + case int[] _ -> new HeapMemorySegmentImpl.OfInt(off, base, len, readOnly, bufferScope); + case float[] _ -> new HeapMemorySegmentImpl.OfFloat(off, base, len, readOnly, bufferScope); + case long[] _ -> new HeapMemorySegmentImpl.OfLong(off, base, len, readOnly, bufferScope); + case double[] _ -> new HeapMemorySegmentImpl.OfDouble(off, base, len, readOnly, bufferScope); + default -> throw new AssertionError("Cannot get here"); + }; + } else if (unmapper == null) { + return new NativeMemorySegmentImpl(off, len, readOnly, bufferScope); + } else { + return new MappedMemorySegmentImpl(off, unmapper, len, readOnly, bufferScope); + } + } + + private static Object bufferRef(Buffer buffer) { + if (buffer instanceof DirectBuffer directBuffer) { + // direct buffer, return either the buffer attachment (for slices and views), or the buffer itself + return directBuffer.attachment() != null ? + directBuffer.attachment() : directBuffer; + } else { + // heap buffer, return the underlying array + return NIO_ACCESS.getBufferBase(buffer); + } + } + + @ForceInline + public static void copy(MemorySegment srcSegment, ValueLayout srcElementLayout, long srcOffset, + MemorySegment dstSegment, ValueLayout dstElementLayout, long dstOffset, + long elementCount) { + + Utils.checkNonNegativeIndex(elementCount, "elementCount"); + AbstractMemorySegmentImpl srcImpl = (AbstractMemorySegmentImpl)srcSegment; + AbstractMemorySegmentImpl dstImpl = (AbstractMemorySegmentImpl)dstSegment; + if (srcElementLayout.byteSize() != dstElementLayout.byteSize()) { + throw new IllegalArgumentException("Source and destination layouts must have same size"); + } + Utils.checkElementAlignment(srcElementLayout, "Source layout alignment greater than its size"); + Utils.checkElementAlignment(dstElementLayout, "Destination layout alignment greater than its size"); + if (!srcImpl.isAlignedForElement(srcOffset, srcElementLayout)) { + throw new IllegalArgumentException("Source segment incompatible with alignment constraints"); + } + if (!dstImpl.isAlignedForElement(dstOffset, dstElementLayout)) { + throw new IllegalArgumentException("Destination segment incompatible with alignment constraints"); + } + long size = elementCount * srcElementLayout.byteSize(); + srcImpl.checkAccess(srcOffset, size, true); + dstImpl.checkAccess(dstOffset, size, false); + if (srcElementLayout.byteSize() == 1 || srcElementLayout.order() == dstElementLayout.order()) { + ScopedMemoryAccess.getScopedMemoryAccess().copyMemory(srcImpl.sessionImpl(), dstImpl.sessionImpl(), + srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcOffset, + dstImpl.unsafeGetBase(), dstImpl.unsafeGetOffset() + dstOffset, size); + } else { + ScopedMemoryAccess.getScopedMemoryAccess().copySwapMemory(srcImpl.sessionImpl(), dstImpl.sessionImpl(), + srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcOffset, + dstImpl.unsafeGetBase(), dstImpl.unsafeGetOffset() + dstOffset, size, srcElementLayout.byteSize()); + } + } + + @ForceInline + public static void copy(MemorySegment srcSegment, ValueLayout srcLayout, long srcOffset, + Object dstArray, int dstIndex, + int elementCount) { + Utils.checkNonNegativeIndex(elementCount, "elementCount"); + var dstInfo = Utils.BaseAndScale.of(dstArray); + if (dstArray.getClass().componentType() != srcLayout.carrier()) { + throw new IllegalArgumentException("Incompatible value layout: " + srcLayout); + } + AbstractMemorySegmentImpl srcImpl = (AbstractMemorySegmentImpl)srcSegment; + Utils.checkElementAlignment(srcLayout, "Source layout alignment greater than its size"); + if (!srcImpl.isAlignedForElement(srcOffset, srcLayout)) { + throw new IllegalArgumentException("Source segment incompatible with alignment constraints"); + } + srcImpl.checkAccess(srcOffset, elementCount * dstInfo.scale(), true); + Objects.checkFromIndexSize(dstIndex, elementCount, Array.getLength(dstArray)); + if (dstInfo.scale() == 1 || srcLayout.order() == ByteOrder.nativeOrder()) { + ScopedMemoryAccess.getScopedMemoryAccess().copyMemory(srcImpl.sessionImpl(), null, + srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcOffset, + dstArray, dstInfo.base() + (dstIndex * dstInfo.scale()), elementCount * dstInfo.scale()); + } else { + ScopedMemoryAccess.getScopedMemoryAccess().copySwapMemory(srcImpl.sessionImpl(), null, + srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcOffset, + dstArray, dstInfo.base() + (dstIndex * dstInfo.scale()), elementCount * dstInfo.scale(), dstInfo.scale()); + } + } + + @ForceInline + public static void copy(Object srcArray, int srcIndex, + MemorySegment dstSegment, ValueLayout dstLayout, long dstOffset, + int elementCount) { + var srcInfo = Utils.BaseAndScale.of(srcArray); + if (srcArray.getClass().componentType() != dstLayout.carrier()) { + throw new IllegalArgumentException("Incompatible value layout: " + dstLayout); + } + Objects.checkFromIndexSize(srcIndex, elementCount, Array.getLength(srcArray)); + AbstractMemorySegmentImpl destImpl = (AbstractMemorySegmentImpl)dstSegment; + Utils.checkElementAlignment(dstLayout, "Destination layout alignment greater than its size"); + if (!destImpl.isAlignedForElement(dstOffset, dstLayout)) { + throw new IllegalArgumentException("Destination segment incompatible with alignment constraints"); + } + destImpl.checkAccess(dstOffset, elementCount * srcInfo.scale(), false); + if (srcInfo.scale() == 1 || dstLayout.order() == ByteOrder.nativeOrder()) { + ScopedMemoryAccess.getScopedMemoryAccess().copyMemory(null, destImpl.sessionImpl(), + srcArray, srcInfo.base() + (srcIndex * srcInfo.scale()), + destImpl.unsafeGetBase(), destImpl.unsafeGetOffset() + dstOffset, elementCount * srcInfo.scale()); + } else { + ScopedMemoryAccess.getScopedMemoryAccess().copySwapMemory(null, destImpl.sessionImpl(), + srcArray, srcInfo.base() + (srcIndex * srcInfo.scale()), + destImpl.unsafeGetBase(), destImpl.unsafeGetOffset() + dstOffset, elementCount * srcInfo.scale(), srcInfo.scale()); + } + } + + public static long mismatch(MemorySegment srcSegment, long srcFromOffset, long srcToOffset, + MemorySegment dstSegment, long dstFromOffset, long dstToOffset) { + AbstractMemorySegmentImpl srcImpl = (AbstractMemorySegmentImpl)Objects.requireNonNull(srcSegment); + AbstractMemorySegmentImpl dstImpl = (AbstractMemorySegmentImpl)Objects.requireNonNull(dstSegment); + long srcBytes = srcToOffset - srcFromOffset; + long dstBytes = dstToOffset - dstFromOffset; + srcImpl.checkAccess(srcFromOffset, srcBytes, true); + dstImpl.checkAccess(dstFromOffset, dstBytes, true); + + long bytes = Math.min(srcBytes, dstBytes); + long i = 0; + if (bytes > 7) { + if (srcImpl.get(JAVA_BYTE, srcFromOffset) != dstImpl.get(JAVA_BYTE, dstFromOffset)) { + return 0; + } + i = AbstractMemorySegmentImpl.vectorizedMismatchLargeForBytes(srcImpl.sessionImpl(), dstImpl.sessionImpl(), + srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcFromOffset, + dstImpl.unsafeGetBase(), dstImpl.unsafeGetOffset() + dstFromOffset, + bytes); + if (i >= 0) { + return i; + } + long remaining = ~i; + assert remaining < 8 : "remaining greater than 7: " + remaining; + i = bytes - remaining; + } + for (; i < bytes; i++) { + if (srcImpl.get(JAVA_BYTE, srcFromOffset + i) != dstImpl.get(JAVA_BYTE, dstFromOffset + i)) { + return i; + } + } + return srcBytes != dstBytes ? bytes : -1; + } + + private static int getScaleFactor(Buffer buffer) { + return switch (buffer) { + case ByteBuffer _ -> 0; + case CharBuffer _, ShortBuffer _ -> 1; + case IntBuffer _, FloatBuffer _ -> 2; + case LongBuffer _, DoubleBuffer _ -> 3; + }; + } + + // accessors + + @ForceInline + @Override + public byte get(ValueLayout.OfByte layout, long offset) { + return (byte) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfByte layout, long offset, byte value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public boolean get(ValueLayout.OfBoolean layout, long offset) { + return (boolean) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfBoolean layout, long offset, boolean value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public char get(ValueLayout.OfChar layout, long offset) { + return (char) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfChar layout, long offset, char value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public short get(ValueLayout.OfShort layout, long offset) { + return (short) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfShort layout, long offset, short value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public int get(ValueLayout.OfInt layout, long offset) { + return (int) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfInt layout, long offset, int value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public float get(ValueLayout.OfFloat layout, long offset) { + return (float) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfFloat layout, long offset, float value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public long get(ValueLayout.OfLong layout, long offset) { + return (long) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfLong layout, long offset, long value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public double get(ValueLayout.OfDouble layout, long offset) { + return (double) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(ValueLayout.OfDouble layout, long offset, double value) { + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public MemorySegment get(AddressLayout layout, long offset) { + return (MemorySegment) layout.varHandle().get((MemorySegment)this, offset); + } + + @ForceInline + @Override + public void set(AddressLayout layout, long offset, MemorySegment value) { + Objects.requireNonNull(value); + layout.varHandle().set((MemorySegment)this, offset, value); + } + + @ForceInline + @Override + public byte getAtIndex(ValueLayout.OfByte layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (byte) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public boolean getAtIndex(ValueLayout.OfBoolean layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (boolean) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public char getAtIndex(ValueLayout.OfChar layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (char) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfChar layout, long index, char value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public short getAtIndex(ValueLayout.OfShort layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (short) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfByte layout, long index, byte value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfBoolean layout, long index, boolean value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfShort layout, long index, short value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public int getAtIndex(ValueLayout.OfInt layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (int) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfInt layout, long index, int value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public float getAtIndex(ValueLayout.OfFloat layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (float) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfFloat layout, long index, float value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public long getAtIndex(ValueLayout.OfLong layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (long) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfLong layout, long index, long value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public double getAtIndex(ValueLayout.OfDouble layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (double) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(ValueLayout.OfDouble layout, long index, double value) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @ForceInline + @Override + public MemorySegment getAtIndex(AddressLayout layout, long index) { + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + return (MemorySegment) layout.varHandle().get((MemorySegment)this, index * layout.byteSize()); + } + + @ForceInline + @Override + public void setAtIndex(AddressLayout layout, long index, MemorySegment value) { + Objects.requireNonNull(value); + Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); + layout.varHandle().set((MemorySegment)this, index * layout.byteSize(), value); + } + + @Override + public String getString(long offset) { + return getString(offset, sun.nio.cs.UTF_8.INSTANCE); + } + + @Override + public String getString(long offset, Charset charset) { + Objects.requireNonNull(charset); + return StringSupport.read(this, offset, charset); + } + + @Override + public void setString(long offset, String str) { + Objects.requireNonNull(str); + setString(offset, str, sun.nio.cs.UTF_8.INSTANCE); + } + + @Override + public void setString(long offset, String str, Charset charset) { + Objects.requireNonNull(charset); + Objects.requireNonNull(str); + StringSupport.write(this, offset, charset, str); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/ArenaImpl.class b/tests/test_data/std/jdk/internal/foreign/ArenaImpl.class new file mode 100644 index 00000000..3f308696 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/ArenaImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/ArenaImpl.java b/tests/test_data/std/jdk/internal/foreign/ArenaImpl.java new file mode 100644 index 00000000..027aed5f --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/ArenaImpl.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySegment.Scope; +import java.util.Objects; + +public final class ArenaImpl implements Arena { + + private final MemorySessionImpl session; + private final boolean shouldReserveMemory; + ArenaImpl(MemorySessionImpl session) { + this.session = session; + shouldReserveMemory = session instanceof ImplicitSession; + } + + @Override + public Scope scope() { + return session; + } + + @Override + public void close() { + session.close(); + } + + public MemorySegment allocateNoInit(long byteSize, long byteAlignment) { + Utils.checkAllocationSizeAndAlign(byteSize, byteAlignment); + return SegmentFactories.allocateSegment(byteSize, byteAlignment, session, shouldReserveMemory); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + MemorySegment segment = allocateNoInit(byteSize, byteAlignment); + return segment.fill((byte)0); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/CABI.class b/tests/test_data/std/jdk/internal/foreign/CABI.class new file mode 100644 index 00000000..9ebca45c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/CABI.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/CABI.java b/tests/test_data/std/jdk/internal/foreign/CABI.java new file mode 100644 index 00000000..500e312b --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/CABI.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign; + +import jdk.internal.foreign.abi.fallback.FallbackLinker; +import jdk.internal.vm.ForeignLinkerSupport; +import jdk.internal.util.OperatingSystem; +import jdk.internal.util.StaticProperty; + +import static java.lang.foreign.ValueLayout.ADDRESS; +import static sun.security.action.GetPropertyAction.privilegedGetProperty; + +public enum CABI { + SYS_V, + WIN_64, + LINUX_AARCH_64, + MAC_OS_AARCH_64, + WIN_AARCH_64, + AIX_PPC_64, + LINUX_PPC_64, + LINUX_PPC_64_LE, + LINUX_RISCV_64, + LINUX_S390, + FALLBACK, + UNSUPPORTED; + + private static final CABI CURRENT = computeCurrent(); + + private static CABI computeCurrent() { + String abi = privilegedGetProperty("jdk.internal.foreign.CABI"); + if (abi != null) { + return CABI.valueOf(abi); + } + + if (ForeignLinkerSupport.isSupported()) { + // figure out the ABI based on the platform + String arch = StaticProperty.osArch(); + long addressSize = ADDRESS.byteSize(); + // might be running in a 32-bit VM on a 64-bit platform. + // addressSize will be correctly 32 + if ((arch.equals("amd64") || arch.equals("x86_64")) && addressSize == 8) { + if (OperatingSystem.isWindows()) { + return WIN_64; + } else { + return SYS_V; + } + } else if (arch.equals("aarch64")) { + if (OperatingSystem.isMacOS()) { + return MAC_OS_AARCH_64; + } else if (OperatingSystem.isWindows()) { + return WIN_AARCH_64; + } else { + // The Linux ABI follows the standard AAPCS ABI + return LINUX_AARCH_64; + } + } else if (arch.equals("ppc64")) { + if (OperatingSystem.isLinux()) { + return LINUX_PPC_64; + } else if (OperatingSystem.isAix()) { + return AIX_PPC_64; + } + } else if (arch.equals("ppc64le")) { + if (OperatingSystem.isLinux()) { + return LINUX_PPC_64_LE; + } + } else if (arch.equals("riscv64")) { + if (OperatingSystem.isLinux()) { + return LINUX_RISCV_64; + } + } else if (arch.equals("s390x")) { + if (OperatingSystem.isLinux()) { + return LINUX_S390; + } + } + } else if (FallbackLinker.isSupported()) { + return FALLBACK; // fallback linker + } + + return UNSUPPORTED; + } + + public static CABI current() { + return CURRENT; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/ConfinedSession$ConfinedResourceList.class b/tests/test_data/std/jdk/internal/foreign/ConfinedSession$ConfinedResourceList.class new file mode 100644 index 00000000..403996a6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/ConfinedSession$ConfinedResourceList.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/ConfinedSession.class b/tests/test_data/std/jdk/internal/foreign/ConfinedSession.class new file mode 100644 index 00000000..19ac80fc Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/ConfinedSession.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/ConfinedSession.java b/tests/test_data/std/jdk/internal/foreign/ConfinedSession.java new file mode 100644 index 00000000..4ff0df78 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/ConfinedSession.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; + +import jdk.internal.vm.annotation.ForceInline; + +/** + * A confined session, which features an owner thread. The liveness check features an additional + * confinement check - that is, calling any operation on this session from a thread other than the + * owner thread will result in an exception. Because of this restriction, checking the liveness bit + * can be performed in plain mode. + */ +final class ConfinedSession extends MemorySessionImpl { + + private int asyncReleaseCount = 0; + + static final VarHandle ASYNC_RELEASE_COUNT; + + static { + try { + ASYNC_RELEASE_COUNT = MethodHandles.lookup().findVarHandle(ConfinedSession.class, "asyncReleaseCount", int.class); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public ConfinedSession(Thread owner) { + super(owner, new ConfinedResourceList()); + } + + @Override + @ForceInline + public void acquire0() { + checkValidState(); + if (state == MAX_FORKS) { + throw tooManyAcquires(); + } + state++; + } + + @Override + @ForceInline + public void release0() { + if (Thread.currentThread() == owner) { + state--; + } else { + // It is possible to end up here in two cases: this session was kept alive by some other confined session + // which is implicitly released (in which case the release call comes from the cleaner thread). Or, + // this session might be kept alive by a shared session, which means the release call can come from any + // thread. + ASYNC_RELEASE_COUNT.getAndAdd(this, 1); + } + } + + void justClose() { + checkValidState(); + int asyncCount = (int)ASYNC_RELEASE_COUNT.getVolatile(this); + if ((state == 0 && asyncCount == 0) + || ((state - asyncCount) == 0)) { + state = CLOSED; + } else { + throw alreadyAcquired(state - asyncCount); + } + } + + /** + * A confined resource list; no races are possible here. + */ + static final class ConfinedResourceList extends ResourceList { + @Override + void add(ResourceCleanup cleanup) { + if (fst != ResourceCleanup.CLOSED_LIST) { + cleanup.next = fst; + fst = cleanup; + } else { + throw alreadyClosed(); + } + } + + @Override + void cleanup() { + if (fst != ResourceCleanup.CLOSED_LIST) { + ResourceCleanup prev = fst; + fst = ResourceCleanup.CLOSED_LIST; + cleanup(prev); + } else { + throw alreadyClosed(); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/FunctionDescriptorImpl.class b/tests/test_data/std/jdk/internal/foreign/FunctionDescriptorImpl.class new file mode 100644 index 00000000..7adb1c37 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/FunctionDescriptorImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/FunctionDescriptorImpl.java b/tests/test_data/std/jdk/internal/foreign/FunctionDescriptorImpl.java new file mode 100644 index 00000000..da172728 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/FunctionDescriptorImpl.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +/** + * @implSpec This class and its subclasses are immutable, thread-safe and value-based. + */ +public final class FunctionDescriptorImpl implements FunctionDescriptor { + + private final MemoryLayout resLayout; // Nullable + private final List argLayouts; + + private FunctionDescriptorImpl(MemoryLayout resLayout, List argLayouts) { + if (resLayout instanceof PaddingLayout) { + throw new IllegalArgumentException("Unsupported padding layout return in function descriptor: " + resLayout); + } + Optional paddingLayout = argLayouts.stream().filter(l -> l instanceof PaddingLayout).findAny(); + if (paddingLayout.isPresent()) { + throw new IllegalArgumentException("Unsupported padding layout argument in function descriptor: " + paddingLayout.get()); + } + this.resLayout = resLayout; + this.argLayouts = List.copyOf(argLayouts); + } + + /** + * {@return the return layout (if any) associated with this function descriptor} + */ + public Optional returnLayout() { + return Optional.ofNullable(resLayout); + } + + /** + * {@return the argument layouts associated with this function descriptor (as an immutable list)}. + */ + public List argumentLayouts() { + return argLayouts; + } + + /** + * Returns a function descriptor with the given argument layouts appended to the argument layout array + * of this function descriptor. + * + * @param addedLayouts the argument layouts to append. + * @return the new function descriptor. + */ + public FunctionDescriptorImpl appendArgumentLayouts(MemoryLayout... addedLayouts) { + return insertArgumentLayouts(argLayouts.size(), addedLayouts); + } + + /** + * Returns a function descriptor with the given argument layouts inserted at the given index, into the argument + * layout array of this function descriptor. + * + * @param index the index at which to insert the arguments + * @param addedLayouts the argument layouts to insert at given index. + * @return the new function descriptor. + * @throws IllegalArgumentException if {@code index < 0 || index > argumentLayouts().size()}. + */ + public FunctionDescriptorImpl insertArgumentLayouts(int index, MemoryLayout... addedLayouts) { + if (index < 0 || index > argLayouts.size()) + throw new IllegalArgumentException("Index out of bounds: " + index); + List added = List.of(addedLayouts); // null check on array and its elements + List newLayouts = new ArrayList<>(argLayouts.size() + addedLayouts.length); + newLayouts.addAll(argLayouts.subList(0, index)); + newLayouts.addAll(added); + newLayouts.addAll(argLayouts.subList(index, argLayouts.size())); + return new FunctionDescriptorImpl(resLayout, newLayouts); + } + + /** + * Returns a function descriptor with the given memory layout as the new return layout. + * + * @param newReturn the new return layout. + * @return the new function descriptor. + */ + public FunctionDescriptorImpl changeReturnLayout(MemoryLayout newReturn) { + requireNonNull(newReturn); + return new FunctionDescriptorImpl(newReturn, argLayouts); + } + + /** + * Returns a function descriptor with the return layout dropped. This is useful to model functions + * which return no values. + * + * @return the new function descriptor. + */ + public FunctionDescriptorImpl dropReturnLayout() { + return new FunctionDescriptorImpl(null, argLayouts); + } + + private static Class carrierTypeFor(MemoryLayout layout) { + if (layout instanceof ValueLayout valueLayout) { + return valueLayout.carrier(); + } else if (layout instanceof GroupLayout || layout instanceof SequenceLayout) { + return MemorySegment.class; + } else { + // Note: we should not worry about padding layouts, as they cannot be present in a function descriptor + throw new AssertionError("Cannot get here"); + } + } + + @Override + public MethodType toMethodType() { + Class returnValue = resLayout != null ? carrierTypeFor(resLayout) : void.class; + Class[] argCarriers = new Class[argLayouts.size()]; + for (int i = 0; i < argCarriers.length; i++) { + argCarriers[i] = carrierTypeFor(argLayouts.get(i)); + } + return MethodType.methodType(returnValue, argCarriers); + } + + /** + * {@return the string representation of this function descriptor} + */ + @Override + public String toString() { + return String.format("(%s)%s", + argLayouts.stream().map(Object::toString) + .collect(Collectors.joining()), + returnLayout() + .map(Object::toString) + .orElse("v")); + } + + /** + * Compares the specified object with this function descriptor for equality. Returns {@code true} if and only if the specified + * object is also a function descriptor, and all the following conditions are met: + *

    + *
  • the two function descriptors have equals return layouts (see {@link MemoryLayout#equals(Object)}), or both have no return layout;
  • + *
  • the two function descriptors have argument layouts that are pair-wise {@linkplain MemoryLayout#equals(Object) equal}; and
  • + *
+ * + * @param other the object to be compared for equality with this function descriptor. + * @return {@code true} if the specified object is equal to this function descriptor. + */ + @Override + public boolean equals(Object other) { + return other instanceof FunctionDescriptorImpl f && + Objects.equals(resLayout, f.resLayout) && + Objects.equals(argLayouts, f.argLayouts); + } + + /** + * {@return the hash code value for this function descriptor} + */ + @Override + public int hashCode() { + return Objects.hash(argLayouts, resLayout); + } + + public static FunctionDescriptor of(MemoryLayout resLayout, List argLayouts) { + return new FunctionDescriptorImpl(resLayout, argLayouts); + } + + public static FunctionDescriptor ofVoid(List argLayouts) { + return new FunctionDescriptorImpl(null, argLayouts); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/GlobalSession$HeapSession.class b/tests/test_data/std/jdk/internal/foreign/GlobalSession$HeapSession.class new file mode 100644 index 00000000..2f79f1fb Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/GlobalSession$HeapSession.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/GlobalSession.class b/tests/test_data/std/jdk/internal/foreign/GlobalSession.class new file mode 100644 index 00000000..3fe5dc42 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/GlobalSession.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/GlobalSession.java b/tests/test_data/std/jdk/internal/foreign/GlobalSession.java new file mode 100644 index 00000000..c075778b --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/GlobalSession.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import jdk.internal.access.JavaNioAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.vm.annotation.ForceInline; +import sun.nio.ch.DirectBuffer; + +import java.nio.Buffer; +import java.util.Objects; + +/** + * The global, non-closeable, shared session. Similar to a shared session, but its {@link #close()} method throws unconditionally. + * Adding new resources to the global session, does nothing: as the session can never become not-alive, there is nothing to track. + * Acquiring and or releasing a memory session similarly does nothing. + */ +non-sealed class GlobalSession extends MemorySessionImpl { + + public GlobalSession() { + super(null, null); + } + + @Override + @ForceInline + public void release0() { + // do nothing + } + + @Override + public boolean isCloseable() { + return false; + } + + @Override + @ForceInline + public void acquire0() { + // do nothing + } + + @Override + void addInternal(ResourceList.ResourceCleanup resource) { + // do nothing + } + + @Override + public void justClose() { + throw nonCloseable(); + } + + /** + * This is a global session that wraps a heap object. Possible objects are: Java arrays, buffers and + * class loaders. Objects of two heap sessions are compared by identity. That is, if the wrapped object is the same, + * then the resulting heap sessions are also considered equals. We do not compare the objects using + * {@link Object#equals(Object)}, as that would be problematic when comparing buffers, whose equality and + * hash codes are content-dependent. + */ + static class HeapSession extends GlobalSession { + + final Object ref; + + public HeapSession(Object ref) { + super(); + this.ref = Objects.requireNonNull(ref); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof HeapSession session && + ref == session.ref; + } + + @Override + public int hashCode() { + return System.identityHashCode(ref); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfByte.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfByte.class new file mode 100644 index 00000000..c8102a43 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfByte.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfChar.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfChar.class new file mode 100644 index 00000000..dba4dc09 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfChar.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfDouble.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfDouble.class new file mode 100644 index 00000000..658ade3f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfDouble.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfFloat.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfFloat.class new file mode 100644 index 00000000..89d69269 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfFloat.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfInt.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfInt.class new file mode 100644 index 00000000..5d903beb Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfInt.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfLong.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfLong.class new file mode 100644 index 00000000..dce75630 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfLong.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfShort.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfShort.class new file mode 100644 index 00000000..aa17610e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl$OfShort.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl.class b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl.class new file mode 100644 index 00000000..caa3ba94 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl.java b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl.java new file mode 100644 index 00000000..2512b9e5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/HeapMemorySegmentImpl.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign; + +import java.lang.foreign.ValueLayout; +import java.nio.ByteBuffer; +import java.util.Objects; +import java.util.Optional; + +import jdk.internal.access.JavaNioAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.vm.annotation.ForceInline; + +/** + * Implementation for heap memory segments. A heap memory segment is composed by an offset and + * a base object (typically an array). To enhance performances, the access to the base object needs to feature + * sharp type information, as well as sharp null-check information. For this reason, many concrete subclasses + * of {@link HeapMemorySegmentImpl} are defined (e.g. {@link OfFloat}), so that each subclass can override the + * {@link HeapMemorySegmentImpl#unsafeGetBase()} method so that it returns an array of the correct (sharp) type. Note that + * the field type storing the 'base' coordinate is just Object; similarly, all the constructor in the subclasses + * accept an Object 'base' parameter instead of a sharper type (e.g. {@code byte[]}). This is deliberate, as + * using sharper types would require use of type-conversions, which in turn would inhibit some C2 optimizations, + * such as the elimination of store barriers in methods like {@link HeapMemorySegmentImpl#dup(long, long, boolean, MemorySessionImpl)}. + */ +abstract sealed class HeapMemorySegmentImpl extends AbstractMemorySegmentImpl { + + // Constants defining the maximum alignment supported by various kinds of heap arrays. + + private static final long MAX_ALIGN_BYTE_ARRAY = ValueLayout.JAVA_BYTE.byteAlignment(); + private static final long MAX_ALIGN_SHORT_ARRAY = ValueLayout.JAVA_SHORT.byteAlignment(); + private static final long MAX_ALIGN_INT_ARRAY = ValueLayout.JAVA_INT.byteAlignment(); + private static final long MAX_ALIGN_LONG_ARRAY = ValueLayout.JAVA_LONG.byteAlignment(); + + final long offset; + final Object base; + + @Override + public Optional heapBase() { + return readOnly ? + Optional.empty() : + Optional.of(base); + } + + @ForceInline + HeapMemorySegmentImpl(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(length, readOnly, session); + this.offset = offset; + this.base = base; + } + + @Override + public long unsafeGetOffset() { + return offset; + } + + @Override + public final long maxByteAlignment() { + return address() == 0 + ? maxAlignMask() + : Math.min(maxAlignMask(), Long.lowestOneBit(address())); + } + + @Override + abstract HeapMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope); + + @Override + ByteBuffer makeByteBuffer() { + if (!(base instanceof byte[] baseByte)) { + throw new UnsupportedOperationException("Not an address to an heap-allocated byte array"); + } + JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess(); + return nioAccess.newHeapByteBuffer(baseByte, (int)offset - Utils.BaseAndScale.BYTE.base(), (int) byteSize(), null); + } + + // factories + + public static final class OfByte extends HeapMemorySegmentImpl { + + OfByte(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfByte dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfByte(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public byte[] unsafeGetBase() { + return (byte[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_BYTE_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.BYTE.base(); + } + } + + public static final class OfChar extends HeapMemorySegmentImpl { + + OfChar(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfChar dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfChar(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public char[] unsafeGetBase() { + return (char[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_SHORT_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.CHAR.base(); + } + } + + public static final class OfShort extends HeapMemorySegmentImpl { + + OfShort(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfShort dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfShort(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public short[] unsafeGetBase() { + return (short[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_SHORT_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.SHORT.base(); + } + } + + public static final class OfInt extends HeapMemorySegmentImpl { + + OfInt(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfInt dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfInt(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public int[] unsafeGetBase() { + return (int[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_INT_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.INT.base(); + } + } + + public static final class OfLong extends HeapMemorySegmentImpl { + + OfLong(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfLong dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfLong(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public long[] unsafeGetBase() { + return (long[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_LONG_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.LONG.base(); + } + } + + public static final class OfFloat extends HeapMemorySegmentImpl { + + OfFloat(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfFloat dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfFloat(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public float[] unsafeGetBase() { + return (float[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_INT_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.FLOAT.base(); + } + } + + public static final class OfDouble extends HeapMemorySegmentImpl { + + OfDouble(long offset, Object base, long length, boolean readOnly, MemorySessionImpl session) { + super(offset, base, length, readOnly, session); + } + + @Override + OfDouble dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new OfDouble(this.offset + offset, base, size, readOnly, scope); + } + + @Override + public double[] unsafeGetBase() { + return (double[])Objects.requireNonNull(base); + } + + @Override + public long maxAlignMask() { + return MAX_ALIGN_LONG_ARRAY; + } + + @Override + public long address() { + return offset - Utils.BaseAndScale.DOUBLE.base(); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/ImplicitSession.class b/tests/test_data/std/jdk/internal/foreign/ImplicitSession.class new file mode 100644 index 00000000..fc103498 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/ImplicitSession.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/ImplicitSession.java b/tests/test_data/std/jdk/internal/foreign/ImplicitSession.java new file mode 100644 index 00000000..0eb36425 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/ImplicitSession.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import sun.nio.ch.DirectBuffer; + +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; + +/** + * This is an implicit, GC-backed memory session. Implicit sessions cannot be closed explicitly. + * While it would be possible to model an implicit session as a non-closeable view of a shared + * session, it is better to capture the fact that an implicit session is not just a non-closeable + * view of some session which might be closeable. This is useful e.g. in the implementations of + * {@link DirectBuffer#address()}, where obtaining an address of a buffer instance associated + * with a potentially closeable session is forbidden. + */ +final class ImplicitSession extends SharedSession { + + public ImplicitSession(Cleaner cleaner) { + super(); + cleaner.register(this, resourceList); + } + + @Override + public void release0() { + Reference.reachabilityFence(this); + } + + @Override + public void acquire0() { + // do nothing + } + + @Override + public boolean isCloseable() { + return false; + } + + @Override + public void justClose() { + throw nonCloseable(); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath$DereferenceElement.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath$DereferenceElement.class new file mode 100644 index 00000000..c2a28928 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath$DereferenceElement.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath$GroupElementByIndex.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath$GroupElementByIndex.class new file mode 100644 index 00000000..1478dfde Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath$GroupElementByIndex.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath$GroupElementByName.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath$GroupElementByName.class new file mode 100644 index 00000000..b7ba74df Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath$GroupElementByName.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElement.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElement.class new file mode 100644 index 00000000..2c8dfd14 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElement.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElementByIndex.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElementByIndex.class new file mode 100644 index 00000000..02e238fa Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElementByIndex.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElementByRange.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElementByRange.class new file mode 100644 index 00000000..e3e14b33 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath$SequenceElementByRange.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath.class b/tests/test_data/std/jdk/internal/foreign/LayoutPath.class new file mode 100644 index 00000000..29eae412 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/LayoutPath.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/LayoutPath.java b/tests/test_data/std/jdk/internal/foreign/LayoutPath.java new file mode 100644 index 00000000..ebd83d1c --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/LayoutPath.java @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign; + +import jdk.internal.vm.annotation.ForceInline; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.joining; + +/** + * This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout)}), + * a path can be constructed by selecting layout elements using the selector methods provided by this class + * (see {@link #sequenceElement()}, {@link #sequenceElement(long)}, {@link #sequenceElement(long, long)}, {@link #groupElement(String)}). + * Once a path has been fully constructed, clients can ask for the offset associated with the layout element selected + * by the path (see {@link #offset}), or obtain var handle to access the selected layout element + * given an address pointing to a segment associated with the root layout (see {@link #dereferenceHandle()}). + */ +public class LayoutPath { + + private static final long[] EMPTY_STRIDES = new long[0]; + private static final long[] EMPTY_BOUNDS = new long[0]; + private static final MethodHandle[] EMPTY_DEREF_HANDLES = new MethodHandle[0]; + + private static final MethodHandle MH_ADD_SCALED_OFFSET; + private static final MethodHandle MH_SLICE; + private static final MethodHandle MH_SLICE_LAYOUT; + private static final MethodHandle MH_CHECK_ENCL_LAYOUT; + private static final MethodHandle MH_SEGMENT_RESIZE; + private static final MethodHandle MH_ADD; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset", + MethodType.methodType(long.class, long.class, long.class, long.class, long.class)); + MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice", + MethodType.methodType(MemorySegment.class, long.class, long.class)); + MH_SLICE_LAYOUT = lookup.findVirtual(MemorySegment.class, "asSlice", + MethodType.methodType(MemorySegment.class, long.class, MemoryLayout.class)); + MH_CHECK_ENCL_LAYOUT = lookup.findStatic(LayoutPath.class, "checkEnclosingLayout", + MethodType.methodType(void.class, MemorySegment.class, long.class, MemoryLayout.class)); + MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment", + MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class)); + MH_ADD = lookup.findStatic(Long.class, "sum", + MethodType.methodType(long.class, long.class, long.class)); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + private final MemoryLayout layout; + private final long offset; + private final LayoutPath enclosing; + private final long[] strides; + private final long[] bounds; + private final MethodHandle[] derefAdapters; + + private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath enclosing) { + this.layout = layout; + this.offset = offset; + this.strides = strides; + this.bounds = bounds; + this.derefAdapters = derefAdapters; + this.enclosing = enclosing; + } + + // Layout path selector methods + + public LayoutPath sequenceElement() { + SequenceLayout seq = requireSequenceLayout(); + MemoryLayout elem = seq.elementLayout(); + return LayoutPath.nestedPath(elem, offset, addStride(elem.byteSize()), addBound(seq.elementCount()), derefAdapters, this); + } + + public LayoutPath sequenceElement(long start, long step) { + SequenceLayout seq = requireSequenceLayout(); + checkSequenceBounds(seq, start); + MemoryLayout elem = seq.elementLayout(); + long elemSize = elem.byteSize(); + long nelems = step > 0 ? + seq.elementCount() - start : + start + 1; + long maxIndex = Math.ceilDiv(nelems, Math.abs(step)); + return LayoutPath.nestedPath(elem, offset + (start * elemSize), + addStride(elemSize * step), addBound(maxIndex), derefAdapters, this); + } + + public LayoutPath sequenceElement(long index) { + SequenceLayout seq = requireSequenceLayout(); + checkSequenceBounds(seq, index); + long elemSize = seq.elementLayout().byteSize(); + long elemOffset = elemSize * index; + return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters, this); + } + + public LayoutPath groupElement(String name) { + GroupLayout g = requireGroupLayout(); + long offset = 0; + MemoryLayout elem = null; + for (int i = 0; i < g.memberLayouts().size(); i++) { + MemoryLayout l = g.memberLayouts().get(i); + if (l.name().isPresent() && + l.name().get().equals(name)) { + elem = l; + break; + } else if (g instanceof StructLayout) { + offset += l.byteSize(); + } + } + if (elem == null) { + throw badLayoutPath( + String.format("cannot resolve '%s' in layout %s", name, breadcrumbs())); + } + return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this); + } + + public LayoutPath groupElement(long index) { + GroupLayout g = requireGroupLayout(); + long elemSize = g.memberLayouts().size(); + long offset = 0; + MemoryLayout elem = null; + for (int i = 0; i <= index; i++) { + if (i == elemSize) { + throw badLayoutPath( + String.format("cannot resolve element %d in layout: %s", index, breadcrumbs())); + } + elem = g.memberLayouts().get(i); + if (g instanceof StructLayout && i < index) { + offset += elem.byteSize(); + } + } + return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this); + } + + public LayoutPath derefElement() { + if (!(layout instanceof AddressLayout addressLayout) || + addressLayout.targetLayout().isEmpty()) { + throw badLayoutPath( + String.format("Cannot dereference layout: %s", breadcrumbs())); + } + MemoryLayout derefLayout = addressLayout.targetLayout().get(); + MethodHandle handle = dereferenceHandle(false).toMethodHandle(VarHandle.AccessMode.GET); + handle = MethodHandles.filterReturnValue(handle, + MethodHandles.insertArguments(MH_SEGMENT_RESIZE, 1, derefLayout)); + return derefPath(derefLayout, handle, this); + } + + private static MemorySegment resizeSegment(MemorySegment segment, MemoryLayout layout) { + return Utils.longToAddress(segment.address(), layout.byteSize(), layout.byteAlignment()); + } + + // Layout path projections + + public long offset() { + return offset; + } + + public VarHandle dereferenceHandle() { + return dereferenceHandle(true); + } + + public VarHandle dereferenceHandle(boolean adapt) { + if (!(layout instanceof ValueLayout valueLayout)) { + throw new IllegalArgumentException( + String.format("Path does not select a value layout: %s", breadcrumbs())); + } + + VarHandle handle = Utils.makeRawSegmentViewVarHandle(valueLayout); + handle = MethodHandles.collectCoordinates(handle, 1, offsetHandle()); + + // we only have to check the alignment of the root layout for the first dereference we do, + // as each dereference checks the alignment of the target address when constructing its segment + // (see Utils::longToAddress) + if (derefAdapters.length == 0) { + // insert align check for the root layout on the initial MS + offset + List> coordinateTypes = handle.coordinateTypes(); + MethodHandle alignCheck = MethodHandles.insertArguments(MH_CHECK_ENCL_LAYOUT, 2, rootLayout()); + handle = MethodHandles.collectCoordinates(handle, 0, alignCheck); + int[] reorder = IntStream.concat(IntStream.of(0, 1), IntStream.range(0, coordinateTypes.size())).toArray(); + handle = MethodHandles.permuteCoordinates(handle, coordinateTypes, reorder); + } + + if (adapt) { + if (derefAdapters.length > 0) { + // plug up the base offset if we have at least 1 enclosing dereference + handle = MethodHandles.insertCoordinates(handle, 1, 0); + } + for (int i = derefAdapters.length; i > 0; i--) { + MethodHandle adapter = derefAdapters[i - 1]; + // the first/outermost adapter will have a base offset coordinate, the rest are constant 0 + if (i > 1) { + // plug in a constant 0 base offset for all but the outermost access in a deref chain + adapter = MethodHandles.insertArguments(adapter, 1, 0); + } + handle = MethodHandles.collectCoordinates(handle, 0, adapter); + } + } + return handle; + } + + @ForceInline + private static long addScaledOffset(long base, long index, long stride, long bound) { + Objects.checkIndex(index, bound); + return base + (stride * index); + } + + public MethodHandle offsetHandle() { + MethodHandle mh = MethodHandles.insertArguments(MH_ADD, 0, offset); + for (int i = strides.length - 1; i >= 0; i--) { + MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i], bounds[i]); + // (J, ...) -> J to (J, J, ...) -> J + // i.e. new coord is prefixed. Last coord will correspond to innermost layout + mh = MethodHandles.collectArguments(mh, 0, collector); + } + + return mh; + } + + private MemoryLayout rootLayout() { + return enclosing != null ? enclosing.rootLayout() : this.layout; + } + + public MethodHandle sliceHandle() { + MethodHandle sliceHandle; + if (enclosing != null) { + // drop the alignment check for the accessed element, we check the root layout instead + sliceHandle = MH_SLICE; // (MS, long, long) -> MS + sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout.byteSize()); // (MS, long) -> MS + } else { + sliceHandle = MH_SLICE_LAYOUT; // (MS, long, MemoryLayout) -> MS + sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout); // (MS, long) -> MS + } + sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle()); // (MS, long, ...) -> MS + + if (enclosing != null) { + // insert align check for the root layout on the initial MS + offset + MethodType oldType = sliceHandle.type(); + MethodHandle alignCheck = MethodHandles.insertArguments(MH_CHECK_ENCL_LAYOUT, 2, rootLayout()); + sliceHandle = MethodHandles.collectArguments(sliceHandle, 0, alignCheck); // (MS, long, MS, long) -> MS + int[] reorder = IntStream.concat(IntStream.of(0, 1), IntStream.range(0, oldType.parameterCount())).toArray(); + sliceHandle = MethodHandles.permuteArguments(sliceHandle, oldType, reorder); // (MS, long, ...) -> MS + } + + return sliceHandle; + } + + private static void checkEnclosingLayout(MemorySegment segment, long offset, MemoryLayout enclosing) { + ((AbstractMemorySegmentImpl)segment).checkAccess(offset, enclosing.byteSize(), true); + if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(offset, enclosing)) { + throw new IllegalArgumentException(String.format( + "Target offset %d is incompatible with alignment constraint %d (of %s) for segment %s" + , offset, enclosing.byteAlignment(), enclosing, segment)); + } + } + + public MemoryLayout layout() { + return layout; + } + + // Layout path construction + + public static LayoutPath rootPath(MemoryLayout layout) { + return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, EMPTY_DEREF_HANDLES, null); + } + + private static LayoutPath nestedPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath encl) { + return new LayoutPath(layout, offset, strides, bounds, derefAdapters, encl); + } + + private static LayoutPath derefPath(MemoryLayout layout, MethodHandle handle, LayoutPath encl) { + MethodHandle[] handles = Arrays.copyOf(encl.derefAdapters, encl.derefAdapters.length + 1); + handles[encl.derefAdapters.length] = handle; + return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, handles, null); + } + + // Helper methods + + private SequenceLayout requireSequenceLayout() { + return requireLayoutType(SequenceLayout.class, "sequence"); + } + + private GroupLayout requireGroupLayout() { + return requireLayoutType(GroupLayout.class, "group"); + } + + private T requireLayoutType(Class layoutClass, String name) { + if (!layoutClass.isAssignableFrom(layout.getClass())) { + throw badLayoutPath( + String.format("attempting to select a %s element from a non-%s layout: %s", + name, name, breadcrumbs())); + } + return layoutClass.cast(layout); + } + + private void checkSequenceBounds(SequenceLayout seq, long index) { + if (index >= seq.elementCount()) { + throw badLayoutPath(String.format("sequence index out of bounds; index: %d, elementCount is %d for layout %s", + index, seq.elementCount(), breadcrumbs())); + } + } + + private static IllegalArgumentException badLayoutPath(String cause) { + return new IllegalArgumentException("Bad layout path: " + cause); + } + + private long[] addStride(long stride) { + long[] newStrides = Arrays.copyOf(strides, strides.length + 1); + newStrides[strides.length] = stride; + return newStrides; + } + + private long[] addBound(long maxIndex) { + long[] newBounds = Arrays.copyOf(bounds, bounds.length + 1); + newBounds[bounds.length] = maxIndex; + return newBounds; + } + + private String breadcrumbs() { + return Stream.iterate(this, Objects::nonNull, lp -> lp.enclosing) + .map(LayoutPath::layout) + .map(Object::toString) + .collect(joining(", selected from: ")); + } + + public record GroupElementByName(String name) + implements MemoryLayout.PathElement, UnaryOperator { + + // Assert invariants + public GroupElementByName { + Objects.requireNonNull(name); + } + + @Override + public LayoutPath apply(LayoutPath layoutPath) { + return layoutPath.groupElement(name); + } + + @Override + public String toString() { + return "groupElement(\"" + name + "\")"; + } + } + + public record GroupElementByIndex(long index) + implements MemoryLayout.PathElement, UnaryOperator { + + // Assert invariants + public GroupElementByIndex { + if (index < 0) { + throw new IllegalArgumentException("Index < 0"); + } + } + + @Override + public LayoutPath apply(LayoutPath layoutPath) { + return layoutPath.groupElement(index); + } + + @Override + public String toString() { + return "groupElement(" + index + ")"; + } + + } + + public record SequenceElementByIndex(long index) + implements MemoryLayout.PathElement, UnaryOperator { + + // Assert invariants + public SequenceElementByIndex { + if (index < 0) { + throw new IllegalArgumentException("Index < 0"); + } + } + + @Override + public LayoutPath apply(LayoutPath layoutPath) { + return layoutPath.sequenceElement(index); + } + + @Override + public String toString() { + return "sequenceElement(" + index + ")"; + } + + } + + public record SequenceElementByRange(long start, long step) + implements MemoryLayout.PathElement, UnaryOperator { + + // Assert invariants + public SequenceElementByRange { + if (start < 0) { + throw new IllegalArgumentException("Start index must be positive: " + start); + } + if (step == 0) { + throw new IllegalArgumentException("Step must be != 0: " + step); + } + } + + @Override + public LayoutPath apply(LayoutPath layoutPath) { + return layoutPath.sequenceElement(start, step); + } + + @Override + public String toString() { + return "sequenceElement(" + start + ", " + step + ")"; + } + + } + + public record SequenceElement() + implements MemoryLayout.PathElement, UnaryOperator { + + private static final SequenceElement INSTANCE = new SequenceElement(); + + @Override + public LayoutPath apply(LayoutPath layoutPath) { + return layoutPath.sequenceElement(); + } + + @Override + public String toString() { + return "sequenceElement()"; + } + + public static MemoryLayout.PathElement instance() { + return INSTANCE; + } + + } + + public record DereferenceElement() + implements MemoryLayout.PathElement, UnaryOperator { + + private static final DereferenceElement INSTANCE = new DereferenceElement(); + + @Override + public LayoutPath apply(LayoutPath layoutPath) { + return layoutPath.derefElement(); + } + + // Overriding here will ensure DereferenceElement will have a hash code + // that is different from the hash code of SequenceElement. + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "dereferenceElement()"; + } + + public static MemoryLayout.PathElement instance() { + return INSTANCE; + } + + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/MappedMemorySegmentImpl.class b/tests/test_data/std/jdk/internal/foreign/MappedMemorySegmentImpl.class new file mode 100644 index 00000000..e5c9dbd5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/MappedMemorySegmentImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/MappedMemorySegmentImpl.java b/tests/test_data/std/jdk/internal/foreign/MappedMemorySegmentImpl.java new file mode 100644 index 00000000..e313780a --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/MappedMemorySegmentImpl.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.nio.ByteBuffer; +import jdk.internal.access.foreign.UnmapperProxy; +import jdk.internal.misc.ScopedMemoryAccess; + +/** + * Implementation for a mapped memory segments. A mapped memory segment is a native memory segment, which + * additionally features an {@link UnmapperProxy} object. This object provides detailed information about the + * memory mapped segment, such as the file descriptor associated with the mapping. This information is crucial + * in order to correctly reconstruct a byte buffer object from the segment (see {@link #makeByteBuffer()}). + */ +final class MappedMemorySegmentImpl extends NativeMemorySegmentImpl { + + private final UnmapperProxy unmapper; + + static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess(); + + public MappedMemorySegmentImpl(long min, UnmapperProxy unmapper, long length, boolean readOnly, MemorySessionImpl scope) { + super(min, length, readOnly, scope); + this.unmapper = unmapper; + } + + @Override + ByteBuffer makeByteBuffer() { + return NIO_ACCESS.newMappedByteBuffer(unmapper, min, (int)length, null, this); + } + + @Override + MappedMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new MappedMemorySegmentImpl(min + offset, unmapper, size, readOnly, scope); + } + + // mapped segment methods + + @Override + public MappedMemorySegmentImpl asSlice(long offset, long newSize) { + return (MappedMemorySegmentImpl)super.asSlice(offset, newSize); + } + + @Override + public boolean isMapped() { + return true; + } + + // support for mapped segments + + public void load() { + if (unmapper != null) { + SCOPED_MEMORY_ACCESS.load(sessionImpl(), min, unmapper.isSync(), length); + } + } + + public void unload() { + if (unmapper != null) { + SCOPED_MEMORY_ACCESS.unload(sessionImpl(), min, unmapper.isSync(), length); + } + } + + public boolean isLoaded() { + return unmapper == null || SCOPED_MEMORY_ACCESS.isLoaded(sessionImpl(), min, unmapper.isSync(), length); + } + + public void force() { + if (unmapper != null) { + SCOPED_MEMORY_ACCESS.force(sessionImpl(), unmapper.fileDescriptor(), min, unmapper.isSync(), 0, length); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup$1.class b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup$1.class new file mode 100644 index 00000000..490675ca Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup$2.class b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup$2.class new file mode 100644 index 00000000..704b0e9a Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup$2.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup.class b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup.class new file mode 100644 index 00000000..3fc2f908 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList$ResourceCleanup.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList.class b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList.class new file mode 100644 index 00000000..f2e8ed87 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl$ResourceList.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl.class b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl.class new file mode 100644 index 00000000..e6b03bdd Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl.java b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl.java new file mode 100644 index 00000000..aef62be8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/MemorySessionImpl.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment.Scope; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.ref.Cleaner; +import java.util.Objects; + +import jdk.internal.foreign.GlobalSession.HeapSession; +import jdk.internal.misc.ScopedMemoryAccess; +import jdk.internal.vm.annotation.ForceInline; + +/** + * This class manages the temporal bounds associated with a memory segment as well + * as thread confinement. A session has a liveness bit, which is updated when the session is closed + * (this operation is triggered by {@link MemorySessionImpl#close()}). This bit is consulted prior + * to memory access (see {@link #checkValidStateRaw()}). + * There are two kinds of memory session: confined memory session and shared memory session. + * A confined memory session has an associated owner thread that confines some operations to + * associated owner thread such as {@link #close()} or {@link #checkValidStateRaw()}. + * Shared sessions do not feature an owner thread - meaning their operations can be called, in a racy + * manner, by multiple threads. To guarantee temporal safety in the presence of concurrent thread, + * shared sessions use a more sophisticated synchronization mechanism, which guarantees that no concurrent + * access is possible when a session is being closed (see {@link jdk.internal.misc.ScopedMemoryAccess}). + */ +public abstract sealed class MemorySessionImpl + implements Scope + permits ConfinedSession, GlobalSession, SharedSession { + static final int OPEN = 0; + static final int CLOSED = -1; + + static final VarHandle STATE; + static final int MAX_FORKS = Integer.MAX_VALUE; + + static final ScopedMemoryAccess.ScopedAccessError ALREADY_CLOSED = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::alreadyClosed); + static final ScopedMemoryAccess.ScopedAccessError WRONG_THREAD = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::wrongThread); + // This is the session of all zero-length memory segments + public static final MemorySessionImpl GLOBAL_SESSION = new GlobalSession(); + + final ResourceList resourceList; + final Thread owner; + int state = OPEN; + + static { + try { + STATE = MethodHandles.lookup().findVarHandle(MemorySessionImpl.class, "state", int.class); + } catch (Exception ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public Arena asArena() { + return new ArenaImpl(this); + } + + @ForceInline + public static MemorySessionImpl toMemorySession(Arena arena) { + return (MemorySessionImpl) arena.scope(); + } + + public final boolean isCloseableBy(Thread thread) { + Objects.requireNonNull(thread); + return isCloseable() && + (owner == null || owner == thread); + } + + public void addCloseAction(Runnable runnable) { + Objects.requireNonNull(runnable); + addInternal(ResourceList.ResourceCleanup.ofRunnable(runnable)); + } + + /** + * Add a cleanup action. If a failure occurred (because of an add vs. close race), call the cleanup action. + * This semantics is useful when allocating new memory segments, since we first do a malloc/mmap and _then_ + * we register the cleanup (free/munmap) against the session; so, if registration fails, we still have to + * clean up memory. From the perspective of the client, such a failure would manifest as a factory + * returning a segment that is already "closed" - which is always possible anyway (e.g. if the session + * is closed _after_ the cleanup for the segment is registered but _before_ the factory returns the + * new segment to the client). For this reason, it's not worth adding extra complexity to the segment + * initialization logic here - and using an optimistic logic works well in practice. + */ + public void addOrCleanupIfFail(ResourceList.ResourceCleanup resource) { + try { + addInternal(resource); + } catch (Throwable ex) { + resource.cleanup(); + throw ex; + } + } + + void addInternal(ResourceList.ResourceCleanup resource) { + checkValidState(); + // Note: from here on we no longer check the session state. Two cases are possible: either the resource cleanup + // is added to the list when the session is still open, in which case everything works ok; or the resource + // cleanup is added while the session is being closed. In this latter case, what matters is whether we have already + // called `ResourceList::cleanup` to run all the cleanup actions. If not, we can still add this resource + // to the list (and, in case of an add vs. close race, it might happen that the cleanup action will be + // called immediately after). + resourceList.add(resource); + } + + protected MemorySessionImpl(Thread owner, ResourceList resourceList) { + this.owner = owner; + this.resourceList = resourceList; + } + + public static MemorySessionImpl createConfined(Thread thread) { + return new ConfinedSession(thread); + } + + public static MemorySessionImpl createShared() { + return new SharedSession(); + } + + public static MemorySessionImpl createImplicit(Cleaner cleaner) { + return new ImplicitSession(cleaner); + } + + public static MemorySessionImpl createHeap(Object ref) { + return new HeapSession(ref); + } + + public abstract void release0(); + + public abstract void acquire0(); + + public void whileAlive(Runnable action) { + Objects.requireNonNull(action); + acquire0(); + try { + action.run(); + } finally { + release0(); + } + } + + public final Thread ownerThread() { + return owner; + } + + public final boolean isAccessibleBy(Thread thread) { + Objects.requireNonNull(thread); + return owner == null || owner == thread; + } + + /** + * Returns true, if this session is still open. This method may be called in any thread. + * @return {@code true} if this session is not closed yet. + */ + public boolean isAlive() { + return state >= OPEN; + } + + /** + * This is a faster version of {@link #checkValidState()}, which is called upon memory access, and which + * relies on invariants associated with the memory session implementations (volatile access + * to the closed state bit is replaced with plain access). This method should be monomorphic, + * to avoid virtual calls in the memory access hot path. This method is not intended as general purpose method + * and should only be used in the memory access handle hot path; for liveness checks triggered by other API methods, + * please use {@link #checkValidState()}. + */ + @ForceInline + public void checkValidStateRaw() { + if (owner != null && owner != Thread.currentThread()) { + throw WRONG_THREAD; + } + if (state < OPEN) { + throw ALREADY_CLOSED; + } + } + + /** + * Checks that this session is still alive (see {@link #isAlive()}). + * @throws IllegalStateException if this session is already closed or if this is + * a confined session and this method is called outside the owner thread. + */ + public void checkValidState() { + try { + checkValidStateRaw(); + } catch (ScopedMemoryAccess.ScopedAccessError error) { + throw error.newRuntimeException(); + } + } + + public static void checkValidState(MemorySegment segment) { + ((AbstractMemorySegmentImpl)segment).sessionImpl().checkValidState(); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + public boolean isCloseable() { + return true; + } + + /** + * Closes this session, executing any cleanup action (where provided). + * @throws IllegalStateException if this session is already closed or if this is + * a confined session and this method is called outside the owner thread. + */ + public void close() { + justClose(); + resourceList.cleanup(); + } + + abstract void justClose(); + + /** + * A list of all cleanup actions associated with a memory session. Cleanup actions are modelled as instances + * of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a session + * is shared or confined, different implementations of this class will be used, see {@link ConfinedSession.ConfinedResourceList} + * and {@link SharedSession.SharedResourceList}. + */ + public abstract static class ResourceList implements Runnable { + ResourceCleanup fst; + + abstract void add(ResourceCleanup cleanup); + + abstract void cleanup(); + + public final void run() { + cleanup(); // cleaner interop + } + + static void cleanup(ResourceCleanup first) { + RuntimeException pendingException = null; + ResourceCleanup current = first; + while (current != null) { + try { + current.cleanup(); + } catch (RuntimeException ex) { + if (pendingException == null) { + pendingException = ex; + } else if (ex != pendingException) { + // note: self-suppression is not supported + pendingException.addSuppressed(ex); + } + } + current = current.next; + } + if (pendingException != null) { + throw pendingException; + } + } + + public abstract static class ResourceCleanup { + ResourceCleanup next; + + public abstract void cleanup(); + + static final ResourceCleanup CLOSED_LIST = new ResourceCleanup() { + @Override + public void cleanup() { + throw new IllegalStateException("This resource list has already been closed!"); + } + }; + + static ResourceCleanup ofRunnable(Runnable cleanupAction) { + return new ResourceCleanup() { + @Override + public void cleanup() { + cleanupAction.run(); + } + }; + } + } + } + + // helper functions to centralize error handling + + static IllegalStateException tooManyAcquires() { + return new IllegalStateException("Session acquire limit exceeded"); + } + + static IllegalStateException alreadyAcquired(int acquires) { + return new IllegalStateException(String.format("Session is acquired by %d clients", acquires)); + } + + static IllegalStateException alreadyClosed() { + return new IllegalStateException("Already closed"); + } + + static WrongThreadException wrongThread() { + return new WrongThreadException("Attempted access outside owning thread"); + } + + static UnsupportedOperationException nonCloseable() { + return new UnsupportedOperationException("Attempted to close a non-closeable session"); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/NativeMemorySegmentImpl.class b/tests/test_data/std/jdk/internal/foreign/NativeMemorySegmentImpl.class new file mode 100644 index 00000000..648c3a3c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/NativeMemorySegmentImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/NativeMemorySegmentImpl.java b/tests/test_data/std/jdk/internal/foreign/NativeMemorySegmentImpl.java new file mode 100644 index 00000000..c2043cf4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/NativeMemorySegmentImpl.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.ForceInline; + +/** + * Implementation for native memory segments. A native memory segment is essentially a wrapper around + * a native long address. + */ +sealed class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl permits MappedMemorySegmentImpl { + + final long min; + + @ForceInline + NativeMemorySegmentImpl(long min, long length, boolean readOnly, MemorySessionImpl scope) { + super(length, readOnly, scope); + this.min = (Unsafe.getUnsafe().addressSize() == 4) + // On 32-bit systems, normalize the upper unused 32-bits to zero + ? min & 0x0000_0000_FFFF_FFFFL + // On 64-bit systems, all the bits are used + : min; + } + + @Override + public long address() { + return min; + } + + @Override + public Optional heapBase() { + return Optional.empty(); + } + + public final long maxByteAlignment() { + return address() == 0 + ? 1L << 62 + : Long.lowestOneBit(address()); + } + + @ForceInline + @Override + NativeMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { + return new NativeMemorySegmentImpl(min + offset, size, readOnly, scope); + } + + @Override + ByteBuffer makeByteBuffer() { + return NIO_ACCESS.newDirectByteBuffer(min, (int) this.length, null, this); + } + + @Override + public boolean isNative() { + return true; + } + + @Override + public long unsafeGetOffset() { + return min; + } + + @Override + public Object unsafeGetBase() { + return null; + } + + @Override + public long maxAlignMask() { + return 0; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/SegmentFactories$1.class b/tests/test_data/std/jdk/internal/foreign/SegmentFactories$1.class new file mode 100644 index 00000000..3a7de219 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SegmentFactories$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SegmentFactories$2.class b/tests/test_data/std/jdk/internal/foreign/SegmentFactories$2.class new file mode 100644 index 00000000..bb5c6956 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SegmentFactories$2.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SegmentFactories.class b/tests/test_data/std/jdk/internal/foreign/SegmentFactories.class new file mode 100644 index 00000000..bfc8fc78 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SegmentFactories.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SegmentFactories.java b/tests/test_data/std/jdk/internal/foreign/SegmentFactories.java new file mode 100644 index 00000000..133631e2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/SegmentFactories.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import jdk.internal.access.foreign.UnmapperProxy; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfByte; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfChar; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfDouble; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfFloat; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfInt; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfLong; +import jdk.internal.foreign.HeapMemorySegmentImpl.OfShort; +import jdk.internal.misc.Unsafe; +import jdk.internal.misc.VM; +import jdk.internal.vm.annotation.ForceInline; + +import java.lang.foreign.MemorySegment; +import java.util.Objects; + +/** + * This class is used to retrieve concrete memory segment implementations, while making sure that classes + * are initialized in the right order (that is, that {@code MemorySegment} is always initialized first). + * See {@link SegmentFactories#ensureInitialized()}. + */ +public class SegmentFactories { + + // The maximum alignment supported by malloc - typically 16 bytes on + // 64-bit platforms and 8 bytes on 32-bit platforms. + private static final long MAX_MALLOC_ALIGN = Unsafe.ADDRESS_SIZE == 4 ? 8 : 16; + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + // Unsafe native segment factories. These are used by the implementation code, to skip the sanity checks + // associated with MemorySegment::ofAddress. + + @ForceInline + public static MemorySegment makeNativeSegmentUnchecked(long min, + long byteSize, + MemorySessionImpl sessionImpl, + boolean readOnly, + Runnable action) { + ensureInitialized(); + if (action == null) { + sessionImpl.checkValidState(); + } else { + sessionImpl.addCloseAction(action); + } + return new NativeMemorySegmentImpl(min, byteSize, readOnly, sessionImpl); + } + + @ForceInline + public static MemorySegment makeNativeSegmentUnchecked(long min, long byteSize, MemorySessionImpl sessionImpl) { + ensureInitialized(); + sessionImpl.checkValidState(); + return new NativeMemorySegmentImpl(min, byteSize, false, sessionImpl); + } + + @ForceInline + public static MemorySegment makeNativeSegmentUnchecked(long min, long byteSize) { + ensureInitialized(); + return new NativeMemorySegmentImpl(min, byteSize, false, MemorySessionImpl.GLOBAL_SESSION); + } + + public static MemorySegment fromArray(byte[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.BYTE.scale(); + return new OfByte(Utils.BaseAndScale.BYTE.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment fromArray(short[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.SHORT.scale(); + return new OfShort(Utils.BaseAndScale.SHORT.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment fromArray(int[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.INT.scale(); + return new OfInt(Utils.BaseAndScale.INT.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment fromArray(char[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.CHAR.scale(); + return new OfChar(Utils.BaseAndScale.CHAR.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment fromArray(float[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.FLOAT.scale(); + return new OfFloat(Utils.BaseAndScale.FLOAT.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment fromArray(double[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.DOUBLE.scale(); + return new OfDouble(Utils.BaseAndScale.DOUBLE.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment fromArray(long[] arr) { + ensureInitialized(); + Objects.requireNonNull(arr); + long byteSize = (long)arr.length * Utils.BaseAndScale.LONG.scale(); + return new OfLong(Utils.BaseAndScale.LONG.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); + } + + public static MemorySegment allocateSegment(long byteSize, long byteAlignment, MemorySessionImpl sessionImpl, + boolean shouldReserve) { + ensureInitialized(); + sessionImpl.checkValidState(); + if (VM.isDirectMemoryPageAligned()) { + byteAlignment = Math.max(byteAlignment, AbstractMemorySegmentImpl.NIO_ACCESS.pageSize()); + } + long alignedSize = Math.max(1L, byteAlignment > MAX_MALLOC_ALIGN ? + byteSize + (byteAlignment - 1) : + byteSize); + + if (shouldReserve) { + AbstractMemorySegmentImpl.NIO_ACCESS.reserveMemory(alignedSize, byteSize); + } + + long buf = allocateMemoryWrapper(alignedSize); + long alignedBuf = Utils.alignUp(buf, byteAlignment); + AbstractMemorySegmentImpl segment = new NativeMemorySegmentImpl(buf, alignedSize, + false, sessionImpl); + sessionImpl.addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() { + @Override + public void cleanup() { + UNSAFE.freeMemory(buf); + if (shouldReserve) { + AbstractMemorySegmentImpl.NIO_ACCESS.unreserveMemory(alignedSize, byteSize); + } + } + }); + if (alignedSize != byteSize) { + long delta = alignedBuf - buf; + segment = segment.asSlice(delta, byteSize); + } + return segment; + } + + private static long allocateMemoryWrapper(long size) { + try { + return UNSAFE.allocateMemory(size); + } catch (IllegalArgumentException ex) { + throw new OutOfMemoryError(); + } + } + + public static MemorySegment mapSegment(long size, UnmapperProxy unmapper, boolean readOnly, MemorySessionImpl sessionImpl) { + ensureInitialized(); + if (unmapper != null) { + AbstractMemorySegmentImpl segment = + new MappedMemorySegmentImpl(unmapper.address(), unmapper, size, + readOnly, sessionImpl); + MemorySessionImpl.ResourceList.ResourceCleanup resource = + new MemorySessionImpl.ResourceList.ResourceCleanup() { + @Override + public void cleanup() { + unmapper.unmap(); + } + }; + sessionImpl.addOrCleanupIfFail(resource); + return segment; + } else { + return new MappedMemorySegmentImpl(0, null, 0, readOnly, sessionImpl); + } + } + + // The method below needs to be called before any concrete subclass of MemorySegment + // is instantiated. This is to make sure that we cannot have an initialization deadlock + // where one thread attempts to initialize e.g. MemorySegment (and then NativeMemorySegmentImpl, via + // the MemorySegment.NULL field) while another thread is attempting to initialize + // NativeMemorySegmentImpl (and then MemorySegment, the super-interface). + @ForceInline + private static void ensureInitialized() { + MemorySegment segment = MemorySegment.NULL; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/SharedSession$SharedResourceList.class b/tests/test_data/std/jdk/internal/foreign/SharedSession$SharedResourceList.class new file mode 100644 index 00000000..de3f33da Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SharedSession$SharedResourceList.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SharedSession.class b/tests/test_data/std/jdk/internal/foreign/SharedSession.class new file mode 100644 index 00000000..b4417302 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SharedSession.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SharedSession.java b/tests/test_data/std/jdk/internal/foreign/SharedSession.java new file mode 100644 index 00000000..1569589e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/SharedSession.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import jdk.internal.misc.ScopedMemoryAccess; +import jdk.internal.vm.annotation.ForceInline; + +/** + * A shared session, which can be shared across multiple threads. Closing a shared session has to ensure that + * (i) only one thread can successfully close a session (e.g. in a close vs. close race) and that + * (ii) no other thread is accessing the memory associated with this session while the segment is being + * closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter + * is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}). + * Since it is the responsibility of the closing thread to make sure that no concurrent access is possible, + * checking the liveness bit upon access can be performed in plain mode, as in the confined case. + */ +sealed class SharedSession extends MemorySessionImpl permits ImplicitSession { + + private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess(); + + SharedSession() { + super(null, new SharedResourceList()); + } + + @Override + @ForceInline + public void acquire0() { + int value; + do { + value = (int) STATE.getVolatile(this); + if (value < OPEN) { + //segment is not open! + throw alreadyClosed(); + } else if (value == MAX_FORKS) { + //overflow + throw tooManyAcquires(); + } + } while (!STATE.compareAndSet(this, value, value + 1)); + } + + @Override + @ForceInline + public void release0() { + int value; + do { + value = (int) STATE.getVolatile(this); + if (value <= OPEN) { + //cannot get here - we can't close segment twice + throw alreadyClosed(); + } + } while (!STATE.compareAndSet(this, value, value - 1)); + } + + void justClose() { + int prevState = (int) STATE.compareAndExchange(this, OPEN, CLOSED); + if (prevState < 0) { + throw alreadyClosed(); + } else if (prevState != OPEN) { + throw alreadyAcquired(prevState); + } + SCOPED_MEMORY_ACCESS.closeScope(this, ALREADY_CLOSED); + } + + /** + * A shared resource list; this implementation has to handle add vs. add races, as well as add vs. cleanup races. + */ + static class SharedResourceList extends ResourceList { + + static final VarHandle FST; + + static { + try { + FST = MethodHandles.lookup().findVarHandle(ResourceList.class, "fst", ResourceCleanup.class); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(); + } + } + + @Override + void add(ResourceCleanup cleanup) { + while (true) { + ResourceCleanup prev = (ResourceCleanup) FST.getVolatile(this); + if (prev == ResourceCleanup.CLOSED_LIST) { + // too late + throw alreadyClosed(); + } + cleanup.next = prev; + if (FST.compareAndSet(this, prev, cleanup)) { + return; //victory + } + // keep trying + } + } + + void cleanup() { + // At this point we are only interested about add vs. close races - not close vs. close + // (because MemorySessionImpl::justClose ensured that this thread won the race to close the session). + // So, the only "bad" thing that could happen is that some other thread adds to this list + // while we're closing it. + if (FST.getAcquire(this) != ResourceCleanup.CLOSED_LIST) { + //ok now we're really closing down + ResourceCleanup prev = null; + while (true) { + prev = (ResourceCleanup) FST.getVolatile(this); + // no need to check for DUMMY, since only one thread can get here! + if (FST.compareAndSet(this, prev, ResourceCleanup.CLOSED_LIST)) { + break; + } + } + cleanup(prev); + } else { + throw alreadyClosed(); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/SlicingAllocator.class b/tests/test_data/std/jdk/internal/foreign/SlicingAllocator.class new file mode 100644 index 00000000..5dcedbfd Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SlicingAllocator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SlicingAllocator.java b/tests/test_data/std/jdk/internal/foreign/SlicingAllocator.java new file mode 100644 index 00000000..db7d4760 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/SlicingAllocator.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; + +public final class SlicingAllocator implements SegmentAllocator { + + private final MemorySegment segment; + + private long sp = 0L; + + public SlicingAllocator(MemorySegment segment) { + this.segment = segment; + } + + MemorySegment trySlice(long byteSize, long byteAlignment) { + long min = segment.address(); + long start = Utils.alignUp(min + sp, byteAlignment) - min; + MemorySegment slice = segment.asSlice(start, byteSize, byteAlignment); + sp = start + byteSize; + return slice; + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + Utils.checkAllocationSizeAndAlign(byteSize, byteAlignment); + // try to slice from current segment first... + return trySlice(byteSize, byteAlignment); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/StringSupport$CharsetKind.class b/tests/test_data/std/jdk/internal/foreign/StringSupport$CharsetKind.class new file mode 100644 index 00000000..6dcf5a7f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/StringSupport$CharsetKind.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/StringSupport.class b/tests/test_data/std/jdk/internal/foreign/StringSupport.class new file mode 100644 index 00000000..09aba84f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/StringSupport.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/StringSupport.java b/tests/test_data/std/jdk/internal/foreign/StringSupport.java new file mode 100644 index 00000000..b74d09be --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/StringSupport.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.util.ArraysSupport; +import sun.security.action.GetPropertyAction; + +import java.lang.foreign.MemorySegment; +import java.nio.charset.Charset; + +import static java.lang.foreign.ValueLayout.*; + +/** + * Miscellaneous functions to read and write strings, in various charsets. + */ +public final class StringSupport { + + static final JavaLangAccess JAVA_LANG_ACCESS = SharedSecrets.getJavaLangAccess(); + + private StringSupport() {} + + public static String read(MemorySegment segment, long offset, Charset charset) { + return switch (CharsetKind.of(charset)) { + case SINGLE_BYTE -> readByte(segment, offset, charset); + case DOUBLE_BYTE -> readShort(segment, offset, charset); + case QUAD_BYTE -> readInt(segment, offset, charset); + }; + } + + public static void write(MemorySegment segment, long offset, Charset charset, String string) { + switch (CharsetKind.of(charset)) { + case SINGLE_BYTE -> writeByte(segment, offset, charset, string); + case DOUBLE_BYTE -> writeShort(segment, offset, charset, string); + case QUAD_BYTE -> writeInt(segment, offset, charset, string); + } + } + + private static String readByte(MemorySegment segment, long offset, Charset charset) { + long len = chunkedStrlenByte(segment, offset); + byte[] bytes = new byte[(int)len]; + MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int)len); + return new String(bytes, charset); + } + + private static void writeByte(MemorySegment segment, long offset, Charset charset, String string) { + int bytes = copyBytes(string, segment, charset, offset); + segment.set(JAVA_BYTE, offset + bytes, (byte)0); + } + + private static String readShort(MemorySegment segment, long offset, Charset charset) { + long len = chunkedStrlenShort(segment, offset); + byte[] bytes = new byte[(int)len]; + MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int)len); + return new String(bytes, charset); + } + + private static void writeShort(MemorySegment segment, long offset, Charset charset, String string) { + int bytes = copyBytes(string, segment, charset, offset); + segment.set(JAVA_SHORT_UNALIGNED, offset + bytes, (short)0); + } + + private static String readInt(MemorySegment segment, long offset, Charset charset) { + long len = strlenInt(segment, offset); + byte[] bytes = new byte[(int)len]; + MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int)len); + return new String(bytes, charset); + } + + private static void writeInt(MemorySegment segment, long offset, Charset charset, String string) { + int bytes = copyBytes(string, segment, charset, offset); + segment.set(JAVA_INT_UNALIGNED, offset + bytes, 0); + } + + /** + * {@return the shortest distance beginning at the provided {@code start} + * to the encountering of a zero byte in the provided {@code segment}} + *

+ * The method divides the region of interest into three distinct regions: + *

    + *
  • head (access made on a byte-by-byte basis) (if any)
  • + *
  • body (access made with eight bytes at a time at physically 64-bit-aligned memory) (if any)
  • + *
  • tail (access made on a byte-by-byte basis) (if any)
  • + *
+ *

+ * The body is using a heuristic method to determine if a long word + * contains a zero byte. The method might have false positives but + * never false negatives. + *

+ * This method is inspired by the `glibc/string/strlen.c` implementation + * + * @param segment to examine + * @param start from where examination shall begin + * @throws IllegalArgumentException if the examined region contains no zero bytes + * within a length that can be accepted by a String + */ + public static int chunkedStrlenByte(MemorySegment segment, long start) { + + // Handle the first unaligned "head" bytes separately + int headCount = (int)SharedUtils.remainsToAlignment(segment.address() + start, Long.BYTES); + + int offset = 0; + for (; offset < headCount; offset++) { + byte curr = segment.get(JAVA_BYTE, start + offset); + if (curr == 0) { + return offset; + } + } + + // We are now on a long-aligned boundary so this is the "body" + int bodyCount = bodyCount(segment.byteSize() - start - headCount); + + for (; offset < bodyCount; offset += Long.BYTES) { + // We know we are `long` aligned so, we can save on alignment checking here + long curr = segment.get(JAVA_LONG_UNALIGNED, start + offset); + // Is this a candidate? + if (mightContainZeroByte(curr)) { + for (int j = 0; j < 8; j++) { + if (segment.get(JAVA_BYTE, start + offset + j) == 0) { + return offset + j; + } + } + } + } + + // Handle the "tail" + return requireWithinArraySize((long) offset + strlenByte(segment, start + offset)); + } + + /* Bits 63 and N * 8 (N = 1..7) of this number are zero. Call these bits + the "holes". Note that there is a hole just to the left of + each byte, with an extra at the end: + + bits: 01111110 11111110 11111110 11111110 11111110 11111110 11111110 11111111 + bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG HHHHHHHH + + The 1-bits make sure that carries propagate to the next 0-bit. + The 0-bits provide holes for carries to fall into. + */ + private static final long HIMAGIC_FOR_BYTES = 0x8080_8080_8080_8080L; + private static final long LOMAGIC_FOR_BYTES = 0x0101_0101_0101_0101L; + + static boolean mightContainZeroByte(long l) { + return ((l - LOMAGIC_FOR_BYTES) & (~l) & HIMAGIC_FOR_BYTES) != 0; + } + + private static final long HIMAGIC_FOR_SHORTS = 0x8000_8000_8000_8000L; + private static final long LOMAGIC_FOR_SHORTS = 0x0001_0001_0001_0001L; + + static boolean mightContainZeroShort(long l) { + return ((l - LOMAGIC_FOR_SHORTS) & (~l) & HIMAGIC_FOR_SHORTS) != 0; + } + + static int requireWithinArraySize(long size) { + if (size > ArraysSupport.SOFT_MAX_ARRAY_LENGTH) { + throw newIaeStringTooLarge(); + } + return (int) size; + } + + static int bodyCount(long remaining) { + return (int) Math.min( + // Make sure we do not wrap around + Integer.MAX_VALUE - Long.BYTES, + // Remaining bytes to consider + remaining) + & -Long.BYTES; // Mask 0xFFFFFFF8 + } + + private static int strlenByte(MemorySegment segment, long start) { + for (int offset = 0; offset < ArraysSupport.SOFT_MAX_ARRAY_LENGTH; offset += 1) { + byte curr = segment.get(JAVA_BYTE, start + offset); + if (curr == 0) { + return offset; + } + } + throw newIaeStringTooLarge(); + } + + /** + * {@return the shortest distance beginning at the provided {@code start} + * to the encountering of a zero short in the provided {@code segment}} + *

+ * Note: The inspected region must be short aligned. + * + * @see #chunkedStrlenByte(MemorySegment, long) for more information + * + * @param segment to examine + * @param start from where examination shall begin + * @throws IllegalArgumentException if the examined region contains no zero shorts + * within a length that can be accepted by a String + */ + public static int chunkedStrlenShort(MemorySegment segment, long start) { + + // Handle the first unaligned "head" bytes separately + int headCount = (int)SharedUtils.remainsToAlignment(segment.address() + start, Long.BYTES); + + int offset = 0; + for (; offset < headCount; offset += Short.BYTES) { + short curr = segment.get(JAVA_SHORT_UNALIGNED, start + offset); + if (curr == 0) { + return offset; + } + } + + // We are now on a long-aligned boundary so this is the "body" + int bodyCount = bodyCount(segment.byteSize() - start - headCount); + + for (; offset < bodyCount; offset += Long.BYTES) { + // We know we are `long` aligned so, we can save on alignment checking here + long curr = segment.get(JAVA_LONG_UNALIGNED, start + offset); + // Is this a candidate? + if (mightContainZeroShort(curr)) { + for (int j = 0; j < Long.BYTES; j += Short.BYTES) { + if (segment.get(JAVA_SHORT_UNALIGNED, start + offset + j) == 0) { + return offset + j; + } + } + } + } + + // Handle the "tail" + return requireWithinArraySize((long) offset + strlenShort(segment, start + offset)); + } + + private static int strlenShort(MemorySegment segment, long start) { + for (int offset = 0; offset < ArraysSupport.SOFT_MAX_ARRAY_LENGTH; offset += Short.BYTES) { + short curr = segment.get(JAVA_SHORT_UNALIGNED, start + offset); + if (curr == (short)0) { + return offset; + } + } + throw newIaeStringTooLarge(); + } + + // The gain of using `long` wide operations for `int` is lower than for the two other `byte` and `short` variants + // so, there is only one method for ints. + public static int strlenInt(MemorySegment segment, long start) { + for (int offset = 0; offset < ArraysSupport.SOFT_MAX_ARRAY_LENGTH; offset += Integer.BYTES) { + // We are guaranteed to be aligned here so, we can use unaligned access. + int curr = segment.get(JAVA_INT_UNALIGNED, start + offset); + if (curr == 0) { + return offset; + } + } + throw newIaeStringTooLarge(); + } + + public enum CharsetKind { + SINGLE_BYTE(1), + DOUBLE_BYTE(2), + QUAD_BYTE(4); + + final int terminatorCharSize; + + CharsetKind(int terminatorCharSize) { + this.terminatorCharSize = terminatorCharSize; + } + + public int terminatorCharSize() { + return terminatorCharSize; + } + + public static CharsetKind of(Charset charset) { + // Comparing the charset to specific internal implementations avoids loading the class `StandardCharsets` + if (charset == sun.nio.cs.UTF_8.INSTANCE || + charset == sun.nio.cs.ISO_8859_1.INSTANCE || + charset == sun.nio.cs.US_ASCII.INSTANCE) { + return SINGLE_BYTE; + } else if (charset instanceof sun.nio.cs.UTF_16LE || + charset instanceof sun.nio.cs.UTF_16BE || + charset instanceof sun.nio.cs.UTF_16) { + return DOUBLE_BYTE; + } else if (charset instanceof sun.nio.cs.UTF_32LE || + charset instanceof sun.nio.cs.UTF_32BE || + charset instanceof sun.nio.cs.UTF_32) { + return QUAD_BYTE; + } else { + throw new IllegalArgumentException("Unsupported charset: " + charset); + } + } + } + + public static boolean bytesCompatible(String string, Charset charset) { + return JAVA_LANG_ACCESS.bytesCompatible(string, charset); + } + + public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset) { + if (bytesCompatible(string, charset)) { + copyToSegmentRaw(string, segment, offset); + return string.length(); + } else { + byte[] bytes = string.getBytes(charset); + MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length); + return bytes.length; + } + } + + public static void copyToSegmentRaw(String string, MemorySegment segment, long offset) { + JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset); + } + + private static IllegalArgumentException newIaeStringTooLarge() { + return new IllegalArgumentException("String too large"); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/SystemLookup$1.class b/tests/test_data/std/jdk/internal/foreign/SystemLookup$1.class new file mode 100644 index 00000000..e97b6884 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SystemLookup$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SystemLookup$2.class b/tests/test_data/std/jdk/internal/foreign/SystemLookup$2.class new file mode 100644 index 00000000..64272f90 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SystemLookup$2.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SystemLookup$WindowsFallbackSymbols.class b/tests/test_data/std/jdk/internal/foreign/SystemLookup$WindowsFallbackSymbols.class new file mode 100644 index 00000000..b22f2725 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SystemLookup$WindowsFallbackSymbols.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SystemLookup.class b/tests/test_data/std/jdk/internal/foreign/SystemLookup.class new file mode 100644 index 00000000..4a88a283 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/SystemLookup.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/SystemLookup.java b/tests/test_data/std/jdk/internal/foreign/SystemLookup.java new file mode 100644 index 00000000..98e02140 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/SystemLookup.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import jdk.internal.loader.NativeLibrary; +import jdk.internal.loader.RawNativeLibraries; +import sun.security.action.GetPropertyAction; + +import static java.lang.foreign.ValueLayout.ADDRESS; + +public final class SystemLookup implements SymbolLookup { + + private SystemLookup() { } + + private static final SystemLookup INSTANCE = new SystemLookup(); + + /* A fallback lookup, used when creation of system lookup fails. */ + private static final SymbolLookup FALLBACK_LOOKUP = name -> { + Objects.requireNonNull(name); + return Optional.empty(); + }; + + /* + * On POSIX systems, dlsym will allow us to lookup symbol in library dependencies; the same trick doesn't work + * on Windows. For this reason, on Windows we do not generate any side-library, and load msvcrt.dll directly instead. + */ + private static final SymbolLookup SYSTEM_LOOKUP = makeSystemLookup(); + + private static SymbolLookup makeSystemLookup() { + try { + if (Utils.IS_WINDOWS) { + return makeWindowsLookup(); + } else { + return libLookup(libs -> libs.load(jdkLibraryPath("syslookup"))); + } + } catch (Throwable ex) { + // This can happen in the event of a library loading failure - e.g. if one of the libraries the + // system lookup depends on cannot be loaded for some reason. In such extreme cases, rather than + // fail, return a dummy lookup. + return FALLBACK_LOOKUP; + } + } + + private static SymbolLookup makeWindowsLookup() { + @SuppressWarnings("removal") + String systemRoot = AccessController.doPrivileged(new PrivilegedAction<>() { + @Override + public String run() { + return System.getenv("SystemRoot"); + } + }); + Path system32 = Path.of(systemRoot, "System32"); + Path ucrtbase = system32.resolve("ucrtbase.dll"); + Path msvcrt = system32.resolve("msvcrt.dll"); + + @SuppressWarnings("removal") + boolean useUCRT = AccessController.doPrivileged(new PrivilegedAction<>() { + @Override + public Boolean run() { + return Files.exists(ucrtbase); + } + }); + Path stdLib = useUCRT ? ucrtbase : msvcrt; + SymbolLookup lookup = libLookup(libs -> libs.load(stdLib)); + + if (useUCRT) { + // use a fallback lookup to look up inline functions from fallback lib + + SymbolLookup fallbackLibLookup = + libLookup(libs -> libs.load(jdkLibraryPath("syslookup"))); + + @SuppressWarnings("restricted") + MemorySegment funcs = fallbackLibLookup.findOrThrow("funcs") + .reinterpret(WindowsFallbackSymbols.LAYOUT.byteSize()); + + Function> fallbackLookup = name -> Optional.ofNullable(WindowsFallbackSymbols.valueOfOrNull(name)) + .map(symbol -> funcs.getAtIndex(ADDRESS, symbol.ordinal())); + + final SymbolLookup finalLookup = lookup; + lookup = name -> { + Objects.requireNonNull(name); + if (Utils.containsNullChars(name)) return Optional.empty(); + return finalLookup.find(name).or(() -> fallbackLookup.apply(name)); + }; + } + + return lookup; + } + + private static SymbolLookup libLookup(Function loader) { + NativeLibrary lib = loader.apply(RawNativeLibraries.newInstance(MethodHandles.lookup())); + return name -> { + Objects.requireNonNull(name); + if (Utils.containsNullChars(name)) return Optional.empty(); + try { + long addr = lib.lookup(name); + return addr == 0 ? + Optional.empty() : + Optional.of(MemorySegment.ofAddress(addr)); + } catch (NoSuchMethodException e) { + return Optional.empty(); + } + }; + } + + /* + * Returns the path of the given library name from JDK + */ + private static Path jdkLibraryPath(String name) { + Path javahome = Path.of(GetPropertyAction.privilegedGetProperty("java.home")); + String lib = Utils.IS_WINDOWS ? "bin" : "lib"; + String libname = System.mapLibraryName(name); + return javahome.resolve(lib).resolve(libname); + } + + + public static SystemLookup getInstance() { + return INSTANCE; + } + + @Override + public Optional find(String name) { + return SYSTEM_LOOKUP.find(name); + } + + // fallback symbols missing from ucrtbase.dll + // this list has to be kept in sync with the table in the companion native library + private enum WindowsFallbackSymbols { + // stdio + fprintf, + fprintf_s, + fscanf, + fscanf_s, + fwprintf, + fwprintf_s, + fwscanf, + fwscanf_s, + printf, + printf_s, + scanf, + scanf_s, + snprintf, + sprintf, + sprintf_s, + sscanf, + sscanf_s, + swprintf, + swprintf_s, + swscanf, + swscanf_s, + vfprintf, + vfprintf_s, + vfscanf, + vfscanf_s, + vfwprintf, + vfwprintf_s, + vfwscanf, + vfwscanf_s, + vprintf, + vprintf_s, + vscanf, + vscanf_s, + vsnprintf, + vsnprintf_s, + vsprintf, + vsprintf_s, + vsscanf, + vsscanf_s, + vswprintf, + vswprintf_s, + vswscanf, + vswscanf_s, + vwprintf, + vwprintf_s, + vwscanf, + vwscanf_s, + wprintf, + wprintf_s, + wscanf, + wscanf_s, + + // time + gmtime; + + static WindowsFallbackSymbols valueOfOrNull(String name) { + try { + return valueOf(name); + } catch (IllegalArgumentException e) { + return null; + } + } + + static final SequenceLayout LAYOUT = MemoryLayout.sequenceLayout( + values().length, ADDRESS); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/Utils$1VarHandleCache.class b/tests/test_data/std/jdk/internal/foreign/Utils$1VarHandleCache.class new file mode 100644 index 00000000..0dd83701 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/Utils$1VarHandleCache.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/Utils$BaseAndScale.class b/tests/test_data/std/jdk/internal/foreign/Utils$BaseAndScale.class new file mode 100644 index 00000000..0d21a6f9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/Utils$BaseAndScale.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/Utils.class b/tests/test_data/std/jdk/internal/foreign/Utils.class new file mode 100644 index 00000000..b471d734 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/Utils.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/Utils.java b/tests/test_data/std/jdk/internal/foreign/Utils.java new file mode 100644 index 00000000..2ed65e3d --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/Utils.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemoryLayout.PathElement; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.ForceInline; +import sun.invoke.util.Wrapper; + +import static sun.security.action.GetPropertyAction.privilegedGetProperty; + +/** + * This class contains misc helper functions to support creation of memory segments. + */ +public final class Utils { + + public static final boolean IS_WINDOWS = privilegedGetProperty("os.name").startsWith("Windows"); + + // Suppresses default constructor, ensuring non-instantiability. + private Utils() {} + + private static final MethodHandle BYTE_TO_BOOL; + private static final MethodHandle BOOL_TO_BYTE; + private static final MethodHandle ADDRESS_TO_LONG; + private static final MethodHandle LONG_TO_ADDRESS; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + BYTE_TO_BOOL = lookup.findStatic(Utils.class, "byteToBoolean", + MethodType.methodType(boolean.class, byte.class)); + BOOL_TO_BYTE = lookup.findStatic(Utils.class, "booleanToByte", + MethodType.methodType(byte.class, boolean.class)); + ADDRESS_TO_LONG = lookup.findStatic(SharedUtils.class, "unboxSegment", + MethodType.methodType(long.class, MemorySegment.class)); + LONG_TO_ADDRESS = lookup.findStatic(Utils.class, "longToAddress", + MethodType.methodType(MemorySegment.class, long.class, long.class, long.class)); + } catch (Throwable ex) { + throw new ExceptionInInitializerError(ex); + } + } + + public static long alignUp(long n, long alignment) { + return (n + alignment - 1) & -alignment; + } + + public static MemorySegment alignUp(MemorySegment ms, long alignment) { + long offset = ms.address(); + return ms.asSlice(alignUp(offset, alignment) - offset); + } + + /** + * This method returns a raw var handle, that is, a var handle that does not perform any size + * or alignment checks. Such checks are added (using adaptation) by {@link LayoutPath#dereferenceHandle()}. + *

+ * We provide two level of caching of the generated var handles. First, the var handle associated + * with a {@link ValueLayout#varHandle()} call is cached inside a stable field of the value layout implementation. + * This optimizes common code idioms like {@code JAVA_INT.varHandle().getInt(...)}. A second layer of caching + * is then provided by this method: after all, var handles constructed by {@link MemoryLayout#varHandle(PathElement...)} + * will be obtained by adapting some raw var handle generated by this method. + * + * @param layout the value layout for which a raw memory segment var handle is to be created. + * @return a raw memory segment var handle. + */ + public static VarHandle makeRawSegmentViewVarHandle(ValueLayout layout) { + final class VarHandleCache { + private static final Map HANDLE_MAP = new ConcurrentHashMap<>(); + } + return VarHandleCache.HANDLE_MAP + .computeIfAbsent(layout.withoutName(), Utils::makeRawSegmentViewVarHandleInternal); + } + + private static VarHandle makeRawSegmentViewVarHandleInternal(ValueLayout layout) { + Class baseCarrier = layout.carrier(); + if (layout.carrier() == MemorySegment.class) { + baseCarrier = switch ((int) ValueLayout.ADDRESS.byteSize()) { + case Long.BYTES -> long.class; + case Integer.BYTES -> int.class; + default -> throw new UnsupportedOperationException("Unsupported address layout"); + }; + } else if (layout.carrier() == boolean.class) { + baseCarrier = byte.class; + } + + VarHandle handle = SharedSecrets.getJavaLangInvokeAccess().memorySegmentViewHandle(baseCarrier, + layout.byteAlignment() - 1, layout.order()); + + if (layout.carrier() == boolean.class) { + handle = MethodHandles.filterValue(handle, BOOL_TO_BYTE, BYTE_TO_BOOL); + } else if (layout instanceof AddressLayout addressLayout) { + handle = MethodHandles.filterValue(handle, + MethodHandles.explicitCastArguments(ADDRESS_TO_LONG, MethodType.methodType(baseCarrier, MemorySegment.class)), + MethodHandles.explicitCastArguments(MethodHandles.insertArguments(LONG_TO_ADDRESS, 1, + pointeeByteSize(addressLayout), pointeeByteAlign(addressLayout)), + MethodType.methodType(MemorySegment.class, baseCarrier))); + } + return handle; + } + + public static boolean byteToBoolean(byte b) { + return b != 0; + } + + private static byte booleanToByte(boolean b) { + return b ? (byte)1 : (byte)0; + } + + @ForceInline + public static MemorySegment longToAddress(long addr, long size, long align) { + if (!isAligned(addr, align)) { + throw new IllegalArgumentException("Invalid alignment constraint for address: " + toHexString(addr)); + } + return SegmentFactories.makeNativeSegmentUnchecked(addr, size); + } + + @ForceInline + public static MemorySegment longToAddress(long addr, long size, long align, MemorySessionImpl scope) { + if (!isAligned(addr, align)) { + throw new IllegalArgumentException("Invalid alignment constraint for address: " + toHexString(addr)); + } + return SegmentFactories.makeNativeSegmentUnchecked(addr, size, scope); + } + + @ForceInline + public static boolean isAligned(long offset, long align) { + return (offset & (align - 1)) == 0; + } + + @ForceInline + public static boolean isElementAligned(ValueLayout layout) { + // Fast-path: if both size and alignment are powers of two, we can just + // check if one is greater than the other. + assert isPowerOfTwo(layout.byteSize()); + return layout.byteAlignment() <= layout.byteSize(); + } + + @ForceInline + public static void checkElementAlignment(ValueLayout layout, String msg) { + if (!isElementAligned(layout)) { + throw new IllegalArgumentException(msg); + } + } + + @ForceInline + public static void checkElementAlignment(MemoryLayout layout, String msg) { + if (layout.byteSize() % layout.byteAlignment() != 0) { + throw new IllegalArgumentException(msg); + } + } + + public static long pointeeByteSize(AddressLayout addressLayout) { + return addressLayout.targetLayout() + .map(MemoryLayout::byteSize) + .orElse(0L); + } + + public static long pointeeByteAlign(AddressLayout addressLayout) { + return addressLayout.targetLayout() + .map(MemoryLayout::byteAlignment) + .orElse(1L); + } + + public static void checkAllocationSizeAndAlign(long byteSize, long byteAlignment) { + // byteSize should be >= 0 + Utils.checkNonNegativeArgument(byteSize, "allocation size"); + checkAlign(byteAlignment); + } + + public static void checkAlign(long byteAlignment) { + // alignment should be > 0, and power of two + if (byteAlignment <= 0 || + ((byteAlignment & (byteAlignment - 1)) != 0L)) { + throw new IllegalArgumentException("Invalid alignment constraint : " + byteAlignment); + } + } + + @ForceInline + public static void checkNonNegativeArgument(long value, String name) { + if (value < 0) { + throw new IllegalArgumentException("The provided " + name + " is negative: " + value); + } + } + + @ForceInline + public static void checkNonNegativeIndex(long value, String name) { + if (value < 0) { + throw new IndexOutOfBoundsException("The provided " + name + " is negative: " + value); + } + } + + private static long computePadding(long offset, long align) { + boolean isAligned = offset == 0 || offset % align == 0; + if (isAligned) { + return 0; + } else { + long gap = offset % align; + return align - gap; + } + } + + /** + * {@return return a struct layout constructed from the given elements, with padding + * computed automatically so that they are naturally aligned}. + * + * @param elements the structs' fields + */ + public static StructLayout computePaddedStructLayout(MemoryLayout... elements) { + long offset = 0L; + List layouts = new ArrayList<>(); + long align = 0; + for (MemoryLayout l : elements) { + long padding = computePadding(offset, l.byteAlignment()); + if (padding != 0) { + layouts.add(MemoryLayout.paddingLayout(padding)); + offset += padding; + } + layouts.add(l); + align = Math.max(align, l.byteAlignment()); + offset += l.byteSize(); + } + long padding = computePadding(offset, align); + if (padding != 0) { + layouts.add(MemoryLayout.paddingLayout(padding)); + } + return MemoryLayout.structLayout(layouts.toArray(MemoryLayout[]::new)); + } + + public static int byteWidthOfPrimitive(Class primitive) { + return Wrapper.forPrimitiveType(primitive).bitWidth() / 8; + } + + public static boolean isPowerOfTwo(long value) { + return (value & (value - 1)) == 0L; + } + + public static L wrapOverflow(Supplier layoutSupplier) { + try { + return layoutSupplier.get(); + } catch (ArithmeticException ex) { + throw new IllegalArgumentException("Layout size exceeds Long.MAX_VALUE"); + } + } + + public static boolean containsNullChars(String s) { + return s.indexOf('\u0000') >= 0; + } + + public static String toHexString(long value) { + return "0x" + Long.toHexString(value); + } + + public record BaseAndScale(int base, long scale) { + + public static final BaseAndScale BYTE = + new BaseAndScale(Unsafe.ARRAY_BYTE_BASE_OFFSET, Unsafe.ARRAY_BYTE_INDEX_SCALE); + public static final BaseAndScale CHAR = + new BaseAndScale(Unsafe.ARRAY_CHAR_BASE_OFFSET, Unsafe.ARRAY_CHAR_INDEX_SCALE); + public static final BaseAndScale SHORT = + new BaseAndScale(Unsafe.ARRAY_SHORT_BASE_OFFSET, Unsafe.ARRAY_SHORT_INDEX_SCALE); + public static final BaseAndScale INT = + new BaseAndScale(Unsafe.ARRAY_INT_BASE_OFFSET, Unsafe.ARRAY_INT_INDEX_SCALE); + public static final BaseAndScale FLOAT = + new BaseAndScale(Unsafe.ARRAY_FLOAT_BASE_OFFSET, Unsafe.ARRAY_FLOAT_INDEX_SCALE); + public static final BaseAndScale LONG = + new BaseAndScale(Unsafe.ARRAY_LONG_BASE_OFFSET, Unsafe.ARRAY_LONG_INDEX_SCALE); + public static final BaseAndScale DOUBLE = + new BaseAndScale(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + + public static BaseAndScale of(Object array) { + return switch (array) { + case byte[] _ -> BaseAndScale.BYTE; + case char[] _ -> BaseAndScale.CHAR; + case short[] _ -> BaseAndScale.SHORT; + case int[] _ -> BaseAndScale.INT; + case float[] _ -> BaseAndScale.FLOAT; + case long[] _ -> BaseAndScale.LONG; + case double[] _ -> BaseAndScale.DOUBLE; + default -> throw new IllegalArgumentException("Not a supported array class: " + array.getClass().getSimpleName()); + }; + } + + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ABIDescriptor.class b/tests/test_data/std/jdk/internal/foreign/abi/ABIDescriptor.class new file mode 100644 index 00000000..ed0bd631 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ABIDescriptor.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ABIDescriptor.java b/tests/test_data/std/jdk/internal/foreign/abi/ABIDescriptor.java new file mode 100644 index 00000000..b25280ed --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ABIDescriptor.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +/** + * Carrier class used to communicate with the VM + * + * It is particularly low-level since the VM will be accessing these fields directly + */ +public class ABIDescriptor { + final Architecture arch; + + public final VMStorage[][] inputStorage; + public final VMStorage[][] outputStorage; + + final VMStorage[][] volatileStorage; + + final int stackAlignment; + final int shadowSpace; + + final VMStorage scratch1; + final VMStorage scratch2; + + final VMStorage targetAddrStorage; + final VMStorage retBufAddrStorage; + final VMStorage capturedStateStorage; + + public ABIDescriptor(Architecture arch, VMStorage[][] inputStorage, VMStorage[][] outputStorage, + VMStorage[][] volatileStorage, int stackAlignment, int shadowSpace, + VMStorage scratch1, VMStorage scratch2, + VMStorage targetAddrStorage, VMStorage retBufAddrStorage, + VMStorage capturedStateStorage) { + this.arch = arch; + this.inputStorage = inputStorage; + this.outputStorage = outputStorage; + this.volatileStorage = volatileStorage; + this.stackAlignment = stackAlignment; + this.shadowSpace = shadowSpace; + this.scratch1 = scratch1; + this.scratch2 = scratch2; + this.targetAddrStorage = targetAddrStorage; + this.retBufAddrStorage = retBufAddrStorage; + this.capturedStateStorage = capturedStateStorage; + } + + public VMStorage targetAddrStorage() { + return targetAddrStorage; + } + + public VMStorage retBufAddrStorage() { + return retBufAddrStorage; + } + + public VMStorage capturedStateStorage() { + return capturedStateStorage; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker$LinkRequest.class b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker$LinkRequest.class new file mode 100644 index 00000000..a1d0bc04 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker$LinkRequest.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker$UpcallStubFactory.class b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker$UpcallStubFactory.class new file mode 100644 index 00000000..6a619f69 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker$UpcallStubFactory.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker.class b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker.class new file mode 100644 index 00000000..1f017ab1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker.java b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker.java new file mode 100644 index 00000000..4f3baaa0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/AbstractLinker.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import jdk.internal.foreign.SystemLookup; +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker; +import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker; +import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64Linker; +import jdk.internal.foreign.abi.fallback.FallbackLinker; +import jdk.internal.foreign.abi.ppc64.aix.AixPPC64Linker; +import jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64Linker; +import jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64leLinker; +import jdk.internal.foreign.abi.riscv64.linux.LinuxRISCV64Linker; +import jdk.internal.foreign.abi.s390.linux.LinuxS390Linker; +import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker; +import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker; +import jdk.internal.foreign.layout.AbstractLayout; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.UnionLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.HashSet; +import java.util.List; +import java.nio.ByteOrder; +import java.util.Objects; +import java.util.Set; + +public abstract sealed class AbstractLinker implements Linker permits LinuxAArch64Linker, MacOsAArch64Linker, + SysVx64Linker, WindowsAArch64Linker, + Windowsx64Linker, AixPPC64Linker, + LinuxPPC64Linker, LinuxPPC64leLinker, + LinuxRISCV64Linker, LinuxS390Linker, + FallbackLinker { + + public interface UpcallStubFactory { + MemorySegment makeStub(MethodHandle target, Arena arena); + } + + private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) {} + private final SoftReferenceCache DOWNCALL_CACHE = new SoftReferenceCache<>(); + private final SoftReferenceCache UPCALL_CACHE = new SoftReferenceCache<>(); + private final Set CANONICAL_LAYOUTS_CACHE = new HashSet<>(canonicalLayouts().values()); + + @Override + @CallerSensitive + public final MethodHandle downcallHandle(MemorySegment symbol, FunctionDescriptor function, Option... options) { + Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle"); + SharedUtils.checkSymbol(symbol); + return downcallHandle0(function, options).bindTo(symbol); + } + + @Override + @CallerSensitive + public final MethodHandle downcallHandle(FunctionDescriptor function, Option... options) { + Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle"); + return downcallHandle0(function, options); + } + + private MethodHandle downcallHandle0(FunctionDescriptor function, Option... options) { + Objects.requireNonNull(function); + Objects.requireNonNull(options); + checkLayouts(function); + function = stripNames(function); + LinkerOptions optionSet = LinkerOptions.forDowncall(function, options); + validateVariadicLayouts(function, optionSet); + + return DOWNCALL_CACHE.get(new LinkRequest(function, optionSet), linkRequest -> { + FunctionDescriptor fd = linkRequest.descriptor(); + MethodType type = fd.toMethodType(); + MethodHandle handle = arrangeDowncall(type, fd, linkRequest.options()); + handle = SharedUtils.maybeCheckCaptureSegment(handle, linkRequest.options()); + handle = SharedUtils.maybeInsertAllocator(fd, handle); + return handle; + }); + } + + protected abstract MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options); + + @Override + @CallerSensitive + public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options) { + Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "upcallStub"); + Objects.requireNonNull(arena); + Objects.requireNonNull(target); + Objects.requireNonNull(function); + checkLayouts(function); + SharedUtils.checkExceptions(target); + function = stripNames(function); + LinkerOptions optionSet = LinkerOptions.forUpcall(function, options); + + MethodType type = function.toMethodType(); + if (!type.equals(target.type())) { + throw new IllegalArgumentException("Wrong method handle type: " + target.type()); + } + + UpcallStubFactory factory = UPCALL_CACHE.get(new LinkRequest(function, optionSet), linkRequest -> + arrangeUpcall(type, linkRequest.descriptor(), linkRequest.options())); + return factory.makeStub(target, arena); + } + + protected abstract UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options); + + @Override + public SystemLookup defaultLookup() { + return SystemLookup.getInstance(); + } + + // C spec mandates that variadic arguments smaller than int are promoted to int, + // and float is promoted to double + // See: https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions + // We reject the corresponding layouts here, to avoid issues where unsigned values + // are sign extended when promoted. (as we don't have a way to unambiguously represent signed-ness atm). + private void validateVariadicLayouts(FunctionDescriptor function, LinkerOptions optionSet) { + if (optionSet.isVariadicFunction()) { + List argumentLayouts = function.argumentLayouts(); + List variadicLayouts = argumentLayouts.subList(optionSet.firstVariadicArgIndex(), argumentLayouts.size()); + + for (MemoryLayout variadicLayout : variadicLayouts) { + if (variadicLayout.equals(ValueLayout.JAVA_BOOLEAN) + || variadicLayout.equals(ValueLayout.JAVA_BYTE) + || variadicLayout.equals(ValueLayout.JAVA_CHAR) + || variadicLayout.equals(ValueLayout.JAVA_SHORT) + || variadicLayout.equals(ValueLayout.JAVA_FLOAT)) { + throw new IllegalArgumentException("Invalid variadic argument layout: " + variadicLayout); + } + } + } + } + + private void checkLayouts(FunctionDescriptor descriptor) { + descriptor.returnLayout().ifPresent(this::checkLayout); + descriptor.argumentLayouts().forEach(this::checkLayout); + } + + private void checkLayout(MemoryLayout layout) { + // Note: we should not worry about padding layouts, as they cannot be present in a function descriptor + if (layout instanceof SequenceLayout) { + throw new IllegalArgumentException("Unsupported layout: " + layout); + } else { + checkLayoutRecursive(layout); + } + } + + // some ABIs have special handling for struct members + protected void checkStructMember(MemoryLayout member, long offset) { + checkLayoutRecursive(member); + } + + private void checkLayoutRecursive(MemoryLayout layout) { + if (layout instanceof ValueLayout vl) { + checkSupported(vl); + } else if (layout instanceof StructLayout sl) { + checkHasNaturalAlignment(layout); + long offset = 0; + long lastUnpaddedOffset = 0; + for (MemoryLayout member : sl.memberLayouts()) { + // check element offset before recursing so that an error points at the + // outermost layout first + checkMemberOffset(sl, member, lastUnpaddedOffset, offset); + checkStructMember(member, offset); + + offset += member.byteSize(); + if (!(member instanceof PaddingLayout)) { + lastUnpaddedOffset = offset; + } + } + checkGroupSize(sl, lastUnpaddedOffset); + } else if (layout instanceof UnionLayout ul) { + checkHasNaturalAlignment(layout); + long maxUnpaddedLayout = 0; + for (MemoryLayout member : ul.memberLayouts()) { + checkLayoutRecursive(member); + if (!(member instanceof PaddingLayout)) { + maxUnpaddedLayout = Long.max(maxUnpaddedLayout, member.byteSize()); + } + } + checkGroupSize(ul, maxUnpaddedLayout); + } else if (layout instanceof SequenceLayout sl) { + checkHasNaturalAlignment(layout); + checkLayoutRecursive(sl.elementLayout()); + } + } + + // check for trailing padding + private void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) { + long expectedSize = Utils.alignUp(maxUnpaddedOffset, gl.byteAlignment()); + if (gl.byteSize() != expectedSize) { + throw new IllegalArgumentException("Layout '" + gl + "' has unexpected size: " + + gl.byteSize() + " != " + expectedSize); + } + } + + // checks both that there is no excess padding between 'memberLayout' and + // the previous layout + private void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout, + long lastUnpaddedOffset, long offset) { + long expectedOffset = Utils.alignUp(lastUnpaddedOffset, memberLayout.byteAlignment()); + if (expectedOffset != offset) { + throw new IllegalArgumentException("Member layout '" + memberLayout + "', of '" + parent + "'" + + " found at unexpected offset: " + offset + " != " + expectedOffset); + } + } + + private void checkSupported(ValueLayout valueLayout) { + valueLayout = valueLayout.withoutName(); + if (valueLayout instanceof AddressLayout addressLayout) { + valueLayout = addressLayout.withoutTargetLayout(); + } + if (!CANONICAL_LAYOUTS_CACHE.contains(valueLayout.withoutName())) { + throw new IllegalArgumentException("Unsupported layout: " + valueLayout); + } + } + + private void checkHasNaturalAlignment(MemoryLayout layout) { + if (!((AbstractLayout) layout).hasNaturalAlignment()) { + throw new IllegalArgumentException("Layout alignment must be natural alignment: " + layout); + } + } + + @SuppressWarnings("restricted") + private static MemoryLayout stripNames(MemoryLayout ml) { + // we don't care about transferring alignment and byte order here + // since the linker already restricts those such that they will always be the same + return switch (ml) { + case StructLayout sl -> MemoryLayout.structLayout(stripNames(sl.memberLayouts())); + case UnionLayout ul -> MemoryLayout.unionLayout(stripNames(ul.memberLayouts())); + case SequenceLayout sl -> MemoryLayout.sequenceLayout(sl.elementCount(), stripNames(sl.elementLayout())); + case AddressLayout al -> al.targetLayout() + .map(tl -> al.withoutName().withTargetLayout(stripNames(tl))) // restricted + .orElseGet(al::withoutName); + default -> ml.withoutName(); // ValueLayout and PaddingLayout + }; + } + + private static MemoryLayout[] stripNames(List layouts) { + return layouts.stream() + .map(AbstractLinker::stripNames) + .toArray(MemoryLayout[]::new); + } + + private static FunctionDescriptor stripNames(FunctionDescriptor function) { + return function.returnLayout() + .map(rl -> FunctionDescriptor.of(stripNames(rl), stripNames(function.argumentLayouts()))) + .orElseGet(() -> FunctionDescriptor.ofVoid(stripNames(function.argumentLayouts()))); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Architecture.class b/tests/test_data/std/jdk/internal/foreign/abi/Architecture.class new file mode 100644 index 00000000..b273cfcb Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Architecture.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Architecture.java b/tests/test_data/std/jdk/internal/foreign/abi/Architecture.java new file mode 100644 index 00000000..c94c4485 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/Architecture.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +public interface Architecture { + boolean isStackType(int cls); + int typeSize(int cls); +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Allocate.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Allocate.class new file mode 100644 index 00000000..c2b277e5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Allocate.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$BoxAddress.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$BoxAddress.class new file mode 100644 index 00000000..149b82ee Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$BoxAddress.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$BufferLoad.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$BufferLoad.class new file mode 100644 index 00000000..03236548 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$BufferLoad.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$BufferStore.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$BufferStore.class new file mode 100644 index 00000000..182e2ebf Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$BufferStore.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Builder.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Builder.class new file mode 100644 index 00000000..b295f4b6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Builder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Cast$1.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Cast$1.class new file mode 100644 index 00000000..ae41f043 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Cast$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Cast.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Cast.class new file mode 100644 index 00000000..66dce070 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Cast.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Copy.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Copy.class new file mode 100644 index 00000000..56215ae1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Copy.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Dereference.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Dereference.class new file mode 100644 index 00000000..90691de4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Dereference.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Dup.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Dup.class new file mode 100644 index 00000000..e54e0dfc Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Dup.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$Move.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Move.class new file mode 100644 index 00000000..c25b350b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$Move.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$SegmentBase.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$SegmentBase.class new file mode 100644 index 00000000..36294817 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$SegmentBase.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$SegmentOffset.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$SegmentOffset.class new file mode 100644 index 00000000..44a393c5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$SegmentOffset.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$ShiftLeft.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$ShiftLeft.class new file mode 100644 index 00000000..8837381f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$ShiftLeft.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$ShiftRight.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$ShiftRight.class new file mode 100644 index 00000000..cdff094b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$ShiftRight.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$VMLoad.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$VMLoad.class new file mode 100644 index 00000000..ec7875d0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$VMLoad.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding$VMStore.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding$VMStore.class new file mode 100644 index 00000000..29246e25 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding$VMStore.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding.class b/tests/test_data/std/jdk/internal/foreign/abi/Binding.class new file mode 100644 index 00000000..a4ad3ae3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/Binding.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/Binding.java b/tests/test_data/std/jdk/internal/foreign/abi/Binding.java new file mode 100644 index 00000000..1f7f3327 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/Binding.java @@ -0,0 +1,902 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import jdk.internal.foreign.AbstractMemorySegmentImpl; +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.BindingInterpreter.LoadFunc; +import jdk.internal.foreign.abi.BindingInterpreter.StoreFunc; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import static java.lang.foreign.ValueLayout.JAVA_BYTE; +import static java.lang.foreign.ValueLayout.JAVA_INT_UNALIGNED; +import static java.lang.foreign.ValueLayout.JAVA_SHORT_UNALIGNED; + +/** + * The binding operators defined in the Binding class can be combined into argument and return value processing 'recipes'. + * + * The binding operators are interpreted using a stack-base interpreter. Operators can either consume operands from the + * stack, or push them onto the stack. + * + * In the description of each binding we talk about 'boxing' and 'unboxing'. + * - Unboxing is the process of taking a Java value and decomposing it, and storing components into machine + * storage locations. As such, the binding interpreter stack starts with the Java value on it, and should end empty. + * - Boxing is the process of re-composing a Java value by pulling components from machine storage locations. + * If a MemorySegment is needed to store the result, one should be allocated using the ALLOCATE_BUFFER operator. + * The binding interpreter stack starts off empty, and ends with the value to be returned as the only value on it. + * A binding operator can be interpreted differently based on whether we are boxing or unboxing a value. For example, + * the CONVERT_ADDRESS operator 'unboxes' a MemoryAddress to a long, but 'boxes' a long to a MemoryAddress. + * + * Here are some examples of binding recipes derived from C declarations, and according to the Windows ABI (recipes are + * ABI-specific). Note that each argument has its own recipe, which is indicated by '[number]:' (though, the only + * example that has multiple arguments is the one using varargs). + * + * -------------------- + * + * void f(int i); + * + * Argument bindings: + * 0: VM_STORE(rcx, int.class) // move an 'int' into the RCX register + * + * Return bindings: + * none + * + * -------------------- + * + * void f(int* i); + * + * Argument bindings: + * 0: UNBOX_ADDRESS // the 'MemoryAddress' is converted into a 'long' + * VM_STORE(rcx, long.class) // the 'long' is moved into the RCX register + * + * Return bindings: + * none + * + * -------------------- + * + * int* f(); + * + * Argument bindings: + * none + * + * Return bindings: + * 0: VM_LOAD(rax, long) // load a 'long' from the RAX register + * BOX_ADDRESS // convert the 'long' into a 'MemoryAddress' + * + * -------------------- + * + * typedef struct { // fits into single register + * int x; + * int y; + * } MyStruct; + * + * void f(MyStruct ms); + * + * Argument bindings: + * 0: BUFFER_LOAD(0, long.class) // From the struct's memory region, load a 'long' from offset '0' + * VM_STORE(rcx, long.class) // and copy that into the RCX register + * + * Return bindings: + * none + * + * -------------------- + * + * typedef struct { // does not fit into single register + * long long x; + * long long y; + * } MyStruct; + * + * void f(MyStruct ms); + * + * For the Windows ABI: + * + * Argument bindings: + * 0: COPY(16, 8) // copy the memory region containing the struct + * BASE_ADDRESS // take the base address of the copy + * UNBOX_ADDRESS // converts the base address to a 'long' + * VM_STORE(rcx, long.class) // moves the 'long' into the RCX register + * + * Return bindings: + * none + * + * For the SysV ABI: + * + * Argument bindings: + * 0: DUP // duplicates the MemoryRegion operand + * BUFFER_LOAD(0, long.class) // loads a 'long' from offset '0' + * VM_STORE(rdx, long.class) // moves the long into the RDX register + * BUFFER_LOAD(8, long.class) // loads a 'long' from offset '8' + * VM_STORE(rcx, long.class) // moves the long into the RCX register + * + * Return bindings: + * none + * + * -------------------- + * + * typedef struct { // fits into single register + * int x; + * int y; + * } MyStruct; + * + * MyStruct f(); + * + * Argument bindings: + * none + * + * Return bindings: + * 0: ALLOCATE(GroupLayout(C_INT, C_INT)) // allocate a buffer with the memory layout of the struct + * DUP // duplicate the allocated buffer + * VM_LOAD(rax, long.class) // loads a 'long' from rax + * BUFFER_STORE(0, long.class) // stores a 'long' at offset 0 + * + * -------------------- + * + * typedef struct { // does not fit into single register + * long long x; + * long long y; + * } MyStruct; + * + * MyStruct f(); + * + * !! uses synthetic argument, which is a pointer to a pre-allocated buffer + * + * Argument bindings: + * 0: UNBOX_ADDRESS // unbox the MemoryAddress synthetic argument + * VM_STORE(rcx, long.class) // moves the 'long' into the RCX register + * + * Return bindings: + * none + * + * -------------------- + * + * void f(int dummy, ...); // varargs + * + * f(0, 10f); // passing a float + * + * Argument bindings: + * 0: VM_STORE(rcx, int.class) // moves the 'int dummy' into the RCX register + * + * 1: DUP // duplicates the '10f' argument + * VM_STORE(rdx, float.class) // move one copy into the RDX register + * VM_STORE(xmm1, float.class) // moves the other copy into the xmm2 register + * + * Return bindings: + * none + * + * -------------------- + */ +public sealed interface Binding { + + void verify(Deque> stack); + + void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator); + + private static void checkType(Class type) { + if (type != Object.class && (!type.isPrimitive() || type == void.class)) + throw new IllegalArgumentException("Illegal type: " + type); + } + + private static void checkOffset(long offset) { + if (offset < 0) + throw new IllegalArgumentException("Negative offset: " + offset); + } + + private static void checkByteWidth(int byteWidth, Class type) { + if (byteWidth < 0 || byteWidth > Utils.byteWidthOfPrimitive(type)) + throw new IllegalArgumentException("Illegal byteWidth: " + byteWidth); + } + + static VMStore vmStore(VMStorage storage, Class type) { + checkType(type); + return new VMStore(storage, type); + } + + static VMLoad vmLoad(VMStorage storage, Class type) { + checkType(type); + return new VMLoad(storage, type); + } + + static BufferStore bufferStore(long offset, Class type) { + return bufferStore(offset, type, Utils.byteWidthOfPrimitive(type)); + } + + static BufferStore bufferStore(long offset, Class type, int byteWidth) { + checkType(type); + checkOffset(offset); + checkByteWidth(byteWidth, type); + return new BufferStore(offset, type, byteWidth); + } + + static BufferLoad bufferLoad(long offset, Class type) { + return Binding.bufferLoad(offset, type, Utils.byteWidthOfPrimitive(type)); + } + + static BufferLoad bufferLoad(long offset, Class type, int byteWidth) { + checkType(type); + checkOffset(offset); + checkByteWidth(byteWidth, type); + return new BufferLoad(offset, type, byteWidth); + } + + static Copy copy(MemoryLayout layout) { + return new Copy(layout.byteSize(), layout.byteAlignment()); + } + + static Allocate allocate(MemoryLayout layout) { + return new Allocate(layout.byteSize(), layout.byteAlignment()); + } + + static BoxAddress boxAddressRaw(long size, long align) { + return new BoxAddress(size, align, false); + } + + static BoxAddress boxAddress(MemoryLayout layout) { + return new BoxAddress(layout.byteSize(), layout.byteAlignment(), true); + } + + static BoxAddress boxAddress(long byteSize) { + return new BoxAddress(byteSize, 1, true); + } + + // alias + static SegmentOffset unboxAddress() { + return segmentOffsetNoAllowHeap(); + } + + static SegmentBase segmentBase() { + return SegmentBase.INSTANCE; + } + + static SegmentOffset segmentOffsetAllowHeap() { + return SegmentOffset.INSTANCE_ALLOW_HEAP; + } + + static SegmentOffset segmentOffsetNoAllowHeap() { + return SegmentOffset.INSTANCE_NO_ALLOW_HEAP; + } + + static Dup dup() { + return Dup.INSTANCE; + } + + static ShiftLeft shiftLeft(int shiftAmount) { + if (shiftAmount <= 0) + throw new IllegalArgumentException("shiftAmount must be positive"); + return new ShiftLeft(shiftAmount); + } + + static ShiftRight shiftRight(int shiftAmount) { + if (shiftAmount <= 0) + throw new IllegalArgumentException("shiftAmount must be positive"); + return new ShiftRight(shiftAmount); + } + + static Binding cast(Class fromType, Class toType) { + if (fromType == int.class) { + if (toType == boolean.class) { + return Cast.INT_TO_BOOLEAN; + } else if (toType == byte.class) { + return Cast.INT_TO_BYTE; + } else if (toType == short.class) { + return Cast.INT_TO_SHORT; + } else if (toType == char.class) { + return Cast.INT_TO_CHAR; + } else if (toType == long.class) { + return Cast.INT_TO_LONG; + } + } else if (toType == int.class) { + if (fromType == boolean.class) { + return Cast.BOOLEAN_TO_INT; + } else if (fromType == byte.class) { + return Cast.BYTE_TO_INT; + } else if (fromType == short.class) { + return Cast.SHORT_TO_INT; + } else if (fromType == char.class) { + return Cast.CHAR_TO_INT; + } else if (fromType == long.class) { + return Cast.LONG_TO_INT; + } + } else if (fromType == long.class) { + if (toType == byte.class) { + return Cast.LONG_TO_BYTE; + } else if (toType == short.class) { + return Cast.LONG_TO_SHORT; + } else if (toType == char.class) { + return Cast.LONG_TO_CHAR; + } + } else if (toType == long.class) { + if (fromType == byte.class) { + return Cast.BYTE_TO_LONG; + } else if (fromType == short.class) { + return Cast.SHORT_TO_LONG; + } else if (fromType == char.class) { + return Cast.CHAR_TO_LONG; + } + } + throw new IllegalArgumentException("Unknown conversion: " + fromType + " -> " + toType); + } + + + static Binding.Builder builder() { + return new Binding.Builder(); + } + + /** + * A builder helper class for generating lists of Bindings + */ + class Builder { + private final List bindings = new ArrayList<>(); + + private static boolean isSubIntType(Class type) { + return type == boolean.class || type == byte.class || type == short.class || type == char.class; + } + + public Binding.Builder vmStore(VMStorage storage, Class type) { + if (isSubIntType(type)) { + bindings.add(Binding.cast(type, int.class)); + type = int.class; + } + bindings.add(Binding.vmStore(storage, type)); + return this; + } + + public Binding.Builder vmLoad(VMStorage storage, Class type) { + Class loadType = type; + if (isSubIntType(type)) { + loadType = int.class; + } + bindings.add(Binding.vmLoad(storage, loadType)); + if (isSubIntType(type)) { + bindings.add(Binding.cast(int.class, type)); + } + return this; + } + + public Binding.Builder bufferStore(long offset, Class type) { + bindings.add(Binding.bufferStore(offset, type)); + return this; + } + + public Binding.Builder bufferStore(long offset, Class type, int byteWidth) { + bindings.add(Binding.bufferStore(offset, type, byteWidth)); + return this; + } + + public Binding.Builder bufferLoad(long offset, Class type) { + bindings.add(Binding.bufferLoad(offset, type)); + return this; + } + + public Binding.Builder bufferLoad(long offset, Class type, int byteWidth) { + bindings.add(Binding.bufferLoad(offset, type, byteWidth)); + return this; + } + + public Binding.Builder copy(MemoryLayout layout) { + bindings.add(Binding.copy(layout)); + return this; + } + + public Binding.Builder allocate(MemoryLayout layout) { + bindings.add(Binding.allocate(layout)); + return this; + } + + public Binding.Builder boxAddressRaw(long size, long align) { + bindings.add(Binding.boxAddressRaw(size, align)); + return this; + } + + public Binding.Builder boxAddress(MemoryLayout layout) { + bindings.add(Binding.boxAddress(layout)); + return this; + } + + public Binding.Builder unboxAddress() { + bindings.add(Binding.unboxAddress()); + return this; + } + + public Binding.Builder segmentBase() { + bindings.add(Binding.segmentBase()); + return this; + } + + public Binding.Builder segmentOffsetAllowHeap() { + bindings.add(Binding.segmentOffsetAllowHeap()); + return this; + } + + public Binding.Builder segmentOffsetNoAllowHeap() { + bindings.add(Binding.segmentOffsetNoAllowHeap()); + return this; + } + + public Binding.Builder dup() { + bindings.add(Binding.dup()); + return this; + } + + // Converts to long if needed then shifts left by the given number of Bytes. + public Binding.Builder shiftLeft(int shiftAmount, Class type) { + if (type != long.class) { + bindings.add(Binding.cast(type, long.class)); + } + bindings.add(Binding.shiftLeft(shiftAmount)); + return this; + } + + // Shifts right by the given number of Bytes then converts from long if needed. + public Binding.Builder shiftRight(int shiftAmount, Class type) { + bindings.add(Binding.shiftRight(shiftAmount)); + if (type != long.class) { + bindings.add(Binding.cast(long.class, type)); + } + return this; + } + + public List build() { + return List.copyOf(bindings); + } + } + + sealed interface Move extends Binding { + VMStorage storage(); + Class type(); + } + + /** + * VM_STORE([storage location], [type]) + * Pops a [type] from the operand stack, and moves it to [storage location] + * The [type] must be one of byte, short, char, int, long, float, or double. + * [storage location] may be 'null', indicating that this value should be passed + * to the VM stub, but does not have an explicit target register (e.g. oop offsets) + */ + record VMStore(VMStorage storage, Class type) implements Move { + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + Class expectedType = type(); + SharedUtils.checkType(actualType, expectedType); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + storeFunc.store(storage(), stack.pop()); + } + } + + /** + * VM_LOAD([storage location], [type]) + * Loads a [type] from [storage location], and pushes it onto the operand stack. + * The [type] must be one of byte, short, char, int, long, float, or double + */ + record VMLoad(VMStorage storage, Class type) implements Move { + + @Override + public void verify(Deque> stack) { + stack.push(type()); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + stack.push(loadFunc.load(storage(), type())); + } + } + + sealed interface Dereference extends Binding { + long offset(); + Class type(); + } + + /** + * BUFFER_STORE([offset into memory region], [type], [width]) + * Pops a [type] from the operand stack, then pops a MemorySegment from the operand stack. + * Stores [width] bytes of the value contained in the [type] to [offset into memory region]. + * The [type] must be one of byte, short, char, int, long, float, or double + */ + record BufferStore(long offset, Class type, int byteWidth) implements Dereference { + + @Override + public void verify(Deque> stack) { + Class storeType = stack.pop(); + SharedUtils.checkType(storeType, type()); + Class segmentType = stack.pop(); + SharedUtils.checkType(segmentType, MemorySegment.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + Object value = stack.pop(); + MemorySegment writeAddress = (MemorySegment) stack.pop(); + if (SharedUtils.isPowerOfTwo(byteWidth())) { + // exact size match + SharedUtils.write(writeAddress, offset(), type(), value); + } else { + // non-exact match, need to do chunked load + long longValue = ((Number) value).longValue(); + // byteWidth is smaller than the width of 'type', so it will always be < 8 here + int remaining = byteWidth(); + int chunkOffset = 0; + do { + int chunkSize = Integer.highestOneBit(remaining); // next power of 2, in bytes + long writeOffset = offset() + SharedUtils.pickChunkOffset(chunkOffset, byteWidth(), chunkSize); + int shiftAmount = chunkOffset * Byte.SIZE; + switch (chunkSize) { + case 4 -> { + int writeChunk = (int) (((0xFFFF_FFFFL << shiftAmount) & longValue) >>> shiftAmount); + writeAddress.set(JAVA_INT_UNALIGNED, writeOffset, writeChunk); + } + case 2 -> { + short writeChunk = (short) (((0xFFFFL << shiftAmount) & longValue) >>> shiftAmount); + writeAddress.set(JAVA_SHORT_UNALIGNED, writeOffset, writeChunk); + } + case 1 -> { + byte writeChunk = (byte) (((0xFFL << shiftAmount) & longValue) >>> shiftAmount); + writeAddress.set(JAVA_BYTE, writeOffset, writeChunk); + } + default -> + throw new IllegalStateException("Unexpected chunk size for chunked write: " + chunkSize); + } + remaining -= chunkSize; + chunkOffset += chunkSize; + } while (remaining != 0); + } + } + } + + /** + * BUFFER_LOAD([offset into memory region], [type], [width]) + * Pops a MemorySegment from the operand stack, + * and then loads [width] bytes from it at [offset into memory region], into a [type]. + * The [type] must be one of byte, short, char, int, long, float, or double + */ + record BufferLoad(long offset, Class type, int byteWidth) implements Dereference { + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + SharedUtils.checkType(actualType, MemorySegment.class); + Class newType = type(); + stack.push(newType); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + MemorySegment readAddress = (MemorySegment) stack.pop(); + if (SharedUtils.isPowerOfTwo(byteWidth())) { + // exact size match + stack.push(SharedUtils.read(readAddress, offset(), type())); + } else { + // non-exact match, need to do chunked load + long result = 0; + // byteWidth is smaller than the width of 'type', so it will always be < 8 here + int remaining = byteWidth(); + int chunkOffset = 0; + do { + int chunkSize = Integer.highestOneBit(remaining); // next power of 2 + long readOffset = offset() + SharedUtils.pickChunkOffset(chunkOffset, byteWidth(), chunkSize); + long readChunk = switch (chunkSize) { + case 4 -> Integer.toUnsignedLong(readAddress.get(JAVA_INT_UNALIGNED, readOffset)); + case 2 -> Short.toUnsignedLong(readAddress.get(JAVA_SHORT_UNALIGNED, readOffset)); + case 1 -> Byte.toUnsignedLong(readAddress.get(JAVA_BYTE, readOffset)); + default -> + throw new IllegalStateException("Unexpected chunk size for chunked write: " + chunkSize); + }; + result |= readChunk << (chunkOffset * Byte.SIZE); + remaining -= chunkSize; + chunkOffset += chunkSize; + } while (remaining != 0); + + if (type() == int.class) { // 3 byte write + stack.push((int) result); + } else if (type() == long.class) { // 5, 6, 7 byte write + stack.push(result); + } else { + throw new IllegalStateException("Unexpected type for chunked load: " + type()); + } + } + } + } + + /** + * COPY([size], [alignment]) + * Creates a new MemorySegment with the given [size] and [alignment], + * and copies contents from a MemorySegment popped from the top of the operand stack into this new buffer, + * and pushes the new buffer onto the operand stack + */ + record Copy(long size, long alignment) implements Binding { + private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment, SegmentAllocator allocator) { + return allocator.allocate(size, alignment) + .copyFrom(operand.asSlice(0, size)); + } + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + SharedUtils.checkType(actualType, MemorySegment.class); + stack.push(MemorySegment.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + MemorySegment operand = (MemorySegment) stack.pop(); + MemorySegment copy = copyBuffer(operand, size, alignment, allocator); + stack.push(copy); + } + } + + /** + * ALLOCATE([size], [alignment]) + * Creates a new MemorySegment with the give [size] and [alignment], and pushes it onto the operand stack. + */ + record Allocate(long size, long alignment) implements Binding { + private static MemorySegment allocateBuffer(long size, long alignment, SegmentAllocator allocator) { + return allocator.allocate(size, alignment); + } + + @Override + public void verify(Deque> stack) { + stack.push(MemorySegment.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + stack.push(allocateBuffer(size, alignment, allocator)); + } + } + + /** + * SEGMENT_BASE() + * Pops a MemorySegment from the stack, retrieves the heap base object from it, or null if there is none + * (See: AbstractMemorySegmentImpl::unsafeGetBase), and pushes the result onto the operand stack. + */ + record SegmentBase() implements Binding { + static final SegmentBase INSTANCE = new SegmentBase(); + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + SharedUtils.checkType(actualType, MemorySegment.class); + stack.push(Object.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + stack.push(((AbstractMemorySegmentImpl)stack.pop()).unsafeGetBase()); + } + } + + /** + * SEGMENT_OFFSET([allowHeap]) + * Pops a MemorySegment from the stack, retrieves the offset from it, + * (See: AbstractMemorySegmentImpl::unsafeGetOffset), and pushes the result onto the operand stack. + * Note that for heap segments, the offset is a virtual address into the heap base object. + * If [allowHeap] is 'false' an exception will be thrown for heap segments (See SharedUtils::checkNative). + */ + record SegmentOffset(boolean allowHeap) implements Binding { + static final SegmentOffset INSTANCE_NO_ALLOW_HEAP = new SegmentOffset(false); + static final SegmentOffset INSTANCE_ALLOW_HEAP = new SegmentOffset(true); + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + SharedUtils.checkType(actualType, MemorySegment.class); + stack.push(long.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + MemorySegment operand = (MemorySegment) stack.pop(); + if (!allowHeap) { + SharedUtils.checkNative(operand); + } + stack.push(((AbstractMemorySegmentImpl)operand).unsafeGetOffset()); + } + } + + /** + * BOX_ADDRESS() + * Pops a 'long' from the operand stack, converts it to a 'MemorySegment', with the given size and memory scope + * (either the context scope, or the global scope), and pushes that onto the operand stack. + */ + record BoxAddress(long size, long align, boolean needsScope) implements Binding { + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + SharedUtils.checkType(actualType, long.class); + stack.push(MemorySegment.class); + } + + @Override + @SuppressWarnings("restricted") + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + MemorySegment segment = Utils.longToAddress((long) stack.pop(), size, align); + if (needsScope) { + segment = segment.reinterpret((Arena) allocator, null); // restricted + } + stack.push(segment); + } + } + + /** + * DUP() + * Duplicates the value on the top of the operand stack (without popping it!), + * and pushes the duplicate onto the operand stack + */ + record Dup() implements Binding { + static final Dup INSTANCE = new Dup(); + + @Override + public void verify(Deque> stack) { + stack.push(stack.peekLast()); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + stack.push(stack.peekLast()); + } + } + + /** + * ShiftLeft([shiftAmount]) + * Shifts the Bytes on the top of the operand stack (64 bit unsigned). + * Shifts left by the given number of Bytes. + */ + record ShiftLeft(int shiftAmount) implements Binding { + + @Override + public void verify(Deque> stack) { + Class last = stack.pop(); + SharedUtils.checkType(last, long.class); + stack.push(long.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + long l = (long) stack.pop(); + l <<= (shiftAmount * Byte.SIZE); + stack.push(l); + } + } + + /** + * ShiftRight([shiftAmount]) + * Shifts the Bytes on the top of the operand stack (64 bit unsigned). + * Shifts right by the given number of Bytes. + */ + record ShiftRight(int shiftAmount) implements Binding { + + @Override + public void verify(Deque> stack) { + Class last = stack.pop(); + SharedUtils.checkType(last, long.class); + stack.push(long.class); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + long l = (long) stack.pop(); + l >>>= (shiftAmount * Byte.SIZE); + stack.push(l); + } + } + + /** + * CAST([fromType], [toType]) + * Pop a [fromType] from the stack, convert it to [toType], and push the resulting + * value onto the stack. + * + */ + enum Cast implements Binding { + INT_TO_BOOLEAN(int.class, boolean.class) { + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + // implement least significant byte non-zero test + int arg = (int) stack.pop(); + boolean result = Utils.byteToBoolean((byte) arg); + stack.push(result); + } + }, + INT_TO_BYTE(int.class, byte.class), + INT_TO_CHAR(int.class, char.class), + INT_TO_SHORT(int.class, short.class), + INT_TO_LONG(int.class, long.class), + + BOOLEAN_TO_INT(boolean.class, int.class), + BYTE_TO_INT(byte.class, int.class), + CHAR_TO_INT(char.class, int.class), + SHORT_TO_INT(short.class, int.class), + LONG_TO_INT(long.class, int.class), + + LONG_TO_BYTE(long.class, byte.class), + LONG_TO_SHORT(long.class, short.class), + LONG_TO_CHAR(long.class, char.class), + + BYTE_TO_LONG(byte.class, long.class), + SHORT_TO_LONG(short.class, long.class), + CHAR_TO_LONG(char.class, long.class); + + private final Class fromType; + private final Class toType; + + Cast(Class fromType, Class toType) { + this.fromType = fromType; + this.toType = toType; + } + + public Class fromType() { + return fromType; + } + + public Class toType() { + return toType; + } + + @Override + public void verify(Deque> stack) { + Class actualType = stack.pop(); + SharedUtils.checkType(actualType, fromType); + stack.push(toType); + } + + @Override + public void interpret(Deque stack, StoreFunc storeFunc, + LoadFunc loadFunc, SegmentAllocator allocator) { + Object arg = stack.pop(); + MethodHandle converter = MethodHandles.explicitCastArguments(MethodHandles.identity(toType), + MethodType.methodType(toType, fromType)); + try { + Object result = converter.invoke(arg); + stack.push(result); + } catch (Throwable e) { + throw new InternalError(e); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter$LoadFunc.class b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter$LoadFunc.class new file mode 100644 index 00000000..134008b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter$LoadFunc.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter$StoreFunc.class b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter$StoreFunc.class new file mode 100644 index 00000000..d018f417 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter$StoreFunc.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter.class b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter.class new file mode 100644 index 00000000..17be4ff9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter.java b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter.java new file mode 100644 index 00000000..9434e70a --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/BindingInterpreter.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import java.lang.foreign.SegmentAllocator; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +public class BindingInterpreter { + + static void unbox(Object arg, List bindings, StoreFunc storeFunc, SegmentAllocator allocator) { + Deque stack = new LinkedList<>(); // Use LinkedList as a null-friendly Deque for null segment bases + + stack.push(arg); + for (Binding b : bindings) { + b.interpret(stack, storeFunc, null, allocator); + } + } + + static Object box(List bindings, LoadFunc loadFunc, SegmentAllocator allocator) { + Deque stack = new ArrayDeque<>(); + for (Binding b : bindings) { + b.interpret(stack, null, loadFunc, allocator); + } + return stack.pop(); + } + + @FunctionalInterface + public interface StoreFunc { + void store(VMStorage storage, Object o); + } + + @FunctionalInterface + public interface LoadFunc { + Object load(VMStorage storage, Class type); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer$1.class b/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer$1.class new file mode 100644 index 00000000..37dc3602 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer.class b/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer.class new file mode 100644 index 00000000..3dec97e9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer.java b/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer.java new file mode 100644 index 00000000..7f5ef54b --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/BindingSpecializer.java @@ -0,0 +1,996 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import java.lang.classfile.ClassFile; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.Label; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import jdk.internal.foreign.AbstractMemorySegmentImpl; +import jdk.internal.foreign.MemorySessionImpl; +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.Binding.Allocate; +import jdk.internal.foreign.abi.Binding.BoxAddress; +import jdk.internal.foreign.abi.Binding.BufferLoad; +import jdk.internal.foreign.abi.Binding.BufferStore; +import jdk.internal.foreign.abi.Binding.Cast; +import jdk.internal.foreign.abi.Binding.Copy; +import jdk.internal.foreign.abi.Binding.Dup; +import jdk.internal.foreign.abi.Binding.SegmentBase; +import jdk.internal.foreign.abi.Binding.SegmentOffset; +import jdk.internal.foreign.abi.Binding.ShiftLeft; +import jdk.internal.foreign.abi.Binding.ShiftRight; +import jdk.internal.foreign.abi.Binding.VMLoad; +import jdk.internal.foreign.abi.Binding.VMStore; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetPropertyAction; + +import java.io.IOException; +import java.lang.constant.ClassDesc; +import java.lang.constant.Constable; +import java.lang.constant.ConstantDesc; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.ClassFileFormatVersion; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; + +import static java.lang.constant.ConstantDescs.*; +import static java.lang.classfile.ClassFile.*; +import static java.lang.classfile.TypeKind.*; + +public class BindingSpecializer { + private static final String DUMP_CLASSES_DIR + = GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.abi.Specializer.DUMP_CLASSES_DIR"); + private static final boolean PERFORM_VERIFICATION + = GetBooleanAction.privilegedGetProperty("jdk.internal.foreign.abi.Specializer.PERFORM_VERIFICATION"); + + // Bunch of helper constants + private static final int CLASSFILE_VERSION = ClassFileFormatVersion.latest().major(); + + private static final ClassDesc CD_Arena = desc(Arena.class); + private static final ClassDesc CD_MemorySegment = desc(MemorySegment.class); + private static final ClassDesc CD_MemorySegment_Scope = desc(MemorySegment.Scope.class); + private static final ClassDesc CD_SharedUtils = desc(SharedUtils.class); + private static final ClassDesc CD_AbstractMemorySegmentImpl = desc(AbstractMemorySegmentImpl.class); + private static final ClassDesc CD_MemorySessionImpl = desc(MemorySessionImpl.class); + private static final ClassDesc CD_Utils = desc(Utils.class); + private static final ClassDesc CD_SegmentAllocator = desc(SegmentAllocator.class); + private static final ClassDesc CD_ValueLayout = desc(ValueLayout.class); + private static final ClassDesc CD_ValueLayout_OfBoolean = desc(ValueLayout.OfBoolean.class); + private static final ClassDesc CD_ValueLayout_OfByte = desc(ValueLayout.OfByte.class); + private static final ClassDesc CD_ValueLayout_OfShort = desc(ValueLayout.OfShort.class); + private static final ClassDesc CD_ValueLayout_OfChar = desc(ValueLayout.OfChar.class); + private static final ClassDesc CD_ValueLayout_OfInt = desc(ValueLayout.OfInt.class); + private static final ClassDesc CD_ValueLayout_OfLong = desc(ValueLayout.OfLong.class); + private static final ClassDesc CD_ValueLayout_OfFloat = desc(ValueLayout.OfFloat.class); + private static final ClassDesc CD_ValueLayout_OfDouble = desc(ValueLayout.OfDouble.class); + private static final ClassDesc CD_AddressLayout = desc(AddressLayout.class); + + private static final MethodTypeDesc MTD_NEW_BOUNDED_ARENA = MethodTypeDesc.of(CD_Arena, CD_long); + private static final MethodTypeDesc MTD_NEW_EMPTY_ARENA = MethodTypeDesc.of(CD_Arena); + private static final MethodTypeDesc MTD_SCOPE = MethodTypeDesc.of(CD_MemorySegment_Scope); + private static final MethodTypeDesc MTD_SESSION_IMPL = MethodTypeDesc.of(CD_MemorySessionImpl); + private static final MethodTypeDesc MTD_CLOSE = MTD_void; + private static final MethodTypeDesc MTD_CHECK_NATIVE = MethodTypeDesc.of(CD_void, CD_MemorySegment); + private static final MethodTypeDesc MTD_UNSAFE_GET_BASE = MethodTypeDesc.of(CD_Object); + private static final MethodTypeDesc MTD_UNSAFE_GET_OFFSET = MethodTypeDesc.of(CD_long); + private static final MethodTypeDesc MTD_COPY = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_long, CD_MemorySegment, CD_long, CD_long); + private static final MethodTypeDesc MTD_LONG_TO_ADDRESS_NO_SCOPE = MethodTypeDesc.of(CD_MemorySegment, CD_long, CD_long, CD_long); + private static final MethodTypeDesc MTD_LONG_TO_ADDRESS_SCOPE = MethodTypeDesc.of(CD_MemorySegment, CD_long, CD_long, CD_long, CD_MemorySessionImpl); + private static final MethodTypeDesc MTD_ALLOCATE = MethodTypeDesc.of(CD_MemorySegment, CD_long, CD_long); + private static final MethodTypeDesc MTD_HANDLE_UNCAUGHT_EXCEPTION = MethodTypeDesc.of(CD_void, CD_Throwable); + private static final MethodTypeDesc MTD_RELEASE0 = MTD_void; + private static final MethodTypeDesc MTD_ACQUIRE0 = MTD_void; + private static final MethodTypeDesc MTD_INTEGER_TO_UNSIGNED_LONG = MethodTypeDesc.of(CD_long, CD_int); + private static final MethodTypeDesc MTD_SHORT_TO_UNSIGNED_LONG = MethodTypeDesc.of(CD_long, CD_short); + private static final MethodTypeDesc MTD_BYTE_TO_UNSIGNED_LONG = MethodTypeDesc.of(CD_long, CD_byte); + private static final MethodTypeDesc MTD_BYTE_TO_BOOLEAN = MethodTypeDesc.of(CD_boolean, CD_byte); + + private static final ConstantDesc CLASS_DATA_DESC = DynamicConstantDesc.of(BSM_CLASS_DATA); + + private static final String CLASS_NAME_DOWNCALL = "jdk/internal/foreign/abi/DowncallStub"; + private static final String CLASS_NAME_UPCALL = "jdk/internal/foreign/abi/UpcallStub"; + private static final String METHOD_NAME = "invoke"; + + // Instance fields start here + private final CodeBuilder cb; + private final MethodType callerMethodType; + private final CallingSequence callingSequence; + private final ABIDescriptor abi; + private final MethodType leafType; + + private int[] leafArgSlots; + private int[] scopeSlots; + private int curScopeLocalIdx = -1; + private int returnAllocatorIdx = -1; + private int contextIdx = -1; + private int returnBufferIdx = -1; + private int retValIdx = -1; + private Deque> typeStack; + private List> leafArgTypes; + private int paramIndex; + private long retBufOffset; // for needsReturnBuffer + + private BindingSpecializer(CodeBuilder cb, MethodType callerMethodType, + CallingSequence callingSequence, ABIDescriptor abi, MethodType leafType) { + this.cb = cb; + this.callerMethodType = callerMethodType; + this.callingSequence = callingSequence; + this.abi = abi; + this.leafType = leafType; + } + + static MethodHandle specializeDowncall(MethodHandle leafHandle, CallingSequence callingSequence, ABIDescriptor abi) { + MethodType callerMethodType = callingSequence.callerMethodType(); + if (callingSequence.needsReturnBuffer()) { + callerMethodType = callerMethodType.dropParameterTypes(0, 1); // Return buffer does not appear in the parameter list + } + callerMethodType = callerMethodType.insertParameterTypes(0, SegmentAllocator.class); + + byte[] bytes = specializeHelper(leafHandle.type(), callerMethodType, callingSequence, abi); + + try { + MethodHandles.Lookup definedClassLookup = MethodHandles.lookup() + .defineHiddenClassWithClassData(bytes, leafHandle, false); + return definedClassLookup.findStatic(definedClassLookup.lookupClass(), METHOD_NAME, callerMethodType); + } catch (IllegalAccessException | NoSuchMethodException e) { + throw new InternalError("Should not happen", e); + } + } + + static MethodHandle specializeUpcall(MethodType targetType, CallingSequence callingSequence, ABIDescriptor abi) { + MethodType callerMethodType = callingSequence.callerMethodType(); + callerMethodType = callerMethodType.insertParameterTypes(0, MethodHandle.class); // target + + byte[] bytes = specializeHelper(targetType, callerMethodType, callingSequence, abi); + + try { + // For upcalls, we must initialize the class since the upcall stubs don't have a clinit barrier, + // and the slow path in the c2i adapter we end up calling can not handle the particular code shape + // where the caller is an upcall stub. + MethodHandles.Lookup defineClassLookup = MethodHandles.lookup().defineHiddenClass(bytes, true); + return defineClassLookup.findStatic(defineClassLookup.lookupClass(), METHOD_NAME, callerMethodType); + } catch (IllegalAccessException | NoSuchMethodException e) { + throw new InternalError("Should not happen", e); + } + } + + private static byte[] specializeHelper(MethodType leafType, MethodType callerMethodType, + CallingSequence callingSequence, ABIDescriptor abi) { + String className = callingSequence.forDowncall() ? CLASS_NAME_DOWNCALL : CLASS_NAME_UPCALL; + byte[] bytes = ClassFile.of().build(ClassDesc.ofInternalName(className), clb -> { + clb.withFlags(ACC_PUBLIC + ACC_FINAL + ACC_SUPER); + clb.withSuperclass(CD_Object); + clb.withVersion(CLASSFILE_VERSION, 0); + + clb.withMethodBody(METHOD_NAME, desc(callerMethodType), ACC_PUBLIC | ACC_STATIC, + cb -> new BindingSpecializer(cb, callerMethodType, callingSequence, abi, leafType).specialize()); + }); + + if (DUMP_CLASSES_DIR != null) { + String fileName = className + escapeForFileName(callingSequence.functionDesc().toString()) + ".class"; + Path dumpPath = Path.of(DUMP_CLASSES_DIR).resolve(fileName); + try { + Files.createDirectories(dumpPath.getParent()); + Files.write(dumpPath, bytes); + } catch (IOException e) { + throw new InternalError(e); + } + } + + if (PERFORM_VERIFICATION) { + List errors = ClassFile.of().verify(bytes); + if (!errors.isEmpty()) { + errors.forEach(System.err::println); + throw new IllegalStateException("Verification error(s)"); + } + } + + return bytes; + } + + private static String escapeForFileName(String str) { + StringBuilder sb = new StringBuilder(str.length()); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + sb.append(switch (c) { + case ' ' -> '_'; + case '[', '<' -> '{'; + case ']', '>' -> '}'; + case '/', '\\', ':', '*', '?', '"', '|' -> '!'; // illegal in Windows file names. + default -> c; + }); + } + return sb.toString(); + } + + // binding operand stack manipulation + + private void pushType(Class type) { + typeStack.push(type); + } + + private Class popType(Class expected) { + Class found = typeStack.pop(); + if (!expected.equals(found)) { + throw new IllegalStateException( + String.format("Invalid type on binding operand stack; found %s - expected %s", + found.descriptorString(), expected.descriptorString())); + } + return found; + } + + // specialization + + private void specialize() { + // slots that store the output arguments (passed to the leaf handle) + leafArgSlots = new int[leafType.parameterCount()]; + for (int i = 0; i < leafType.parameterCount(); i++) { + leafArgSlots[i] = cb.allocateLocal(TypeKind.from(leafType.parameterType(i))); + } + + // allocator passed to us for allocating the return MS (downcalls only) + if (callingSequence.forDowncall()) { + returnAllocatorIdx = 0; // first param + + // for downcalls we also acquire/release scoped parameters before/after the call + // create a bunch of locals here to keep track of their scopes (to release later) + int[] initialScopeSlots = new int[callerMethodType.parameterCount()]; + int numScopes = 0; + for (int i = 0; i < callerMethodType.parameterCount(); i++) { + if (shouldAcquire(i)) { + int scopeLocal = cb.allocateLocal(ReferenceType); + initialScopeSlots[numScopes++] = scopeLocal; + cb.loadConstant(null); + cb.storeLocal(ReferenceType, scopeLocal); // need to initialize all scope locals here in case an exception occurs + } + } + scopeSlots = Arrays.copyOf(initialScopeSlots, numScopes); // fit to size + curScopeLocalIdx = 0; // used from emitGetInput + } + + // create a Binding.Context for this call + if (callingSequence.allocationSize() != 0) { + cb.loadConstant(callingSequence.allocationSize()); + cb.invokestatic(CD_SharedUtils, "newBoundedArena", MTD_NEW_BOUNDED_ARENA); + } else if (callingSequence.forUpcall() && needsSession()) { + cb.invokestatic(CD_SharedUtils, "newEmptyArena", MTD_NEW_EMPTY_ARENA); + } else { + cb.getstatic(CD_SharedUtils, "DUMMY_ARENA", CD_Arena); + } + contextIdx = cb.allocateLocal(ReferenceType); + cb.storeLocal(ReferenceType, contextIdx); + + // in case the call needs a return buffer, allocate it here. + // for upcalls the VM wrapper stub allocates the buffer. + if (callingSequence.needsReturnBuffer() && callingSequence.forDowncall()) { + emitLoadInternalAllocator(); + emitAllocateCall(callingSequence.returnBufferSize(), 1); + returnBufferIdx = cb.allocateLocal(ReferenceType); + cb.storeLocal(ReferenceType, returnBufferIdx); + } + + Label tryStart = cb.newLabel(); + Label tryEnd = cb.newLabel(); + Label catchStart = cb.newLabel(); + + cb.labelBinding(tryStart); + + // stack to keep track of types on the bytecode stack between bindings. + // this is needed to e.g. emit the right DUP instruction, + // but also used for type checking. + typeStack = new ArrayDeque<>(); + // leaf arg types are the types of the args passed to the leaf handle. + // these are collected from VM_STORE instructions for downcalls, and + // recipe outputs for upcalls (see uses emitSetOutput for both) + leafArgTypes = new ArrayList<>(); + paramIndex = 1; // +1 to skip SegmentAllocator or MethodHandle + for (int i = 0; i < callingSequence.argumentBindingsCount(); i++) { + if (callingSequence.forDowncall()) { + // for downcalls, recipes have an input value, which we set up here + if (callingSequence.needsReturnBuffer() && i == 0) { + assert returnBufferIdx != -1; + cb.loadLocal(ReferenceType, returnBufferIdx); + pushType(MemorySegment.class); + } else { + emitGetInput(); + } + } + + // emit code according to binding recipe + doBindings(callingSequence.argumentBindings(i)); + + if (callingSequence.forUpcall()) { + // for upcalls, recipes have a result, which we handle here + if (callingSequence.needsReturnBuffer() && i == 0) { + // return buffer ptr is wrapped in a MemorySegment above, but not passed to the leaf handle + popType(MemorySegment.class); + returnBufferIdx = cb.allocateLocal(ReferenceType); + cb.storeLocal(ReferenceType, returnBufferIdx); + } else { + // for upcalls the recipe result is an argument to the leaf handle + emitSetOutput(typeStack.pop()); + } + } + assert typeStack.isEmpty(); + } + + assert leafArgTypes.equals(leafType.parameterList()); + + // load the leaf MethodHandle + if (callingSequence.forDowncall()) { + cb.loadConstant(CLASS_DATA_DESC); + } else { + cb.loadLocal(ReferenceType, 0); // load target arg + } + cb.checkcast(CD_MethodHandle); + // load all the leaf args + for (int i = 0; i < leafArgSlots.length; i++) { + cb.loadLocal(TypeKind.from(leafArgTypes.get(i)), leafArgSlots[i]); + } + // call leaf MH + cb.invokevirtual(CD_MethodHandle, "invokeExact", desc(leafType)); + + // for downcalls, store the result of the leaf handle call away, until + // it is requested by a VM_LOAD in the return recipe. + if (callingSequence.forDowncall() && leafType.returnType() != void.class) { + emitSaveReturnValue(leafType.returnType()); + } + // for upcalls we leave the return value on the stack to be picked up + // as an input of the return recipe. + + // return value processing + if (callingSequence.hasReturnBindings()) { + if (callingSequence.forUpcall()) { + pushType(leafType.returnType()); + } + + retBufOffset = 0; // offset for reading from return buffer + doBindings(callingSequence.returnBindings()); + + if (callingSequence.forUpcall() && !callingSequence.needsReturnBuffer()) { + // was VM_STOREd somewhere in the bindings + emitRestoreReturnValue(callerMethodType.returnType()); + } + cb.labelBinding(tryEnd); + // finally + emitCleanup(); + + if (callerMethodType.returnType() == void.class) { + // The case for upcalls that return by return buffer + assert typeStack.isEmpty(); + cb.return_(); + } else { + popType(callerMethodType.returnType()); + assert typeStack.isEmpty(); + cb.return_(TypeKind.from(callerMethodType.returnType())); + } + } else { + assert callerMethodType.returnType() == void.class; + assert typeStack.isEmpty(); + cb.labelBinding(tryEnd); + // finally + emitCleanup(); + cb.return_(); + } + + cb.labelBinding(catchStart); + // finally + emitCleanup(); + if (callingSequence.forDowncall()) { + cb.athrow(); + } else { + cb.invokestatic(CD_SharedUtils, "handleUncaughtException", MTD_HANDLE_UNCAUGHT_EXCEPTION); + if (callerMethodType.returnType() != void.class) { + TypeKind returnTypeKind = TypeKind.from(callerMethodType.returnType()); + emitConstZero(returnTypeKind); + cb.return_(returnTypeKind); + } else { + cb.return_(); + } + } + + cb.exceptionCatchAll(tryStart, tryEnd, catchStart); + } + + private boolean needsSession() { + return callingSequence.argumentBindings() + .filter(BoxAddress.class::isInstance) + .map(BoxAddress.class::cast) + .anyMatch(BoxAddress::needsScope); + } + + private boolean shouldAcquire(int paramIndex) { + if (!callingSequence.forDowncall() || // we only acquire in downcalls + paramIndex == 0) { // the first parameter in a downcall is SegmentAllocator + return false; + } + + // if call needs return buffer, the descriptor has an extra leading layout + int offset = callingSequence.needsReturnBuffer() ? 0 : 1; + MemoryLayout paramLayout = callingSequence.functionDesc() + .argumentLayouts() + .get(paramIndex - offset); + + // is this an address layout? + return paramLayout instanceof AddressLayout; + } + + private void emitCleanup() { + emitCloseContext(); + if (callingSequence.forDowncall()) { + emitReleaseScopes(); + } + } + + private void doBindings(List bindings) { + for (Binding binding : bindings) { + switch (binding) { + case VMStore vmStore -> emitVMStore(vmStore); + case VMLoad vmLoad -> emitVMLoad(vmLoad); + case BufferStore bufferStore -> emitBufferStore(bufferStore); + case BufferLoad bufferLoad -> emitBufferLoad(bufferLoad); + case Copy copy -> emitCopyBuffer(copy); + case Allocate allocate -> emitAllocBuffer(allocate); + case BoxAddress boxAddress -> emitBoxAddress(boxAddress); + case SegmentBase unused -> emitSegmentBase(); + case SegmentOffset segmentOffset -> emitSegmentOffset(segmentOffset); + case Dup unused -> emitDupBinding(); + case ShiftLeft shiftLeft -> emitShiftLeft(shiftLeft); + case ShiftRight shiftRight -> emitShiftRight(shiftRight); + case Cast cast -> emitCast(cast); + } + } + } + + private void emitSetOutput(Class storeType) { + cb.storeLocal(TypeKind.from(storeType), leafArgSlots[leafArgTypes.size()]); + leafArgTypes.add(storeType); + } + + private void emitGetInput() { + Class highLevelType = callerMethodType.parameterType(paramIndex); + cb.loadLocal(TypeKind.from(highLevelType), cb.parameterSlot(paramIndex)); + + if (shouldAcquire(paramIndex)) { + cb.dup(); + emitAcquireScope(); + } + + pushType(highLevelType); + paramIndex++; + } + + private void emitAcquireScope() { + cb.checkcast(CD_AbstractMemorySegmentImpl); + cb.invokevirtual(CD_AbstractMemorySegmentImpl, "sessionImpl", MTD_SESSION_IMPL); + Label skipAcquire = cb.newLabel(); + Label end = cb.newLabel(); + + // start with 1 scope to maybe acquire on the stack + assert curScopeLocalIdx != -1; + boolean hasOtherScopes = curScopeLocalIdx != 0; + for (int i = 0; i < curScopeLocalIdx; i++) { + cb.dup(); // dup for comparison + cb.loadLocal(ReferenceType, scopeSlots[i]); + cb.if_acmpeq(skipAcquire); + } + + // 1 scope to acquire on the stack + cb.dup(); + int nextScopeLocal = scopeSlots[curScopeLocalIdx++]; + // call acquire first here. So that if it fails, we don't call release + cb.invokevirtual(CD_MemorySessionImpl, "acquire0", MTD_ACQUIRE0); // call acquire on the other + cb.storeLocal(ReferenceType, nextScopeLocal); // store off one to release later + + if (hasOtherScopes) { // avoid ASM generating a bunch of nops for the dead code + cb.goto_(end); + + cb.labelBinding(skipAcquire); + cb.pop(); // drop scope + } + + cb.labelBinding(end); + } + + private void emitReleaseScopes() { + for (int scopeLocal : scopeSlots) { + cb.loadLocal(ReferenceType, scopeLocal); + cb.ifThen(Opcode.IFNONNULL, ifCb -> { + ifCb.loadLocal(ReferenceType, scopeLocal); + ifCb.invokevirtual(CD_MemorySessionImpl, "release0", MTD_RELEASE0); + }); + } + } + + private void emitSaveReturnValue(Class storeType) { + TypeKind typeKind = TypeKind.from(storeType); + retValIdx = cb.allocateLocal(typeKind); + cb.storeLocal(typeKind, retValIdx); + } + + private void emitRestoreReturnValue(Class loadType) { + assert retValIdx != -1; + cb.loadLocal(TypeKind.from(loadType), retValIdx); + pushType(loadType); + } + + private void emitLoadInternalSession() { + assert contextIdx != -1; + cb.loadLocal(ReferenceType, contextIdx); + cb.checkcast(CD_Arena); + cb.invokeinterface(CD_Arena, "scope", MTD_SCOPE); + cb.checkcast(CD_MemorySessionImpl); + } + + private void emitLoadInternalAllocator() { + assert contextIdx != -1; + cb.loadLocal(ReferenceType, contextIdx); + } + + private void emitCloseContext() { + assert contextIdx != -1; + cb.loadLocal(ReferenceType, contextIdx); + cb.checkcast(CD_Arena); + cb.invokeinterface(CD_Arena, "close", MTD_CLOSE); + } + + private void emitBoxAddress(BoxAddress boxAddress) { + popType(long.class); + cb.loadConstant(boxAddress.size()); + cb.loadConstant(boxAddress.align()); + if (needsSession()) { + emitLoadInternalSession(); + cb.invokestatic(CD_Utils, "longToAddress", MTD_LONG_TO_ADDRESS_SCOPE); + } else { + cb.invokestatic(CD_Utils, "longToAddress", MTD_LONG_TO_ADDRESS_NO_SCOPE); + } + pushType(MemorySegment.class); + } + + private void emitAllocBuffer(Allocate binding) { + if (callingSequence.forDowncall()) { + assert returnAllocatorIdx != -1; + cb.loadLocal(ReferenceType, returnAllocatorIdx); + } else { + emitLoadInternalAllocator(); + } + emitAllocateCall(binding.size(), binding.alignment()); + pushType(MemorySegment.class); + } + + private void emitBufferStore(BufferStore bufferStore) { + Class storeType = bufferStore.type(); + TypeKind storeTypeKind = TypeKind.from(storeType); + long offset = bufferStore.offset(); + int byteWidth = bufferStore.byteWidth(); + + popType(storeType); + popType(MemorySegment.class); + + if (SharedUtils.isPowerOfTwo(byteWidth)) { + int valueIdx = cb.allocateLocal(storeTypeKind); + cb.storeLocal(storeTypeKind, valueIdx); + + ClassDesc valueLayoutType = emitLoadLayoutConstant(storeType); + cb.loadConstant(offset); + cb.loadLocal(storeTypeKind, valueIdx); + MethodTypeDesc descriptor = MethodTypeDesc.of(CD_void, valueLayoutType, CD_long, desc(storeType)); + cb.invokeinterface(CD_MemorySegment, "set", descriptor); + } else { + // long longValue = ((Number) value).longValue(); + if (storeType == int.class) { + cb.i2l(); + } else { + assert storeType == long.class; // chunking only for int and long + } + int longValueIdx = cb.allocateLocal(LongType); + cb.storeLocal(LongType, longValueIdx); + int writeAddrIdx = cb.allocateLocal(ReferenceType); + cb.storeLocal(ReferenceType, writeAddrIdx); + + int remaining = byteWidth; + int chunkOffset = 0; + do { + int chunkSize = Integer.highestOneBit(remaining); // next power of 2, in bytes + Class chunkStoreType; + long mask; + switch (chunkSize) { + case Integer.BYTES -> { + chunkStoreType = int.class; + mask = 0xFFFF_FFFFL; + } + case Short.BYTES -> { + chunkStoreType = short.class; + mask = 0xFFFFL; + } + case Byte.BYTES -> { + chunkStoreType = byte.class; + mask = 0xFFL; + } + default -> + throw new IllegalStateException("Unexpected chunk size for chunked write: " + chunkSize); + } + //int writeChunk = (int) (((0xFFFF_FFFFL << shiftAmount) & longValue) >>> shiftAmount); + int shiftAmount = chunkOffset * Byte.SIZE; + mask = mask << shiftAmount; + cb.loadLocal(LongType, longValueIdx); + cb.loadConstant(mask); + cb.land(); + if (shiftAmount != 0) { + cb.loadConstant(shiftAmount); + cb.lushr(); + } + cb.l2i(); + TypeKind chunkStoreTypeKind = TypeKind.from(chunkStoreType); + int chunkIdx = cb.allocateLocal(chunkStoreTypeKind); + cb.storeLocal(chunkStoreTypeKind, chunkIdx); + // chunk done, now write it + + //writeAddress.set(JAVA_SHORT_UNALIGNED, offset, writeChunk); + cb.loadLocal(ReferenceType, writeAddrIdx); + ClassDesc valueLayoutType = emitLoadLayoutConstant(chunkStoreType); + long writeOffset = offset + SharedUtils.pickChunkOffset(chunkOffset, byteWidth, chunkSize); + cb.loadConstant(writeOffset); + cb.loadLocal(chunkStoreTypeKind, chunkIdx); + MethodTypeDesc descriptor = MethodTypeDesc.of(CD_void, valueLayoutType, CD_long, desc(chunkStoreType)); + cb.invokeinterface(CD_MemorySegment, "set", descriptor); + + remaining -= chunkSize; + chunkOffset += chunkSize; + } while (remaining != 0); + } + } + + // VM_STORE and VM_LOAD are emulated, which is different for down/upcalls + private void emitVMStore(VMStore vmStore) { + Class storeType = vmStore.type(); + TypeKind storeTypeKind = TypeKind.from(storeType); + popType(storeType); + + if (callingSequence.forDowncall()) { + // processing arg + emitSetOutput(storeType); + } else { + // processing return + if (!callingSequence.needsReturnBuffer()) { + emitSaveReturnValue(storeType); + } else { + int valueIdx = cb.allocateLocal(storeTypeKind); + cb.storeLocal(storeTypeKind, valueIdx); // store away the stored value, need it later + + assert returnBufferIdx != -1; + cb.loadLocal(ReferenceType, returnBufferIdx); + ClassDesc valueLayoutType = emitLoadLayoutConstant(storeType); + cb.loadConstant(retBufOffset); + cb.loadLocal(storeTypeKind, valueIdx); + MethodTypeDesc descriptor = MethodTypeDesc.of(CD_void, valueLayoutType, CD_long, desc(storeType)); + cb.invokeinterface(CD_MemorySegment, "set", descriptor); + retBufOffset += abi.arch.typeSize(vmStore.storage().type()); + } + } + } + + private void emitVMLoad(VMLoad vmLoad) { + Class loadType = vmLoad.type(); + + if (callingSequence.forDowncall()) { + // processing return + if (!callingSequence.needsReturnBuffer()) { + emitRestoreReturnValue(loadType); + } else { + assert returnBufferIdx != -1; + cb.loadLocal(ReferenceType, returnBufferIdx); + ClassDesc valueLayoutType = emitLoadLayoutConstant(loadType); + cb.loadConstant(retBufOffset); + MethodTypeDesc descriptor = MethodTypeDesc.of(desc(loadType), valueLayoutType, CD_long); + cb.invokeinterface(CD_MemorySegment, "get", descriptor); + retBufOffset += abi.arch.typeSize(vmLoad.storage().type()); + pushType(loadType); + } + } else { + // processing arg + emitGetInput(); + } + } + + private void emitDupBinding() { + Class dupType = typeStack.peek(); + emitDup(dupType); + pushType(dupType); + } + + private void emitShiftLeft(ShiftLeft shiftLeft) { + popType(long.class); + cb.loadConstant(shiftLeft.shiftAmount() * Byte.SIZE); + cb.lshl(); + pushType(long.class); + } + + private void emitShiftRight(ShiftRight shiftRight) { + popType(long.class); + cb.loadConstant(shiftRight.shiftAmount() * Byte.SIZE); + cb.lushr(); + pushType(long.class); + } + + private void emitCast(Cast cast) { + Class fromType = cast.fromType(); + Class toType = cast.toType(); + + popType(fromType); + switch (cast) { + case INT_TO_BOOLEAN -> { + // implement least significant byte non-zero test + + // select first byte + cb.loadConstant(0xFF); + cb.iand(); + + // convert to boolean + cb.invokestatic(CD_Utils, "byteToBoolean", MTD_BYTE_TO_BOOLEAN); + } + case INT_TO_BYTE -> cb.i2b(); + case INT_TO_CHAR -> cb.i2c(); + case INT_TO_SHORT -> cb.i2s(); + case BYTE_TO_LONG, CHAR_TO_LONG, SHORT_TO_LONG, INT_TO_LONG -> cb.i2l(); + case LONG_TO_BYTE -> { cb.l2i(); cb.i2b(); } + case LONG_TO_SHORT -> { cb.l2i(); cb.i2s(); } + case LONG_TO_CHAR -> { cb.l2i(); cb.i2c(); } + case LONG_TO_INT -> cb.l2i(); + case BOOLEAN_TO_INT, BYTE_TO_INT, CHAR_TO_INT, SHORT_TO_INT -> { + // no-op in bytecode + } + default -> throw new IllegalStateException("Unknown cast: " + cast); + } + pushType(toType); + } + + private void emitSegmentBase() { + popType(MemorySegment.class); + cb.checkcast(CD_AbstractMemorySegmentImpl); + cb.invokevirtual(CD_AbstractMemorySegmentImpl, "unsafeGetBase", MTD_UNSAFE_GET_BASE); + pushType(Object.class); + } + + private void emitSegmentOffset(SegmentOffset segmentOffset) { + popType(MemorySegment.class); + + if (!segmentOffset.allowHeap()) { + cb.dup(); + cb.invokestatic(CD_SharedUtils, "checkNative", MTD_CHECK_NATIVE); + } + cb.checkcast(CD_AbstractMemorySegmentImpl); + cb.invokevirtual(CD_AbstractMemorySegmentImpl, "unsafeGetOffset", MTD_UNSAFE_GET_OFFSET); + + pushType(long.class); + } + + private void emitBufferLoad(BufferLoad bufferLoad) { + Class loadType = bufferLoad.type(); + long offset = bufferLoad.offset(); + int byteWidth = bufferLoad.byteWidth(); + + popType(MemorySegment.class); + + if (SharedUtils.isPowerOfTwo(byteWidth)) { + ClassDesc valueLayoutType = emitLoadLayoutConstant(loadType); + cb.loadConstant(offset); + MethodTypeDesc descriptor = MethodTypeDesc.of(desc(loadType), valueLayoutType, CD_long); + cb.invokeinterface(CD_MemorySegment, "get", descriptor); + } else { + // chunked + int readAddrIdx = cb.allocateLocal(ReferenceType); + cb.storeLocal(ReferenceType, readAddrIdx); + + cb.loadConstant(0L); // result + int resultIdx = cb.allocateLocal(LongType); + cb.storeLocal(LongType, resultIdx); + + int remaining = byteWidth; + int chunkOffset = 0; + do { + int chunkSize = Integer.highestOneBit(remaining); // next power of 2 + Class chunkType; + ClassDesc toULongHolder; + MethodTypeDesc toULongDescriptor; + switch (chunkSize) { + case Integer.BYTES -> { + chunkType = int.class; + toULongHolder = CD_Integer; + toULongDescriptor = MTD_INTEGER_TO_UNSIGNED_LONG; + } + case Short.BYTES -> { + chunkType = short.class; + toULongHolder = CD_Short; + toULongDescriptor = MTD_SHORT_TO_UNSIGNED_LONG; + } + case Byte.BYTES -> { + chunkType = byte.class; + toULongHolder = CD_Byte; + toULongDescriptor = MTD_BYTE_TO_UNSIGNED_LONG; + } + default -> + throw new IllegalStateException("Unexpected chunk size for chunked write: " + chunkSize); + } + // read from segment + cb.loadLocal(ReferenceType, readAddrIdx); + ClassDesc valueLayoutType = emitLoadLayoutConstant(chunkType); + MethodTypeDesc descriptor = MethodTypeDesc.of(desc(chunkType), valueLayoutType, CD_long); + long readOffset = offset + SharedUtils.pickChunkOffset(chunkOffset, byteWidth, chunkSize); + cb.loadConstant(readOffset); + cb.invokeinterface(CD_MemorySegment, "get", descriptor); + cb.invokestatic(toULongHolder, "toUnsignedLong", toULongDescriptor); + + // shift to right offset + int shiftAmount = chunkOffset * Byte.SIZE; + if (shiftAmount != 0) { + cb.loadConstant(shiftAmount); + cb.lshl(); + } + // add to result + cb.loadLocal(LongType, resultIdx); + cb.lor(); + cb.storeLocal(LongType, resultIdx); + + remaining -= chunkSize; + chunkOffset += chunkSize; + } while (remaining != 0); + + cb.loadLocal(LongType, resultIdx); + if (loadType == int.class) { + cb.l2i(); + } else { + assert loadType == long.class; // should not have chunking for other types + } + } + + pushType(loadType); + } + + private void emitCopyBuffer(Copy copy) { + long size = copy.size(); + long alignment = copy.alignment(); + + popType(MemorySegment.class); + + // operand/srcSegment is on the stack + // generating a call to: + // MemorySegment::copy(MemorySegment srcSegment, long srcOffset, MemorySegment dstSegment, long dstOffset, long bytes) + cb.loadConstant(0L); + // create the dstSegment by allocating it. Similar to: + // context.allocator().allocate(size, alignment) + emitLoadInternalAllocator(); + emitAllocateCall(size, alignment); + cb.dup(); + int storeIdx = cb.allocateLocal(ReferenceType); + cb.storeLocal(ReferenceType, storeIdx); + cb.loadConstant(0L); + cb.loadConstant(size); + cb.invokestatic(CD_MemorySegment, "copy", MTD_COPY, true); + + cb.loadLocal(ReferenceType, storeIdx); + pushType(MemorySegment.class); + } + + private void emitAllocateCall(long size, long alignment) { + cb.loadConstant(size); + cb.loadConstant(alignment); + cb.invokeinterface(CD_SegmentAllocator, "allocate", MTD_ALLOCATE); + } + + private ClassDesc emitLoadLayoutConstant(Class type) { + ClassDesc valueLayoutType = valueLayoutTypeFor(type); + String valueLayoutConstantName = valueLayoutConstantFor(type); + cb.getstatic(CD_ValueLayout, valueLayoutConstantName, valueLayoutType); + return valueLayoutType; + } + + private static String valueLayoutConstantFor(Class type) { + if (type == boolean.class) { + return "JAVA_BOOLEAN"; + } else if (type == byte.class) { + return "JAVA_BYTE"; + } else if (type == short.class) { + return "JAVA_SHORT_UNALIGNED"; + } else if (type == char.class) { + return "JAVA_CHAR_UNALIGNED"; + } else if (type == int.class) { + return "JAVA_INT_UNALIGNED"; + } else if (type == long.class) { + return "JAVA_LONG_UNALIGNED"; + } else if (type == float.class) { + return "JAVA_FLOAT_UNALIGNED"; + } else if (type == double.class) { + return "JAVA_DOUBLE_UNALIGNED"; + } else if (type == MemorySegment.class) { + return "ADDRESS_UNALIGNED"; + } else { + throw new IllegalStateException("Unknown type: " + type); + } + } + + private static ClassDesc valueLayoutTypeFor(Class type) { + if (type == boolean.class) { + return CD_ValueLayout_OfBoolean; + } else if (type == byte.class) { + return CD_ValueLayout_OfByte; + } else if (type == short.class) { + return CD_ValueLayout_OfShort; + } else if (type == char.class) { + return CD_ValueLayout_OfChar; + } else if (type == int.class) { + return CD_ValueLayout_OfInt; + } else if (type == long.class) { + return CD_ValueLayout_OfLong; + } else if (type == float.class) { + return CD_ValueLayout_OfFloat; + } else if (type == double.class) { + return CD_ValueLayout_OfDouble; + } else if (type == MemorySegment.class) { + return CD_AddressLayout; + } else { + throw new IllegalStateException("Unknown type: " + type); + } + } + + private void emitDup(Class type) { + if (type == double.class || type == long.class) { + cb.dup2(); + } else { + cb.dup(); + } + } + + /* + * Low-level emit helpers. + */ + + private void emitConstZero(TypeKind kind) { + switch (kind) { + case BooleanType, ByteType, ShortType, CharType, IntType -> cb.iconst_0(); + case LongType -> cb.lconst_0(); + case FloatType -> cb.fconst_0(); + case DoubleType -> cb.dconst_0(); + case ReferenceType -> cb.aconst_null(); + } + } + + @SuppressWarnings("unchecked") + private static T desc(Constable c) { + return (T) c.describeConstable().orElseThrow(); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/CallingSequence.class b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequence.class new file mode 100644 index 00000000..50b67023 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequence.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/CallingSequence.java b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequence.java new file mode 100644 index 00000000..e301f692 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequence.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import java.lang.foreign.FunctionDescriptor; + +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.stream.Stream; + +public class CallingSequence { + private final boolean forUpcall; + private final MethodType callerMethodType; + private final MethodType calleeMethodType; + private final FunctionDescriptor desc; + private final boolean needsReturnBuffer; + private final long returnBufferSize; + private final long allocationSize; + + private final List returnBindings; + private final List> argumentBindings; + + private final LinkerOptions linkerOptions; + + public CallingSequence(boolean forUpcall, MethodType callerMethodType, MethodType calleeMethodType, FunctionDescriptor desc, + boolean needsReturnBuffer, long returnBufferSize, long allocationSize, + List> argumentBindings, List returnBindings, + LinkerOptions linkerOptions) { + this.forUpcall = forUpcall; + this.callerMethodType = callerMethodType; + this.calleeMethodType = calleeMethodType; + this.desc = desc; + this.needsReturnBuffer = needsReturnBuffer; + this.returnBufferSize = returnBufferSize; + this.allocationSize = allocationSize; + this.returnBindings = returnBindings; + this.argumentBindings = argumentBindings; + this.linkerOptions = linkerOptions; + } + + /** + * An important distinction is that downcalls have 1 recipe per caller parameter and + * each callee parameter corresponds to a VM_STORE. Upcalls have 1 recipe per callee parameter and + * each caller parameter corresponds to a VM_LOAD. + * + * The VM_STOREs are then implemented by the leaf handle for downcalls, and vice versa, the wrapper + * stub that wraps an upcall handle implements the VM_LOADS. In both cases the register values are + * communicated through Java primitives. + * + * The 'argumentBindingsCount' below corresponds to the number of recipes, so it is the + * caller parameter count for downcalls, and the callee parameter count for upcalls. + * + * @return the number of binding recipes in this calling sequence + */ + public int argumentBindingsCount() { + return argumentBindings.size(); + } + + public List argumentBindings(int i) { + return argumentBindings.get(i); + } + + public Stream argumentBindings() { + return argumentBindings.stream().flatMap(List::stream); + } + + public List returnBindings() { + return returnBindings; + } + + public boolean forUpcall() { + return forUpcall; + } + + public boolean forDowncall() { + return !forUpcall; + } + + /** + * Returns the caller method type, which is the high-level method type + * for downcalls (the type of the downcall method handle) + * and the low-level method type (all primitives, VM facing) for upcalls. + * + * Note that for downcalls a single parameter in this method type corresponds + * to a single argument binding recipe in this calling sequence, but it may + * correspond to multiple parameters in the callee method type (for instance + * if a struct is split into multiple register values). + * + * @return the caller method type. + */ + public MethodType callerMethodType() { + return callerMethodType; + } + + /** + * Returns the callee method type, which is the low-level method type + * (all primitives, VM facing) for downcalls and the high-level method type + * for upcalls (also the method type of the user-supplied target MH). + * + * Note that for upcalls a single parameter in this method type corresponds + * to a single argument binding recipe in this calling sequence, but it may + * correspond to multiple parameters in the caller method type (for instance + * if a struct is reconstructed from multiple register values). + * + * @return the callee method type. + */ + public MethodType calleeMethodType() { + return calleeMethodType; + } + + public FunctionDescriptor functionDesc() { + return desc; + } + + /** + * Whether this calling sequence needs a return buffer. + * + * A return buffer is used to support functions that return values + * in multiple registers, which is not possible to do just with Java primitives + * (we can only return 1 value in Java, meaning only 1 register value). + * + * To emulate these multi-register returns, we instead use a pre-allocated buffer + * (the return buffer) from/into which the return values are loaded/stored. + * + * For downcalls, we allocate the buffer in Java code, and pass the address down + * to the VM stub, which stores the returned register values into this buffer. + * VM_LOADs in the binding recipe for the return value then load the value from this buffer. + * + * For upcalls, the VM stub allocates a buffer (on the stack), and passes the address + * to the Java method handle it calls. VM_STOREs in the return binding recipe then + * store values into this buffer, after which the VM stub moves the values from the buffer + * into the right register. + * + * @return whether this calling sequence needs a return buffer. + */ + public boolean needsReturnBuffer() { + return needsReturnBuffer; + } + + /** + * The size of the return buffer, if one is needed. + * + * @see #needsReturnBuffer + * + * @return the return buffer size + */ + public long returnBufferSize() { + return returnBufferSize; + } + + /** + * The amount of bytes this calling sequence needs to allocate during an invocation. + * + * Includes the return buffer size as well as space for any buffer copies in the recipes. + * + * @return the allocation size + */ + public long allocationSize() { + return allocationSize; + } + + public boolean hasReturnBindings() { + return !returnBindings.isEmpty(); + } + + public int capturedStateMask() { + return linkerOptions.capturedCallState() + .mapToInt(CapturableState::mask) + .reduce(0, (a, b) -> a | b); + } + + public boolean needsTransition() { + return !linkerOptions.isCritical(); + } + + public int numLeadingParams() { + return 2 + (linkerOptions.hasCapturedCallState() ? 1 : 0); // 2 for addr, allocator + } + + public String asString() { + StringBuilder sb = new StringBuilder(); + + sb.append("CallingSequence: {\n"); + sb.append(" callerMethodType: ").append(callerMethodType); + sb.append(" calleeMethodType: ").append(calleeMethodType); + sb.append(" FunctionDescriptor: ").append(desc); + sb.append(" Argument Bindings:\n"); + for (int i = 0; i < argumentBindingsCount(); i++) { + sb.append(" ").append(i).append(": ").append(argumentBindings.get(i)).append("\n"); + } + if (!returnBindings.isEmpty()) { + sb.append(" ").append("Return: ").append(returnBindings).append("\n"); + } + sb.append("}\n"); + + return sb.toString(); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/CallingSequenceBuilder.class b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequenceBuilder.class new file mode 100644 index 00000000..e02b6b57 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequenceBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/CallingSequenceBuilder.java b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequenceBuilder.java new file mode 100644 index 00000000..a439df3e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/CallingSequenceBuilder.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.Binding.*; +import jdk.internal.foreign.abi.Binding.BoxAddress; +import jdk.internal.foreign.abi.Binding.BufferLoad; +import jdk.internal.foreign.abi.Binding.BufferStore; +import jdk.internal.foreign.abi.Binding.Cast; +import jdk.internal.foreign.abi.Binding.Copy; +import jdk.internal.foreign.abi.Binding.Dup; +import jdk.internal.foreign.abi.Binding.SegmentBase; +import jdk.internal.foreign.abi.Binding.SegmentOffset; +import jdk.internal.foreign.abi.Binding.ShiftLeft; +import jdk.internal.foreign.abi.Binding.ShiftRight; +import jdk.internal.foreign.abi.Binding.VMLoad; +import jdk.internal.foreign.abi.Binding.VMStore; +import sun.security.action.GetPropertyAction; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodType; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import static java.lang.invoke.MethodType.methodType; + +public class CallingSequenceBuilder { + private static final boolean VERIFY_BINDINGS = Boolean.parseBoolean( + GetPropertyAction.privilegedGetProperty("java.lang.foreign.VERIFY_BINDINGS", "true")); + + private final ABIDescriptor abi; + private final LinkerOptions linkerOptions; + + private final boolean forUpcall; + private final List> inputBindings = new ArrayList<>(); + private List outputBindings = List.of(); + + private MethodType mt = MethodType.methodType(void.class); + private FunctionDescriptor desc = FunctionDescriptor.ofVoid(); + + public CallingSequenceBuilder(ABIDescriptor abi, boolean forUpcall, LinkerOptions linkerOptions) { + this.abi = abi; + this.forUpcall = forUpcall; + this.linkerOptions = linkerOptions; + } + + public final CallingSequenceBuilder addArgumentBindings(Class carrier, MemoryLayout layout, + List bindings) { + addArgumentBinding(inputBindings.size(), carrier, layout, bindings); + return this; + } + + private void addArgumentBinding(int index, Class carrier, MemoryLayout layout, List bindings) { + verifyBindings(true, carrier, bindings); + inputBindings.add(index, bindings); + mt = mt.insertParameterTypes(index, carrier); + desc = desc.insertArgumentLayouts(index, layout); + } + + public CallingSequenceBuilder setReturnBindings(Class carrier, MemoryLayout layout, + List bindings) { + verifyBindings(false, carrier, bindings); + this.outputBindings = bindings; + mt = mt.changeReturnType(carrier); + desc = desc.changeReturnLayout(layout); + return this; + } + + private boolean needsReturnBuffer() { + return outputBindings.stream() + .filter(Binding.Move.class::isInstance) + .count() > 1; + } + + public CallingSequence build() { + boolean needsReturnBuffer = needsReturnBuffer(); + long returnBufferSize = needsReturnBuffer ? computeReturnBufferSize() : 0; + long allocationSize = computeAllocationSize() + returnBufferSize; + MethodType callerMethodType; + MethodType calleeMethodType; + if (!forUpcall) { + if (linkerOptions.hasCapturedCallState()) { + addArgumentBinding(0, MemorySegment.class, ValueLayout.ADDRESS, List.of( + Binding.unboxAddress(), + Binding.vmStore(abi.capturedStateStorage(), long.class))); + } + addArgumentBinding(0, MemorySegment.class, ValueLayout.ADDRESS, List.of( + Binding.unboxAddress(), + Binding.vmStore(abi.targetAddrStorage(), long.class))); + if (needsReturnBuffer) { + addArgumentBinding(0, MemorySegment.class, ValueLayout.ADDRESS, List.of( + Binding.unboxAddress(), + Binding.vmStore(abi.retBufAddrStorage(), long.class))); + } + + callerMethodType = mt; + calleeMethodType = computeCalleeTypeForDowncall(); + } else { // forUpcall == true + if (needsReturnBuffer) { + addArgumentBinding(0, MemorySegment.class, ValueLayout.ADDRESS, List.of( + Binding.vmLoad(abi.retBufAddrStorage(), long.class), + Binding.boxAddress(returnBufferSize))); + } + + callerMethodType = computeCallerTypeForUpcall(); + calleeMethodType = mt; + } + return new CallingSequence(forUpcall, callerMethodType, calleeMethodType, desc, needsReturnBuffer, + returnBufferSize, allocationSize, inputBindings, outputBindings, linkerOptions); + } + + private MethodType computeCallerTypeForUpcall() { + return computeTypeHelper(VMLoad.class, VMStore.class); + } + + private MethodType computeCalleeTypeForDowncall() { + return computeTypeHelper(VMStore.class, VMLoad.class); + } + + private MethodType computeTypeHelper(Class inputVMClass, + Class outputVMClass) { + Class[] paramTypes = inputBindings.stream() + .flatMap(List::stream) + .filter(inputVMClass::isInstance) + .map(inputVMClass::cast) + .map(Binding.Move::type) + .toArray(Class[]::new); + + Binding.Move[] retMoves = outputBindings.stream() + .filter(outputVMClass::isInstance) + .map(outputVMClass::cast) + .toArray(Binding.Move[]::new); + Class returnType = retMoves.length == 1 ? retMoves[0].type() : void.class; + + return methodType(returnType, paramTypes); + } + + private long computeAllocationSize() { + // FIXME: > 16 bytes alignment might need extra space since the + // starting address of the allocator might be un-aligned. + long size = 0; + for (List bindings : inputBindings) { + for (Binding b : bindings) { + if (b instanceof Copy copy) { + size = Utils.alignUp(size, copy.alignment()); + size += copy.size(); + } else if (b instanceof Allocate allocate) { + size = Utils.alignUp(size, allocate.alignment()); + size += allocate.size(); + } + } + } + return size; + } + + private long computeReturnBufferSize() { + return outputBindings.stream() + .filter(Binding.Move.class::isInstance) + .map(Binding.Move.class::cast) + .map(Binding.Move::storage) + .map(VMStorage::type) + .mapToLong(abi.arch::typeSize) + .sum(); + } + + private void verifyBindings(boolean forArguments, Class carrier, List bindings) { + if (VERIFY_BINDINGS) { + if (forUpcall == forArguments) { + verifyBoxBindings(carrier, bindings); + } else { + verifyUnboxBindings(carrier, bindings); + } + } + } + + private static void verifyUnboxBindings(Class inType, List bindings) { + Deque> stack = new ArrayDeque<>(); + stack.push(inType); + + for (Binding b : bindings) { + if (!isUnbox(b)) + throw new IllegalArgumentException("Unexpected operator: " + b); + b.verify(stack); + } + + if (!stack.isEmpty()) { + throw new IllegalArgumentException("Stack must be empty after recipe"); + } + } + + static boolean isUnbox(Binding binding) { + return switch (binding) { + case VMStore unused -> true; + case BufferLoad unused -> true; + case Copy unused -> true; + case Dup unused -> true; + case SegmentBase unused -> true; + case SegmentOffset unused -> true; + case ShiftLeft unused -> true; + case ShiftRight unused -> true; + case Cast unused -> true; + case VMLoad unused -> false; + case BufferStore unused -> false; + case Allocate unused -> false; + case BoxAddress unused -> false; + }; + } + + private static void verifyBoxBindings(Class expectedOutType, List bindings) { + Deque> stack = new ArrayDeque<>(); + + for (Binding b : bindings) { + if (!isBox(b)) + throw new IllegalArgumentException("Unexpected operator: " + b); + b.verify(stack); + } + + if (stack.size() != 1) { + throw new IllegalArgumentException("Stack must contain exactly 1 value"); + } + + Class actualOutType = stack.pop(); + SharedUtils.checkType(actualOutType, expectedOutType); + } + + static boolean isBox(Binding binding) { + return switch (binding) { + case VMLoad unused -> true; + case BufferStore unused -> true; + case Copy unused -> true; + case Allocate unused -> true; + case BoxAddress unused -> true; + case Dup unused -> true; + case ShiftLeft unused -> true; + case ShiftRight unused -> true; + case Cast unused -> true; + + case VMStore unused -> false; + case BufferLoad unused -> false; + case SegmentBase unused -> false; + case SegmentOffset unused -> false; + }; + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/CapturableState.class b/tests/test_data/std/jdk/internal/foreign/abi/CapturableState.class new file mode 100644 index 00000000..b3cb8f8f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/CapturableState.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/CapturableState.java b/tests/test_data/std/jdk/internal/foreign/abi/CapturableState.java new file mode 100644 index 00000000..8689751a --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/CapturableState.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import jdk.internal.foreign.Utils; + +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.ValueLayout; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.foreign.ValueLayout.JAVA_INT; + +public enum CapturableState { + GET_LAST_ERROR ("GetLastError", JAVA_INT, 1 << 0, Utils.IS_WINDOWS), + WSA_GET_LAST_ERROR("WSAGetLastError", JAVA_INT, 1 << 1, Utils.IS_WINDOWS), + ERRNO ("errno", JAVA_INT, 1 << 2, true); + + public static final StructLayout LAYOUT = MemoryLayout.structLayout( + supportedStates().map(CapturableState::layout).toArray(MemoryLayout[]::new)); + + private final String stateName; + private final ValueLayout layout; + private final int mask; + private final boolean isSupported; + + CapturableState(String stateName, ValueLayout layout, int mask, boolean isSupported) { + this.stateName = stateName; + this.layout = layout.withName(stateName); + this.mask = mask; + this.isSupported = isSupported; + } + + private static Stream supportedStates() { + return Stream.of(values()).filter(CapturableState::isSupported); + } + + public static CapturableState forName(String name) { + return Stream.of(values()) + .filter(stl -> stl.stateName().equals(name)) + .filter(CapturableState::isSupported) + .findAny() + .orElseThrow(() -> new IllegalArgumentException( + "Unknown name: " + name +", must be one of: " + + supportedStates() + .map(CapturableState::stateName) + .collect(Collectors.joining(", ")))); + } + + public String stateName() { + return stateName; + } + + public ValueLayout layout() { + return layout; + } + + public int mask() { + return mask; + } + + public boolean isSupported() { + return isSupported; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$1.class b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$1.class new file mode 100644 index 00000000..7ace477d Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$2.class b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$2.class new file mode 100644 index 00000000..359f8ae4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$2.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$InvocationData.class b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$InvocationData.class new file mode 100644 index 00000000..a80b41d1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker$InvocationData.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker.class b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker.class new file mode 100644 index 00000000..0e55eb12 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker.java b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker.java new file mode 100644 index 00000000..44fee62c --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/DowncallLinker.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import sun.security.action.GetPropertyAction; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import jdk.internal.foreign.AbstractMemorySegmentImpl; +import jdk.internal.foreign.MemorySessionImpl; + +import static java.lang.invoke.MethodHandles.collectArguments; +import static java.lang.invoke.MethodHandles.foldArguments; +import static java.lang.invoke.MethodHandles.identity; +import static java.lang.invoke.MethodHandles.insertArguments; +import static java.lang.invoke.MethodType.methodType; + +public class DowncallLinker { + private static final boolean USE_SPEC = Boolean.parseBoolean( + GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.DowncallLinker.USE_SPEC", "true")); + + private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); + + private static final MethodHandle MH_INVOKE_INTERP_BINDINGS; + private static final MethodHandle EMPTY_OBJECT_ARRAY_HANDLE = MethodHandles.constant(Object[].class, new Object[0]); + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(DowncallLinker.class, "invokeInterpBindings", + methodType(Object.class, SegmentAllocator.class, Object[].class, InvocationData.class)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private final ABIDescriptor abi; + private final CallingSequence callingSequence; + + public DowncallLinker(ABIDescriptor abi, CallingSequence callingSequence) { + this.abi = abi; + assert callingSequence.forDowncall(); + this.callingSequence = callingSequence; + } + + public MethodHandle getBoundMethodHandle() { + Binding.VMStore[] argMoves = argMoveBindingsStream(callingSequence).toArray(Binding.VMStore[]::new); + Binding.VMLoad[] retMoves = retMoveBindings(callingSequence); + + MethodType leafType = callingSequence.calleeMethodType(); + + NativeEntryPoint nep = NativeEntryPoint.make( + abi, + toStorageArray(argMoves), + toStorageArray(retMoves), + leafType, + callingSequence.needsReturnBuffer(), + callingSequence.capturedStateMask(), + callingSequence.needsTransition() + ); + MethodHandle handle = JLIA.nativeMethodHandle(nep); + + if (USE_SPEC) { + handle = BindingSpecializer.specializeDowncall(handle, callingSequence, abi); + } else { + InvocationData invData = new InvocationData(handle, callingSequence); + handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 2, invData); + MethodType interpType = callingSequence.callerMethodType(); + if (callingSequence.needsReturnBuffer()) { + // Return buffer is supplied by invokeInterpBindings + assert interpType.parameterType(0) == MemorySegment.class; + interpType = interpType.dropParameterTypes(0, 1); + } + MethodHandle collectorInterp = makeCollectorHandle(interpType); + handle = collectArguments(handle, 1, collectorInterp); + handle = handle.asType(handle.type().changeReturnType(interpType.returnType())); + } + + assert handle.type().parameterType(0) == SegmentAllocator.class; + assert handle.type().parameterType(1) == MemorySegment.class; + handle = foldArguments(handle, 1, SharedUtils.MH_CHECK_SYMBOL); + + handle = SharedUtils.swapArguments(handle, 0, 1); // normalize parameter order + + return handle; + } + + // Funnel from type to Object[] + private static MethodHandle makeCollectorHandle(MethodType type) { + return type.parameterCount() == 0 + ? EMPTY_OBJECT_ARRAY_HANDLE + : identity(Object[].class) + .asCollector(Object[].class, type.parameterCount()) + .asType(type.changeReturnType(Object[].class)); + } + + private Stream argMoveBindingsStream(CallingSequence callingSequence) { + return callingSequence.argumentBindings() + .filter(Binding.VMStore.class::isInstance) + .map(Binding.VMStore.class::cast); + } + + private Binding.VMLoad[] retMoveBindings(CallingSequence callingSequence) { + return retMoveBindingsStream(callingSequence).toArray(Binding.VMLoad[]::new); + } + + private Stream retMoveBindingsStream(CallingSequence callingSequence) { + return callingSequence.returnBindings().stream() + .filter(Binding.VMLoad.class::isInstance) + .map(Binding.VMLoad.class::cast); + } + + private VMStorage[] toStorageArray(Binding.Move[] moves) { + return Arrays.stream(moves).map(Binding.Move::storage).toArray(VMStorage[]::new); + } + + private record InvocationData(MethodHandle leaf, CallingSequence callingSequence) {} + + Object invokeInterpBindings(SegmentAllocator allocator, Object[] args, InvocationData invData) throws Throwable { + Arena unboxArena = callingSequence.allocationSize() != 0 + ? SharedUtils.newBoundedArena(callingSequence.allocationSize()) + : SharedUtils.DUMMY_ARENA; + List acquiredScopes = new ArrayList<>(); + try (unboxArena) { + MemorySegment returnBuffer = null; + + // do argument processing, get Object[] as result + if (callingSequence.needsReturnBuffer()) { + // we supply the return buffer (argument array does not contain it) + Object[] prefixedArgs = new Object[args.length + 1]; + returnBuffer = unboxArena.allocate(callingSequence.returnBufferSize()); + prefixedArgs[0] = returnBuffer; + System.arraycopy(args, 0, prefixedArgs, 1, args.length); + args = prefixedArgs; + } + + Object[] leafArgs = new Object[invData.leaf.type().parameterCount()]; + BindingInterpreter.StoreFunc storeFunc = new BindingInterpreter.StoreFunc() { + int argOffset = 0; + @Override + public void store(VMStorage storage, Object o) { + leafArgs[argOffset++] = o; + } + }; + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + if (callingSequence.functionDesc().argumentLayouts().get(i) instanceof AddressLayout) { + MemorySessionImpl sessionImpl = ((AbstractMemorySegmentImpl) arg).sessionImpl(); + if (!(callingSequence.needsReturnBuffer() && i == 0)) { // don't acquire unboxArena's scope + sessionImpl.acquire0(); + // add this scope _after_ we acquire, so we only release scopes we actually acquired + // in case an exception occurs + acquiredScopes.add(sessionImpl); + } + } + BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i), storeFunc, unboxArena); + } + + // call leaf + Object o = invData.leaf.invokeWithArguments(leafArgs); + + // return value processing + if (o == null) { + if (!callingSequence.needsReturnBuffer()) { + return null; + } + MemorySegment finalReturnBuffer = returnBuffer; + return BindingInterpreter.box(callingSequence.returnBindings(), + new BindingInterpreter.LoadFunc() { + int retBufReadOffset = 0; + @Override + public Object load(VMStorage storage, Class type) { + Object result1 = SharedUtils.read(finalReturnBuffer, retBufReadOffset, type); + retBufReadOffset += abi.arch.typeSize(storage.type()); + return result1; + } + }, allocator); + } else { + return BindingInterpreter.box(callingSequence.returnBindings(), (storage, type) -> o, + allocator); + } + } finally { + for (MemorySessionImpl sessionImpl : acquiredScopes) { + sessionImpl.release0(); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$CaptureCallState.class b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$CaptureCallState.class new file mode 100644 index 00000000..907f7847 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$CaptureCallState.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$Critical.class b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$Critical.class new file mode 100644 index 00000000..732b7cee Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$Critical.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg.class b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg.class new file mode 100644 index 00000000..80d2cbce Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$FirstVariadicArg.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl.class b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl.class new file mode 100644 index 00000000..fd99242c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions$LinkerOptionImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions.class b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions.class new file mode 100644 index 00000000..c4d931ba Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions.java b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions.java new file mode 100644 index 00000000..fcc98ecc --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/LinkerOptions.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Stream; + +public class LinkerOptions { + + private static final LinkerOptions EMPTY = new LinkerOptions(Map.of()); + private final Map, LinkerOptionImpl> optionsMap; + + private LinkerOptions(Map, LinkerOptionImpl> optionsMap) { + this.optionsMap = optionsMap; + } + + public static LinkerOptions forDowncall(FunctionDescriptor desc, Linker.Option... options) { + return forShared(LinkerOptionImpl::validateForDowncall, desc, options); + } + + public static LinkerOptions forUpcall(FunctionDescriptor desc, Linker.Option[] options) { + return forShared(LinkerOptionImpl::validateForUpcall, desc, options); + } + + private static LinkerOptions forShared(BiConsumer validator, + FunctionDescriptor desc, Linker.Option... options) { + Map, LinkerOptionImpl> optionMap = new HashMap<>(); + + for (Linker.Option option : options) { + if (optionMap.containsKey(option.getClass())) { + throw new IllegalArgumentException("Duplicate option: " + option); + } + LinkerOptionImpl opImpl = (LinkerOptionImpl) option; + validator.accept(opImpl, desc); + optionMap.put(option.getClass(), opImpl); + } + + LinkerOptions linkerOptions = new LinkerOptions(optionMap); + if (linkerOptions.hasCapturedCallState() && linkerOptions.isCritical()) { + throw new IllegalArgumentException("Incompatible linker options: captureCallState, critical"); + } + return linkerOptions; + } + + public static LinkerOptions empty() { + return EMPTY; + } + + private T getOption(Class type) { + return type.cast(optionsMap.get(type)); + } + + public boolean isVarargsIndex(int argIndex) { + FirstVariadicArg fva = getOption(FirstVariadicArg.class); + return fva != null && argIndex >= fva.index(); + } + + public boolean hasCapturedCallState() { + return getOption(CaptureCallState.class) != null; + } + + public Stream capturedCallState() { + CaptureCallState stl = getOption(CaptureCallState.class); + return stl == null ? Stream.empty() : stl.saved().stream(); + } + + public boolean isVariadicFunction() { + FirstVariadicArg fva = getOption(FirstVariadicArg.class); + return fva != null; + } + + public int firstVariadicArgIndex() { + return getOption(FirstVariadicArg.class).index(); + } + + public boolean isCritical() { + Critical c = getOption(Critical.class); + return c != null; + } + + public boolean allowsHeapAccess() { + Critical c = getOption(Critical.class); + return c != null && c.allowHeapAccess(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + return o instanceof LinkerOptions that + && Objects.equals(optionsMap, that.optionsMap); + } + + @Override + public int hashCode() { + return Objects.hash(optionsMap); + } + + public sealed interface LinkerOptionImpl extends Linker.Option + permits CaptureCallState, FirstVariadicArg, Critical { + default void validateForDowncall(FunctionDescriptor descriptor) { + throw new IllegalArgumentException("Not supported for downcall: " + this); + } + + default void validateForUpcall(FunctionDescriptor descriptor) { + throw new IllegalArgumentException("Not supported for upcall: " + this); + } + } + + public record FirstVariadicArg(int index) implements LinkerOptionImpl { + @Override + public void validateForDowncall(FunctionDescriptor descriptor) { + if (index < 0 || index > descriptor.argumentLayouts().size()) { + throw new IllegalArgumentException("Index '" + index + "' not in bounds for descriptor: " + descriptor); + } + } + } + + public record CaptureCallState(Set saved) implements LinkerOptionImpl { + @Override + public void validateForDowncall(FunctionDescriptor descriptor) { + // done during construction + } + } + + public record Critical(boolean allowHeapAccess) implements LinkerOptionImpl { + public static Critical ALLOW_HEAP = new Critical(true); + public static Critical DONT_ALLOW_HEAP = new Critical(false); + + @Override + public void validateForDowncall(FunctionDescriptor descriptor) { + // always allowed + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint$CacheKey.class b/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint$CacheKey.class new file mode 100644 index 00000000..9d5a8ab2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint$CacheKey.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint.class b/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint.class new file mode 100644 index 00000000..01f1bf29 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint.java b/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint.java new file mode 100644 index 00000000..e65cee68 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/NativeEntryPoint.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign.abi; + +import jdk.internal.ref.CleanerFactory; + +import java.lang.invoke.MethodType; +import java.lang.ref.Cleaner; +import java.util.Arrays; +import java.util.List; + +/** + * This class describes a 'native entry point', which is used as an appendix argument to linkToNative calls. + */ +public class NativeEntryPoint { + static { + registerNatives(); + } + + private final MethodType methodType; + private final long downcallStubAddress; // read by VM + + private static final Cleaner CLEANER = CleanerFactory.cleaner(); + private static final SoftReferenceCache NEP_CACHE = new SoftReferenceCache<>(); + private record CacheKey(MethodType methodType, ABIDescriptor abi, + List argMoves, List retMoves, + boolean needsReturnBuffer, int capturedStateMask, + boolean needsTransition) {} + + private NativeEntryPoint(MethodType methodType, long downcallStubAddress) { + this.methodType = methodType; + this.downcallStubAddress = downcallStubAddress; + } + + public static NativeEntryPoint make(ABIDescriptor abi, + VMStorage[] argMoves, VMStorage[] returnMoves, + MethodType methodType, + boolean needsReturnBuffer, + int capturedStateMask, + boolean needsTransition) { + if (returnMoves.length > 1 != needsReturnBuffer) { + throw new AssertionError("Multiple register return, but needsReturnBuffer was false"); + } + checkType(methodType, needsReturnBuffer, capturedStateMask); + + CacheKey key = new CacheKey(methodType, abi, Arrays.asList(argMoves), Arrays.asList(returnMoves), + needsReturnBuffer, capturedStateMask, needsTransition); + return NEP_CACHE.get(key, k -> { + long downcallStub = makeDowncallStub(methodType, abi, argMoves, returnMoves, needsReturnBuffer, + capturedStateMask, needsTransition); + if (downcallStub == 0) { + throw new OutOfMemoryError("Failed to allocate downcall stub"); + } + NativeEntryPoint nep = new NativeEntryPoint(methodType, downcallStub); + CLEANER.register(nep, () -> freeDowncallStub(downcallStub)); + return nep; + }); + } + + private static void checkType(MethodType methodType, boolean needsReturnBuffer, int savedValueMask) { + if (methodType.parameterType(0) != long.class) { + throw new AssertionError("Address expected as first param: " + methodType); + } + int checkIdx = 1; + if ((needsReturnBuffer && methodType.parameterType(checkIdx++) != long.class) + || (savedValueMask != 0 && methodType.parameterType(checkIdx) != long.class)) { + throw new AssertionError("return buffer and/or preserved value address expected: " + methodType); + } + } + + private static native long makeDowncallStub(MethodType methodType, ABIDescriptor abi, + VMStorage[] encArgMoves, VMStorage[] encRetMoves, + boolean needsReturnBuffer, + int capturedStateMask, + boolean needsTransition); + + private static native boolean freeDowncallStub0(long downcallStub); + private static void freeDowncallStub(long downcallStub) { + if (!freeDowncallStub0(downcallStub)) { + throw new InternalError("Could not free downcall stub"); + } + } + + public MethodType type() { + return methodType; + } + + private static native void registerNatives(); +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$1.class b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$1.class new file mode 100644 index 00000000..da6783a6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$2.class b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$2.class new file mode 100644 index 00000000..ba1c452b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$2.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$3.class b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$3.class new file mode 100644 index 00000000..b034a36f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$3.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$4.class b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$4.class new file mode 100644 index 00000000..9b26e79a Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils$4.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils.class b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils.class new file mode 100644 index 00000000..b6080b05 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils.java b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils.java new file mode 100644 index 00000000..83698398 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/SharedUtils.java @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.JavaLangInvokeAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.foreign.CABI; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker; +import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker; +import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64Linker; +import jdk.internal.foreign.abi.fallback.FallbackLinker; +import jdk.internal.foreign.abi.ppc64.aix.AixPPC64Linker; +import jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64Linker; +import jdk.internal.foreign.abi.ppc64.linux.LinuxPPC64leLinker; +import jdk.internal.foreign.abi.riscv64.linux.LinuxRISCV64Linker; +import jdk.internal.foreign.abi.s390.linux.LinuxS390Linker; +import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker; +import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker; +import jdk.internal.vm.annotation.ForceInline; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySegment.Scope; +import java.lang.foreign.SegmentAllocator; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.ref.Reference; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static java.lang.foreign.ValueLayout.*; +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.methodType; + +public final class SharedUtils { + + private SharedUtils() { + } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); + + private static final MethodHandle MH_ALLOC_BUFFER; + private static final MethodHandle MH_BUFFER_COPY; + private static final MethodHandle MH_REACHABILITY_FENCE; + public static final MethodHandle MH_CHECK_SYMBOL; + private static final MethodHandle MH_CHECK_CAPTURE_SEGMENT; + + @SuppressWarnings("restricted") + public static final AddressLayout C_POINTER = ADDRESS + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); + + public static final Arena DUMMY_ARENA = new Arena() { + @Override + public Scope scope() { + throw new UnsupportedOperationException(); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + throw new UnsupportedOperationException(); + } + + @Override + public void close() { + // do nothing + } + }; + + static { + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MH_ALLOC_BUFFER = lookup.findVirtual(SegmentAllocator.class, "allocate", + methodType(MemorySegment.class, MemoryLayout.class)); + MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy", + methodType(MemorySegment.class, MemorySegment.class, MemorySegment.class)); + MH_REACHABILITY_FENCE = lookup.findStatic(Reference.class, "reachabilityFence", + methodType(void.class, Object.class)); + MH_CHECK_SYMBOL = lookup.findStatic(SharedUtils.class, "checkSymbol", + methodType(void.class, MemorySegment.class)); + MH_CHECK_CAPTURE_SEGMENT = lookup.findStatic(SharedUtils.class, "checkCaptureSegment", + methodType(MemorySegment.class, MemorySegment.class)); + } catch (ReflectiveOperationException e) { + throw new BootstrapMethodError(e); + } + } + + // this allocator should be used when no allocation is expected + public static final SegmentAllocator THROWING_ALLOCATOR = (size, align) -> { + throw new IllegalStateException("Cannot get here"); + }; + + public static long alignUp(long addr, long alignment) { + return ((addr - 1) | (alignment - 1)) + 1; + } + + public static long remainsToAlignment(long addr, long alignment) { + return alignUp(addr, alignment) - addr; + } + + /** + * Takes a MethodHandle that takes an input buffer as a first argument (a MemorySegment), and returns nothing, + * and adapts it to return a MemorySegment, by allocating a MemorySegment for the input + * buffer, calling the target MethodHandle, and then returning the allocated MemorySegment. + * + * This allows viewing a MethodHandle that makes use of in memory return (IMR) as a MethodHandle that just returns + * a MemorySegment without requiring a pre-allocated buffer as an explicit input. + * + * @param handle the target handle to adapt + * @param cDesc the function descriptor of the native function (with actual return layout) + * @return the adapted handle + */ + public static MethodHandle adaptDowncallForIMR(MethodHandle handle, FunctionDescriptor cDesc, CallingSequence sequence) { + if (handle.type().returnType() != void.class) + throw new IllegalArgumentException("return expected to be void for in memory returns: " + handle.type()); + int imrAddrIdx = sequence.numLeadingParams(); + if (handle.type().parameterType(imrAddrIdx) != MemorySegment.class) + throw new IllegalArgumentException("MemorySegment expected as third param: " + handle.type()); + if (cDesc.returnLayout().isEmpty()) + throw new IllegalArgumentException("Return layout needed: " + cDesc); + + MethodHandle ret = identity(MemorySegment.class); // (MemorySegment) MemorySegment + handle = collectArguments(ret, 1, handle); // (MemorySegment, MemorySegment, SegmentAllocator, MemorySegment, ...) MemorySegment + handle = mergeArguments(handle, 0, 1 + imrAddrIdx); // (MemorySegment, MemorySegment, SegmentAllocator, ...) MemorySegment + handle = collectArguments(handle, 0, insertArguments(MH_ALLOC_BUFFER, 1, cDesc.returnLayout().get())); // (SegmentAllocator, MemorySegment, SegmentAllocator, ...) MemorySegment + handle = mergeArguments(handle, 0, 2); // (SegmentAllocator, MemorySegment, ...) MemorySegment + handle = swapArguments(handle, 0, 1); // (MemorySegment, SegmentAllocator, ...) MemorySegment + return handle; + } + + /** + * Takes a MethodHandle that returns a MemorySegment, and adapts it to take an input buffer as a first argument + * (a MemorySegment), and upon invocation, copies the contents of the returned MemorySegment into the input buffer + * passed as the first argument. + * + * @param target the target handle to adapt + * @return the adapted handle + */ + private static MethodHandle adaptUpcallForIMR(MethodHandle target, boolean dropReturn) { + if (target.type().returnType() != MemorySegment.class) + throw new IllegalArgumentException("Must return MemorySegment for IMR"); + + target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemorySegment, ...) MemorySegment + + if (dropReturn) { // no handling for return value, need to drop it + target = dropReturn(target); + } else { + // adjust return type so that it matches the inferred type of the effective + // function descriptor + target = target.asType(target.type().changeReturnType(MemorySegment.class)); + } + + return target; + } + + public static UpcallStubFactory arrangeUpcallHelper(MethodType targetType, boolean isInMemoryReturn, boolean dropReturn, + ABIDescriptor abi, CallingSequence callingSequence) { + if (isInMemoryReturn) { + // simulate the adaptation to get the type + MethodHandle fakeTarget = MethodHandles.empty(targetType); + targetType = adaptUpcallForIMR(fakeTarget, dropReturn).type(); + } + + UpcallStubFactory factory = UpcallLinker.makeFactory(targetType, abi, callingSequence); + + if (isInMemoryReturn) { + final UpcallStubFactory finalFactory = factory; + factory = (target, scope) -> { + target = adaptUpcallForIMR(target, dropReturn); + return finalFactory.makeStub(target, scope); + }; + } + + return factory; + } + + private static MemorySegment bufferCopy(MemorySegment dest, MemorySegment buffer) { + return dest.copyFrom(buffer); + } + + public static Class primitiveCarrierForSize(long size, boolean useFloat) { + return primitiveLayoutForSize(size, useFloat).carrier(); + } + + public static ValueLayout primitiveLayoutForSize(long size, boolean useFloat) { + if (useFloat) { + if (size == 4) { + return JAVA_FLOAT; + } else if (size == 8) { + return JAVA_DOUBLE; + } + } else { + if (size == 1) { + return JAVA_BYTE; + } else if (size == 2) { + return JAVA_SHORT; + } else if (size <= 4) { + return JAVA_INT; + } else if (size <= 8) { + return JAVA_LONG; + } + } + + throw new IllegalArgumentException("No layout for size: " + size + " isFloat=" + useFloat); + } + + public static Linker getSystemLinker() { + return switch (CABI.current()) { + case WIN_64 -> Windowsx64Linker.getInstance(); + case SYS_V -> SysVx64Linker.getInstance(); + case LINUX_AARCH_64 -> LinuxAArch64Linker.getInstance(); + case MAC_OS_AARCH_64 -> MacOsAArch64Linker.getInstance(); + case WIN_AARCH_64 -> WindowsAArch64Linker.getInstance(); + case AIX_PPC_64 -> AixPPC64Linker.getInstance(); + case LINUX_PPC_64 -> LinuxPPC64Linker.getInstance(); + case LINUX_PPC_64_LE -> LinuxPPC64leLinker.getInstance(); + case LINUX_RISCV_64 -> LinuxRISCV64Linker.getInstance(); + case LINUX_S390 -> LinuxS390Linker.getInstance(); + case FALLBACK -> FallbackLinker.getInstance(); + case UNSUPPORTED -> throw new UnsupportedOperationException("Platform does not support native linker"); + }; + } + + static Map indexMap(Binding.Move[] moves) { + return IntStream.range(0, moves.length) + .boxed() + .collect(Collectors.toMap(i -> moves[i].storage(), i -> i)); + } + + static MethodHandle mergeArguments(MethodHandle mh, int sourceIndex, int destIndex) { + MethodType oldType = mh.type(); + Class sourceType = oldType.parameterType(sourceIndex); + Class destType = oldType.parameterType(destIndex); + if (sourceType != destType) { + // TODO meet? + throw new IllegalArgumentException("Parameter types differ: " + sourceType + " != " + destType); + } + MethodType newType = oldType.dropParameterTypes(destIndex, destIndex + 1); + int[] reorder = new int[oldType.parameterCount()]; + if (destIndex < sourceIndex) { + sourceIndex--; + } + for (int i = 0, index = 0; i < reorder.length; i++) { + if (i != destIndex) { + reorder[i] = index++; + } else { + reorder[i] = sourceIndex; + } + } + return permuteArguments(mh, newType, reorder); + } + + + public static MethodHandle swapArguments(MethodHandle mh, int firstArg, int secondArg) { + MethodType mtype = mh.type(); + int[] perms = new int[mtype.parameterCount()]; + MethodType swappedType = MethodType.methodType(mtype.returnType()); + for (int i = 0 ; i < perms.length ; i++) { + int dst = i; + if (i == firstArg) dst = secondArg; + if (i == secondArg) dst = firstArg; + perms[i] = dst; + swappedType = swappedType.appendParameterTypes(mtype.parameterType(dst)); + } + return permuteArguments(mh, swappedType, perms); + } + + private static MethodHandle reachabilityFenceHandle(Class type) { + return MH_REACHABILITY_FENCE.asType(MethodType.methodType(void.class, type)); + } + + public static void handleUncaughtException(Throwable t) { + if (t != null) { + try { + t.printStackTrace(); + System.err.println("Unrecoverable uncaught exception encountered. The VM will now exit"); + } finally { + JLA.exit(1); + } + } + } + + public static void checkNative(MemorySegment segment) { + if (!segment.isNative()) { + throw new IllegalArgumentException("Heap segment not allowed: " + segment); + } + } + + public static long unboxSegment(MemorySegment segment) { + checkNative(segment); + return segment.address(); + } + + public static void checkExceptions(MethodHandle target) { + Class[] exceptions = JLIA.exceptionTypes(target); + if (exceptions != null && exceptions.length != 0) { + throw new IllegalArgumentException("Target handle may throw exceptions: " + Arrays.toString(exceptions)); + } + } + + public static MethodHandle maybeInsertAllocator(FunctionDescriptor descriptor, MethodHandle handle) { + if (descriptor.returnLayout().isEmpty() || !(descriptor.returnLayout().get() instanceof GroupLayout)) { + // not returning segment, just insert a throwing allocator + handle = insertArguments(handle, 1, THROWING_ALLOCATOR); + } + return handle; + } + + public static MethodHandle maybeCheckCaptureSegment(MethodHandle handle, LinkerOptions options) { + if (options.hasCapturedCallState()) { + // (, SegmentAllocator, , ...) -> ... + handle = MethodHandles.filterArguments(handle, 2, MH_CHECK_CAPTURE_SEGMENT); + } + return handle; + } + + @ForceInline + public static MemorySegment checkCaptureSegment(MemorySegment captureSegment) { + Objects.requireNonNull(captureSegment); + if (captureSegment.equals(MemorySegment.NULL)) { + throw new IllegalArgumentException("Capture segment is NULL: " + captureSegment); + } + return captureSegment.asSlice(0, CapturableState.LAYOUT); + } + + @ForceInline + public static void checkSymbol(MemorySegment symbol) { + Objects.requireNonNull(symbol); + if (symbol.equals(MemorySegment.NULL)) + throw new IllegalArgumentException("Symbol is NULL: " + symbol); + } + + static void checkType(Class actualType, Class expectedType) { + if (expectedType != actualType) { + throw new IllegalArgumentException( + String.format("Invalid operand type: %s. %s expected", actualType, expectedType)); + } + } + + public static boolean isPowerOfTwo(int width) { + return Integer.bitCount(width) == 1; + } + + static long pickChunkOffset(long chunkOffset, long byteWidth, int chunkWidth) { + return ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN + ? byteWidth - chunkWidth - chunkOffset + : chunkOffset; + } + + public static Arena newBoundedArena(long size) { + return new Arena() { + final Arena arena = Arena.ofConfined(); + final SegmentAllocator slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size)); + + @Override + public Scope scope() { + return arena.scope(); + } + + @Override + public void close() { + arena.close(); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + return slicingAllocator.allocate(byteSize, byteAlignment); + } + }; + } + + public static Arena newEmptyArena() { + return new Arena() { + final Arena arena = Arena.ofConfined(); + + @Override + public Scope scope() { + return arena.scope(); + } + + @Override + public void close() { + arena.close(); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + throw new UnsupportedOperationException(); + } + }; + } + + static void writeOverSized(MemorySegment ptr, Class type, Object o) { + // use VH_LONG for integers to zero out the whole register in the process + if (type == long.class) { + ptr.set(JAVA_LONG_UNALIGNED, 0, (long) o); + } else if (type == int.class) { + ptr.set(JAVA_LONG_UNALIGNED, 0, (int) o); + } else if (type == short.class) { + ptr.set(JAVA_LONG_UNALIGNED, 0, (short) o); + } else if (type == char.class) { + ptr.set(JAVA_LONG_UNALIGNED, 0, (char) o); + } else if (type == byte.class) { + ptr.set(JAVA_LONG_UNALIGNED, 0, (byte) o); + } else if (type == float.class) { + ptr.set(JAVA_FLOAT_UNALIGNED, 0, (float) o); + } else if (type == double.class) { + ptr.set(JAVA_DOUBLE_UNALIGNED, 0, (double) o); + } else if (type == boolean.class) { + boolean b = (boolean)o; + ptr.set(JAVA_LONG_UNALIGNED, 0, b ? (long)1 : (long)0); + } else { + throw new IllegalArgumentException("Unsupported carrier: " + type); + } + } + + static void write(MemorySegment ptr, long offset, Class type, Object o) { + if (type == long.class) { + ptr.set(JAVA_LONG_UNALIGNED, offset, (long) o); + } else if (type == int.class) { + ptr.set(JAVA_INT_UNALIGNED, offset, (int) o); + } else if (type == short.class) { + ptr.set(JAVA_SHORT_UNALIGNED, offset, (short) o); + } else if (type == char.class) { + ptr.set(JAVA_CHAR_UNALIGNED, offset, (char) o); + } else if (type == byte.class) { + ptr.set(JAVA_BYTE, offset, (byte) o); + } else if (type == float.class) { + ptr.set(JAVA_FLOAT_UNALIGNED, offset, (float) o); + } else if (type == double.class) { + ptr.set(JAVA_DOUBLE_UNALIGNED, offset, (double) o); + } else if (type == boolean.class) { + ptr.set(JAVA_BOOLEAN, offset, (boolean) o); + } else { + throw new IllegalArgumentException("Unsupported carrier: " + type); + } + } + + static Object read(MemorySegment ptr, long offset, Class type) { + if (type == long.class) { + return ptr.get(JAVA_LONG_UNALIGNED, offset); + } else if (type == int.class) { + return ptr.get(JAVA_INT_UNALIGNED, offset); + } else if (type == short.class) { + return ptr.get(JAVA_SHORT_UNALIGNED, offset); + } else if (type == char.class) { + return ptr.get(JAVA_CHAR_UNALIGNED, offset); + } else if (type == byte.class) { + return ptr.get(JAVA_BYTE, offset); + } else if (type == float.class) { + return ptr.get(JAVA_FLOAT_UNALIGNED, offset); + } else if (type == double.class) { + return ptr.get(JAVA_DOUBLE_UNALIGNED, offset); + } else if (type == boolean.class) { + return ptr.get(JAVA_BOOLEAN, offset); + } else { + throw new IllegalArgumentException("Unsupported carrier: " + type); + } + } + + public static Map canonicalLayouts(ValueLayout longLayout, ValueLayout sizetLayout, ValueLayout wchartLayout) { + return Map.ofEntries( + // specified canonical layouts + Map.entry("bool", ValueLayout.JAVA_BOOLEAN), + Map.entry("char", ValueLayout.JAVA_BYTE), + Map.entry("short", ValueLayout.JAVA_SHORT), + Map.entry("int", ValueLayout.JAVA_INT), + Map.entry("float", ValueLayout.JAVA_FLOAT), + Map.entry("long", longLayout), + Map.entry("long long", ValueLayout.JAVA_LONG), + Map.entry("double", ValueLayout.JAVA_DOUBLE), + Map.entry("void*", ValueLayout.ADDRESS), + Map.entry("size_t", sizetLayout), + Map.entry("wchar_t", wchartLayout), + // unspecified size-dependent layouts + Map.entry("int8_t", ValueLayout.JAVA_BYTE), + Map.entry("int16_t", ValueLayout.JAVA_SHORT), + Map.entry("int32_t", ValueLayout.JAVA_INT), + Map.entry("int64_t", ValueLayout.JAVA_LONG), + // unspecified JNI layouts + Map.entry("jboolean", ValueLayout.JAVA_BOOLEAN), + Map.entry("jchar", ValueLayout.JAVA_CHAR), + Map.entry("jbyte", ValueLayout.JAVA_BYTE), + Map.entry("jshort", ValueLayout.JAVA_SHORT), + Map.entry("jint", ValueLayout.JAVA_INT), + Map.entry("jlong", ValueLayout.JAVA_LONG), + Map.entry("jfloat", ValueLayout.JAVA_FLOAT), + Map.entry("jdouble", ValueLayout.JAVA_DOUBLE) + ); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache$Node.class b/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache$Node.class new file mode 100644 index 00000000..75b7781c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache$Node.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache.class b/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache.class new file mode 100644 index 00000000..9396ceba Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache.java b/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache.java new file mode 100644 index 00000000..04565c66 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/SoftReferenceCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import java.lang.ref.SoftReference; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +final class SoftReferenceCache { + private final Map cache = new ConcurrentHashMap<>(); + + public V get(K key, Function valueFactory) { + return cache + .computeIfAbsent(key, k -> new Node()) // short lock (has to be according to ConcurrentHashMap) + .get(key, valueFactory); // long lock, but just for the particular key + } + + private final class Node { + private volatile SoftReference ref; + + V get(K key, Function valueFactory) { + V result; + if (ref == null || (result = ref.get()) == null) { + synchronized (this) { // don't let threads race on the valueFactory::apply call + if (ref == null || (result = ref.get()) == null) { + result = valueFactory.apply(key); // keep alive + ref = new SoftReference<>(result); + } + } + } + return result; + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/StubLocations.class b/tests/test_data/std/jdk/internal/foreign/abi/StubLocations.class new file mode 100644 index 00000000..e563b5f5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/StubLocations.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/StubLocations.java b/tests/test_data/std/jdk/internal/foreign/abi/StubLocations.java new file mode 100644 index 00000000..e3a45250 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/StubLocations.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +// must keep in sync with StubLocations in VM code +public enum StubLocations { + TARGET_ADDRESS, + RETURN_BUFFER, + CAPTURED_STATE_BUFFER; + + public VMStorage storage(byte type) { + return new VMStorage(type, (short) 8, ordinal()); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker$CallRegs.class b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker$CallRegs.class new file mode 100644 index 00000000..38e6626c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker$CallRegs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker$InvocationData.class b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker$InvocationData.class new file mode 100644 index 00000000..4dd17361 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker$InvocationData.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker.class b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker.class new file mode 100644 index 00000000..09af6742 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker.java b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker.java new file mode 100644 index 00000000..272cfc3c --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/UpcallLinker.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.foreign.abi; + +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import sun.security.action.GetPropertyAction; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +import static java.lang.invoke.MethodHandles.exactInvoker; +import static java.lang.invoke.MethodHandles.insertArguments; +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodType.methodType; +import static sun.security.action.GetBooleanAction.privilegedGetProperty; + +public class UpcallLinker { + private static final boolean DEBUG = + privilegedGetProperty("jdk.internal.foreign.UpcallLinker.DEBUG"); + private static final boolean USE_SPEC = Boolean.parseBoolean( + GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.UpcallLinker.USE_SPEC", "true")); + + private static final MethodHandle MH_invokeInterpBindings; + + static { + try { + MethodHandles.Lookup lookup = lookup(); + MH_invokeInterpBindings = lookup.findStatic(UpcallLinker.class, "invokeInterpBindings", + methodType(Object.class, MethodHandle.class, Object[].class, InvocationData.class)); + } catch (ReflectiveOperationException e) { + throw new InternalError(e); + } + } + + public static UpcallStubFactory makeFactory(MethodType targetType, ABIDescriptor abi, CallingSequence callingSequence) { + assert callingSequence.forUpcall(); + Binding.VMLoad[] argMoves = argMoveBindings(callingSequence); + Binding.VMStore[] retMoves = retMoveBindings(callingSequence); + + MethodType llType = callingSequence.callerMethodType(); + + UnaryOperator doBindingsMaker; + if (USE_SPEC) { + MethodHandle doBindings = BindingSpecializer.specializeUpcall(targetType, callingSequence, abi); + doBindingsMaker = target -> { + MethodHandle handle = MethodHandles.insertArguments(doBindings, 0, target); + assert handle.type() == llType; + return handle; + }; + } else { + Map argIndices = SharedUtils.indexMap(argMoves); + Map retIndices = SharedUtils.indexMap(retMoves); + int spreaderCount = callingSequence.calleeMethodType().parameterCount(); + if (callingSequence.needsReturnBuffer()) { + spreaderCount--; // return buffer is dropped from the argument list + } + final int finalSpreaderCount = spreaderCount; + InvocationData invData = new InvocationData(argIndices, retIndices, callingSequence, retMoves, abi); + MethodHandle doBindings = insertArguments(MH_invokeInterpBindings, 2, invData); + doBindingsMaker = target -> { + target = target.asSpreader(Object[].class, finalSpreaderCount); + MethodHandle handle = MethodHandles.insertArguments(doBindings, 0, target); + handle = handle.asCollector(Object[].class, llType.parameterCount()); + return handle.asType(llType); + }; + } + + VMStorage[] args = Arrays.stream(argMoves).map(Binding.Move::storage).toArray(VMStorage[]::new); + VMStorage[] rets = Arrays.stream(retMoves).map(Binding.Move::storage).toArray(VMStorage[]::new); + CallRegs conv = new CallRegs(args, rets); + return (target, scope) -> { + assert target.type() == targetType; + MethodHandle doBindings = doBindingsMaker.apply(target); + checkPrimitive(doBindings.type()); + doBindings = insertArguments(exactInvoker(doBindings.type()), 0, doBindings); + long entryPoint = makeUpcallStub(doBindings, abi, conv, + callingSequence.needsReturnBuffer(), callingSequence.returnBufferSize()); + if (entryPoint == 0) { + throw new OutOfMemoryError("Failed to allocate upcall stub"); + } + return UpcallStubs.makeUpcall(entryPoint, scope); + }; + } + + private static void checkPrimitive(MethodType type) { + if (!type.returnType().isPrimitive() + || type.parameterList().stream().anyMatch(p -> !p.isPrimitive())) + throw new IllegalArgumentException("MethodHandle type must be primitive: " + type); + } + + private static Stream argMoveBindingsStream(CallingSequence callingSequence) { + return callingSequence.argumentBindings() + .filter(Binding.VMLoad.class::isInstance) + .map(Binding.VMLoad.class::cast); + } + + private static Binding.VMLoad[] argMoveBindings(CallingSequence callingSequence) { + return argMoveBindingsStream(callingSequence) + .toArray(Binding.VMLoad[]::new); + } + + private static Binding.VMStore[] retMoveBindings(CallingSequence callingSequence) { + return callingSequence.returnBindings().stream() + .filter(Binding.VMStore.class::isInstance) + .map(Binding.VMStore.class::cast) + .toArray(Binding.VMStore[]::new); + } + + private record InvocationData(Map argIndexMap, + Map retIndexMap, + CallingSequence callingSequence, + Binding.VMStore[] retMoves, + ABIDescriptor abi) {} + + private static Object invokeInterpBindings(MethodHandle leaf, Object[] lowLevelArgs, InvocationData invData) throws Throwable { + Arena allocator = invData.callingSequence.allocationSize() != 0 + ? SharedUtils.newBoundedArena(invData.callingSequence.allocationSize()) + : SharedUtils.newEmptyArena(); + try (allocator) { + /// Invoke interpreter, got array of high-level arguments back + Object[] highLevelArgs = new Object[invData.callingSequence.calleeMethodType().parameterCount()]; + for (int i = 0; i < highLevelArgs.length; i++) { + highLevelArgs[i] = BindingInterpreter.box(invData.callingSequence.argumentBindings(i), + (storage, type) -> lowLevelArgs[invData.argIndexMap.get(storage)], allocator); + } + + MemorySegment returnBuffer = null; + if (invData.callingSequence.needsReturnBuffer()) { + // this one is for us + returnBuffer = (MemorySegment) highLevelArgs[0]; + Object[] newArgs = new Object[highLevelArgs.length - 1]; + System.arraycopy(highLevelArgs, 1, newArgs, 0, newArgs.length); + highLevelArgs = newArgs; + } + + if (DEBUG) { + System.err.println("Java arguments:"); + System.err.println(Arrays.toString(highLevelArgs).indent(2)); + } + + // invoke our target + Object o = leaf.invoke(highLevelArgs); + + if (DEBUG) { + System.err.println("Java return:"); + System.err.println(Objects.toString(o).indent(2)); + } + + Object[] returnValues = new Object[invData.retIndexMap.size()]; + if (leaf.type().returnType() != void.class) { + BindingInterpreter.unbox(o, invData.callingSequence.returnBindings(), + (storage, value) -> returnValues[invData.retIndexMap.get(storage)] = value, null); + } + + if (returnValues.length == 0) { + return null; + } else if (returnValues.length == 1) { + return returnValues[0]; + } else { + assert invData.callingSequence.needsReturnBuffer(); + + assert returnValues.length == invData.retMoves().length; + int retBufWriteOffset = 0; + for (int i = 0; i < invData.retMoves().length; i++) { + Binding.VMStore store = invData.retMoves()[i]; + Object value = returnValues[i]; + SharedUtils.writeOverSized(returnBuffer.asSlice(retBufWriteOffset), store.type(), value); + retBufWriteOffset += invData.abi.arch.typeSize(store.storage().type()); + } + return null; + } + } catch(Throwable t) { + SharedUtils.handleUncaughtException(t); + return null; + } + } + + // used for transporting data into native code + private record CallRegs(VMStorage[] argRegs, VMStorage[] retRegs) {} + + static native long makeUpcallStub(MethodHandle mh, ABIDescriptor abi, CallRegs conv, + boolean needsReturnBuffer, long returnBufferSize); + + private static native void registerNatives(); + static { + registerNatives(); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs$1.class b/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs$1.class new file mode 100644 index 00000000..cdb81707 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs.class b/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs.class new file mode 100644 index 00000000..7d1cf183 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs.java b/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs.java new file mode 100644 index 00000000..2d4ccfe3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/UpcallStubs.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.Arena; + +import jdk.internal.foreign.MemorySessionImpl; +import jdk.internal.foreign.Utils; + +public final class UpcallStubs { + + private UpcallStubs() { + } + + private static void freeUpcallStub(long stubAddress) { + if (!freeUpcallStub0(stubAddress)) { + throw new IllegalStateException("Not a stub address: " + Utils.toHexString(stubAddress)); + } + } + + // natives + + // returns true if the stub was found (and freed) + private static native boolean freeUpcallStub0(long addr); + + private static native void registerNatives(); + static { + registerNatives(); + } + + @SuppressWarnings("restricted") + static MemorySegment makeUpcall(long entry, Arena arena) { + MemorySessionImpl.toMemorySession(arena).addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() { + @Override + public void cleanup() { + freeUpcallStub(entry); + } + }); + return MemorySegment.ofAddress(entry).reinterpret(arena, null); // restricted + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/VMStorage.class b/tests/test_data/std/jdk/internal/foreign/abi/VMStorage.class new file mode 100644 index 00000000..c033d64d Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/VMStorage.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/VMStorage.java b/tests/test_data/std/jdk/internal/foreign/abi/VMStorage.java new file mode 100644 index 00000000..1237650e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/VMStorage.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi; + +/** + * + * @param type the type of storage. e.g. stack, or which register type (GP, FP, vector) + * @param segmentMaskOrSize the (on stack) size in bytes when type = stack, a register mask otherwise, + * the register mask indicates which segments of a register are used. + * @param indexOrOffset the index is either a register number within a type, or + * a stack offset in bytes if type = stack. + * (a particular platform might add a bias to this in generate code) + * @param debugName the debug name + */ +public record VMStorage(byte type, + short segmentMaskOrSize, + int indexOrOffset, + String debugName) { + + public VMStorage(byte type, short segmentMaskOrSize, int indexOrOffset) { + this(type, segmentMaskOrSize, indexOrOffset, "Stack@" + indexOrOffset); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture$Regs.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture$Regs.class new file mode 100644 index 00000000..cff9b103 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture$Regs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture$StorageType.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture$StorageType.class new file mode 100644 index 00000000..1bd71903 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture$StorageType.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture.class new file mode 100644 index 00000000..430bec67 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture.java new file mode 100644 index 00000000..bfd8dd19 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/AArch64Architecture.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.Architecture; +import jdk.internal.foreign.abi.StubLocations; +import jdk.internal.foreign.abi.VMStorage; + +public final class AArch64Architecture implements Architecture { + public static final Architecture INSTANCE = new AArch64Architecture(); + + private static final short REG64_MASK = 0b0000_0000_0000_0001; + private static final short V128_MASK = 0b0000_0000_0000_0001; + + private static final int INTEGER_REG_SIZE = 8; + private static final int VECTOR_REG_SIZE = 16; + + // Suppresses default constructor, ensuring non-instantiability. + private AArch64Architecture() {} + + @Override + public boolean isStackType(int cls) { + return cls == StorageType.STACK; + } + + @Override + public int typeSize(int cls) { + return switch (cls) { + case StorageType.INTEGER -> INTEGER_REG_SIZE; + case StorageType.VECTOR -> VECTOR_REG_SIZE; + // STACK is deliberately omitted + default -> throw new IllegalArgumentException("Invalid Storage Class: " + cls); + }; + } + + public interface StorageType { + byte INTEGER = 0; + byte VECTOR = 1; + byte STACK = 2; + byte PLACEHOLDER = 3; + } + + public static class Regs { // break circular dependency + public static final VMStorage r0 = integerRegister(0); + public static final VMStorage r1 = integerRegister(1); + public static final VMStorage r2 = integerRegister(2); + public static final VMStorage r3 = integerRegister(3); + public static final VMStorage r4 = integerRegister(4); + public static final VMStorage r5 = integerRegister(5); + public static final VMStorage r6 = integerRegister(6); + public static final VMStorage r7 = integerRegister(7); + public static final VMStorage r8 = integerRegister(8); + public static final VMStorage r9 = integerRegister(9); + public static final VMStorage r10 = integerRegister(10); + public static final VMStorage r11 = integerRegister(11); + public static final VMStorage r12 = integerRegister(12); + public static final VMStorage r13 = integerRegister(13); + public static final VMStorage r14 = integerRegister(14); + public static final VMStorage r15 = integerRegister(15); + public static final VMStorage r16 = integerRegister(16); + public static final VMStorage r17 = integerRegister(17); + public static final VMStorage r18 = integerRegister(18); + public static final VMStorage r19 = integerRegister(19); + public static final VMStorage r20 = integerRegister(20); + public static final VMStorage r21 = integerRegister(21); + public static final VMStorage r22 = integerRegister(22); + public static final VMStorage r23 = integerRegister(23); + public static final VMStorage r24 = integerRegister(24); + public static final VMStorage r25 = integerRegister(25); + public static final VMStorage r26 = integerRegister(26); + public static final VMStorage r27 = integerRegister(27); + public static final VMStorage r28 = integerRegister(28); + public static final VMStorage r29 = integerRegister(29); + public static final VMStorage r30 = integerRegister(30); + public static final VMStorage r31 = integerRegister(31); + public static final VMStorage v0 = vectorRegister(0); + public static final VMStorage v1 = vectorRegister(1); + public static final VMStorage v2 = vectorRegister(2); + public static final VMStorage v3 = vectorRegister(3); + public static final VMStorage v4 = vectorRegister(4); + public static final VMStorage v5 = vectorRegister(5); + public static final VMStorage v6 = vectorRegister(6); + public static final VMStorage v7 = vectorRegister(7); + public static final VMStorage v8 = vectorRegister(8); + public static final VMStorage v9 = vectorRegister(9); + public static final VMStorage v10 = vectorRegister(10); + public static final VMStorage v11 = vectorRegister(11); + public static final VMStorage v12 = vectorRegister(12); + public static final VMStorage v13 = vectorRegister(13); + public static final VMStorage v14 = vectorRegister(14); + public static final VMStorage v15 = vectorRegister(15); + public static final VMStorage v16 = vectorRegister(16); + public static final VMStorage v17 = vectorRegister(17); + public static final VMStorage v18 = vectorRegister(18); + public static final VMStorage v19 = vectorRegister(19); + public static final VMStorage v20 = vectorRegister(20); + public static final VMStorage v21 = vectorRegister(21); + public static final VMStorage v22 = vectorRegister(22); + public static final VMStorage v23 = vectorRegister(23); + public static final VMStorage v24 = vectorRegister(24); + public static final VMStorage v25 = vectorRegister(25); + public static final VMStorage v26 = vectorRegister(26); + public static final VMStorage v27 = vectorRegister(27); + public static final VMStorage v28 = vectorRegister(28); + public static final VMStorage v29 = vectorRegister(29); + public static final VMStorage v30 = vectorRegister(30); + public static final VMStorage v31 = vectorRegister(31); + } + + private static VMStorage integerRegister(int index) { + return new VMStorage(StorageType.INTEGER, REG64_MASK, index, "r" + index); + } + + private static VMStorage vectorRegister(int index) { + return new VMStorage(StorageType.VECTOR, V128_MASK, index, "v" + index); + } + + public static VMStorage stackStorage(short size, int byteOffset) { + return new VMStorage(StorageType.STACK, size, byteOffset); + } + + public static ABIDescriptor abiFor(VMStorage[] inputIntRegs, + VMStorage[] inputVectorRegs, + VMStorage[] outputIntRegs, + VMStorage[] outputVectorRegs, + VMStorage[] volatileIntRegs, + VMStorage[] volatileVectorRegs, + int stackAlignment, + int shadowSpace, + VMStorage scratch1, VMStorage scratch2) { + return new ABIDescriptor( + INSTANCE, + new VMStorage[][] { + inputIntRegs, + inputVectorRegs, + }, + new VMStorage[][] { + outputIntRegs, + outputVectorRegs, + }, + new VMStorage[][] { + volatileIntRegs, + volatileVectorRegs, + }, + stackAlignment, + shadowSpace, + scratch1, scratch2, + StubLocations.TARGET_ADDRESS.storage(StorageType.PLACEHOLDER), + StubLocations.RETURN_BUFFER.storage(StorageType.PLACEHOLDER), + StubLocations.CAPTURED_STATE_BUFFER.storage(StorageType.PLACEHOLDER)); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$1.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$1.class new file mode 100644 index 00000000..1caaa0b5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$BindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$BindingCalculator.class new file mode 100644 index 00000000..8ec6edc3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$BindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$Bindings.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$Bindings.class new file mode 100644 index 00000000..de920b75 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$Bindings.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$BoxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$BoxBindingCalculator.class new file mode 100644 index 00000000..c00905e0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$BoxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$StorageCalculator$StructStorage.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$StorageCalculator$StructStorage.class new file mode 100644 index 00000000..b18fcf6d Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$StorageCalculator$StructStorage.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$StorageCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$StorageCalculator.class new file mode 100644 index 00000000..4e6c7e2d Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$StorageCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$UnboxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$UnboxBindingCalculator.class new file mode 100644 index 00000000..4183abd0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger$UnboxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger.class new file mode 100644 index 00000000..eb399a22 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger.java new file mode 100644 index 00000000..9eb4910e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/CallArranger.java @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2022, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.CallingSequenceBuilder; +import jdk.internal.foreign.abi.DowncallLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64CallArranger; +import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64CallArranger; +import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64CallArranger; +import jdk.internal.foreign.Utils; + +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Optional; + +import static jdk.internal.foreign.abi.aarch64.AArch64Architecture.*; +import static jdk.internal.foreign.abi.aarch64.AArch64Architecture.Regs.*; + +/** + * For the AArch64 C ABI specifically, this class uses CallingSequenceBuilder + * to translate a C FunctionDescriptor into a CallingSequence, which can then be turned into a MethodHandle. + * + * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns. + * + * There are minor differences between the ABIs implemented on Linux, macOS, and Windows + * which are handled in sub-classes. Clients should access these through the provided + * public constants CallArranger.LINUX, CallArranger.MACOS, and CallArranger.WINDOWS. + */ +public abstract class CallArranger { + private static final int STACK_SLOT_SIZE = 8; + private static final int MAX_COPY_SIZE = 8; + public static final int MAX_REGISTER_ARGUMENTS = 8; + + private static final VMStorage INDIRECT_RESULT = r8; + + // This is derived from the AAPCS64 spec, restricted to what's + // possible when calling to/from C code. + // + // The indirect result register, r8, is used to return a large + // struct by value. It's treated as an input here as the caller is + // responsible for allocating storage and passing this into the + // function. + // + // Although the AAPCS64 says r0-7 and v0-7 are all valid return + // registers, it's not possible to generate a C function that uses + // r2-7 and v4-7 so, they are omitted here. + protected static final ABIDescriptor C = abiFor( + new VMStorage[] { r0, r1, r2, r3, r4, r5, r6, r7, INDIRECT_RESULT}, + new VMStorage[] { v0, v1, v2, v3, v4, v5, v6, v7 }, + new VMStorage[] { r0, r1 }, + new VMStorage[] { v0, v1, v2, v3 }, + new VMStorage[] { r9, r10, r11, r12, r13, r14, r15 }, + new VMStorage[] { v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30, v31 }, + 16, // Stack is always 16 byte aligned on AArch64 + 0, // No shadow space + r9, r10 // scratch 1 & 2 + ); + + public record Bindings(CallingSequence callingSequence, + boolean isInMemoryReturn) { + } + + public static final CallArranger LINUX = new LinuxAArch64CallArranger(); + public static final CallArranger MACOS = new MacOsAArch64CallArranger(); + public static final CallArranger WINDOWS = new WindowsAArch64CallArranger(); + + /** + * Are variadic arguments assigned to registers as in the standard calling + * convention, or always passed on the stack? + * + * @return true if variadic arguments should be spilled to the stack. + */ + protected abstract boolean varArgsOnStack(); + + /** + * {@return true if this ABI requires sub-slot (smaller than STACK_SLOT_SIZE) packing of arguments on the stack.} + */ + protected abstract boolean requiresSubSlotStackPacking(); + + /** + * Are floating point arguments to variadic functions passed in general purpose registers + * instead of floating point registers? + * + * {@return true if this ABI uses general purpose registers for variadic floating point arguments.} + */ + protected abstract boolean useIntRegsForVariadicFloatingPointArgs(); + + /** + * Should some fields of structs that assigned to registers be passed in registers when there + * are not enough registers for all the fields of the struct? + * + * {@return true if this ABI passes some fields of a struct in registers.} + */ + protected abstract boolean spillsVariadicStructsPartially(); + + /** + * @return The ABIDescriptor used by the CallArranger for the current platform. + */ + protected abstract ABIDescriptor abiDescriptor(); + + protected TypeClass getArgumentClassForBindings(MemoryLayout layout, boolean forVariadicFunction) { + return TypeClass.classifyLayout(layout); + } + + protected CallArranger() {} + + public Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) { + return getBindings(mt, cDesc, forUpcall, LinkerOptions.empty()); + } + + public Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) { + CallingSequenceBuilder csb = new CallingSequenceBuilder(abiDescriptor(), forUpcall, options); + + boolean forVariadicFunction = options.isVariadicFunction(); + + BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, forVariadicFunction, options.allowsHeapAccess()); + BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, forVariadicFunction, false) : new BoxBindingCalculator(false); + + boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout()); + if (returnInMemory) { + csb.addArgumentBindings(MemorySegment.class, SharedUtils.C_POINTER, + argCalc.getIndirectBindings()); + } else if (cDesc.returnLayout().isPresent()) { + Class carrier = mt.returnType(); + MemoryLayout layout = cDesc.returnLayout().get(); + csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout)); + } + + for (int i = 0; i < mt.parameterCount(); i++) { + Class carrier = mt.parameterType(i); + MemoryLayout layout = cDesc.argumentLayouts().get(i); + if (varArgsOnStack() && options.isVarargsIndex(i)) { + argCalc.storageCalculator.adjustForVarArgs(); + } + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } + + return new Bindings(csb.build(), returnInMemory); + } + + public MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, false, options); + + MethodHandle handle = new DowncallLinker(abiDescriptor(), bindings.callingSequence).getBoundMethodHandle(); + + if (bindings.isInMemoryReturn) { + handle = SharedUtils.adaptDowncallForIMR(handle, cDesc, bindings.callingSequence); + } + + return handle; + } + + public UpcallStubFactory arrangeUpcall(MethodType mt, FunctionDescriptor cDesc, + LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, true, options); + final boolean dropReturn = true; /* drop return, since we don't have bindings for it */ + return SharedUtils.arrangeUpcallHelper(mt, bindings.isInMemoryReturn, dropReturn, abiDescriptor(), + bindings.callingSequence); + } + + private static boolean isInMemoryReturn(Optional returnLayout) { + return returnLayout + .filter(GroupLayout.class::isInstance) + .filter(g -> TypeClass.classifyLayout(g) == TypeClass.STRUCT_REFERENCE) + .isPresent(); + } + + class StorageCalculator { + private final boolean forArguments; + private final boolean forVariadicFunction; + private boolean forVarArgs = false; + + private final int[] nRegs = new int[] { 0, 0 }; + private long stackOffset = 0; + + public StorageCalculator(boolean forArguments, boolean forVariadicFunction) { + this.forArguments = forArguments; + this.forVariadicFunction = forVariadicFunction; + } + + private boolean hasRegister(int type) { + return hasEnoughRegisters(type, 1); + } + + private boolean hasEnoughRegisters(int type, int count) { + return nRegs[type] + count <= MAX_REGISTER_ARGUMENTS; + } + + private static Class adjustCarrierForStack(Class carrier) { + if (carrier == float.class) { + carrier = int.class; + } else if (carrier == double.class) { + carrier = long.class; + } + return carrier; + } + + record StructStorage(long offset, Class carrier, int byteWidth, VMStorage storage) {} + + /* + In the simplest case structs are copied in chunks. i.e. the fields don't matter, just the size. + The struct is split into 8-byte chunks, and those chunks are either passed in registers and/or on the stack. + + Homogeneous float aggregates (HFAs) can be copied in a field-wise manner, i.e. the struct is split into it's + fields and those fields are the chunks which are passed. For HFAs the rules are more complicated and ABI based: + + | enough registers | some registers, but not enough | no registers + ----------------+------------------+---------------------------------+------------------------- + Linux | FW in regs | CW on the stack | CW on the stack + macOS, non-VA | FW in regs | FW on the stack | FW on the stack + macOS, VA | FW in regs | CW on the stack | CW on the stack + Windows, non-VF | FW in regs | CW on the stack | CW on the stack + Windows, VF | FW in regs | CW split between regs and stack | CW on the stack + (where FW = Field-wise copy, CW = Chunk-wise copy, VA is a variadic argument, and VF is a variadic function) + + For regular structs, the rules are as follows: + + | enough registers | some registers, but not enough | no registers + ----------------+------------------+---------------------------------+------------------------- + Linux | CW in regs | CW on the stack | CW on the stack + macOS | CW in regs | CW on the stack | CW on the stack + Windows, non-VF | CW in regs | CW on the stack | CW on the stack + Windows, VF | CW in regs | CW split between regs and stack | CW on the stack + */ + StructStorage[] structStorages(GroupLayout layout, boolean forHFA) { + int numChunks = (int)Utils.alignUp(layout.byteSize(), MAX_COPY_SIZE) / MAX_COPY_SIZE; + + int regType = StorageType.INTEGER; + List scalarLayouts = null; + int requiredStorages = numChunks; + if (forHFA) { + regType = StorageType.VECTOR; + scalarLayouts = TypeClass.scalarLayouts(layout); + requiredStorages = scalarLayouts.size(); + } + + boolean hasEnoughRegisters = hasEnoughRegisters(regType, requiredStorages); + + // For the ABI variants that pack arguments spilled to the + // stack, HFA arguments are spilled as if their individual + // fields had been allocated separately rather than as if the + // struct had been spilled as a whole. + boolean useFieldWiseSpill = requiresSubSlotStackPacking() && !forVarArgs; + boolean isFieldWise = forHFA && (hasEnoughRegisters || useFieldWiseSpill); + if (!isFieldWise) { + requiredStorages = numChunks; + } + + boolean spillPartially = forVariadicFunction && spillsVariadicStructsPartially(); + boolean furtherAllocationFromTheStack = !hasEnoughRegisters && !spillPartially; + if (furtherAllocationFromTheStack) { + // Any further allocations for this register type must + // be from the stack. + nRegs[regType] = MAX_REGISTER_ARGUMENTS; + } + + if (requiresSubSlotStackPacking() && !isFieldWise) { + // Pad to the next stack slot boundary instead of packing + // additional arguments into the unused space. + alignStack(STACK_SLOT_SIZE); + } + + StructStorage[] structStorages = new StructStorage[requiredStorages]; + long offset = 0; + for (int i = 0; i < structStorages.length; i++) { + ValueLayout copyLayout; + long copySize; + if (isFieldWise) { + // We should only get here for HFAs, which can't have padding + copyLayout = (ValueLayout) scalarLayouts.get(i); + copySize = Utils.byteWidthOfPrimitive(copyLayout.carrier()); + } else { + // chunk-wise copy + copySize = Math.min(layout.byteSize() - offset, MAX_COPY_SIZE); + boolean useFloat = false; // never use float for chunk-wise copies + copyLayout = SharedUtils.primitiveLayoutForSize(copySize, useFloat); + } + + VMStorage storage = nextStorage(regType, copyLayout); + Class carrier = copyLayout.carrier(); + if (isFieldWise && storage.type() == StorageType.STACK) { + // copyLayout is a field of an HFA + // Don't use floats on the stack + carrier = adjustCarrierForStack(carrier); + } + structStorages[i] = new StructStorage(offset, carrier, (int) copySize, storage); + offset += copyLayout.byteSize(); + } + + if (requiresSubSlotStackPacking() && !isFieldWise) { + // Pad to the next stack slot boundary instead of packing + // additional arguments into the unused space. + alignStack(STACK_SLOT_SIZE); + } + + return structStorages; + } + + private void alignStack(long alignment) { + stackOffset = Utils.alignUp(stackOffset, alignment); + } + + // allocate a single ValueLayout, either in a register or on the stack + VMStorage nextStorage(int type, ValueLayout layout) { + return hasRegister(type) ? regAlloc(type) : stackAlloc(layout); + } + + private VMStorage regAlloc(int type) { + ABIDescriptor abiDescriptor = abiDescriptor(); + VMStorage[] source = (forArguments ? abiDescriptor.inputStorage : abiDescriptor.outputStorage)[type]; + return source[nRegs[type]++]; + } + + private VMStorage stackAlloc(ValueLayout layout) { + assert forArguments : "no stack returns"; + long stackSlotAlignment = requiresSubSlotStackPacking() && !forVarArgs + ? layout.byteAlignment() + : Math.max(layout.byteAlignment(), STACK_SLOT_SIZE); + long alignedStackOffset = Utils.alignUp(stackOffset, stackSlotAlignment); + + short encodedSize = (short) layout.byteSize(); + assert (encodedSize & 0xFFFF) == layout.byteSize(); + + VMStorage storage = AArch64Architecture.stackStorage(encodedSize, (int)alignedStackOffset); + stackOffset = alignedStackOffset + layout.byteSize(); + return storage; + } + + void adjustForVarArgs() { + // This system passes all variadic parameters on the stack. Ensure + // no further arguments are allocated to registers. + nRegs[StorageType.INTEGER] = MAX_REGISTER_ARGUMENTS; + nRegs[StorageType.VECTOR] = MAX_REGISTER_ARGUMENTS; + forVarArgs = true; + } + } + + abstract class BindingCalculator { + protected final StorageCalculator storageCalculator; + + protected BindingCalculator(boolean forArguments, boolean forVariadicFunction) { + this.storageCalculator = new StorageCalculator(forArguments, forVariadicFunction); + } + + abstract List getBindings(Class carrier, MemoryLayout layout); + + abstract List getIndirectBindings(); + } + + class UnboxBindingCalculator extends BindingCalculator { + protected final boolean forArguments; + protected final boolean forVariadicFunction; + private final boolean useAddressPairs; + + UnboxBindingCalculator(boolean forArguments, boolean forVariadicFunction, boolean useAddressPairs) { + super(forArguments, forVariadicFunction); + this.forArguments = forArguments; + this.forVariadicFunction = forVariadicFunction; + this.useAddressPairs = useAddressPairs; + } + + @Override + List getIndirectBindings() { + return Binding.builder() + .unboxAddress() + .vmStore(INDIRECT_RESULT, long.class) + .build(); + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = getArgumentClassForBindings(layout, forVariadicFunction); + Binding.Builder bindings = Binding.builder(); + + switch (argumentClass) { + case STRUCT_REGISTER, STRUCT_HFA -> { + assert carrier == MemorySegment.class; + boolean forHFA = argumentClass == TypeClass.STRUCT_HFA; + StorageCalculator.StructStorage[] structStorages + = storageCalculator.structStorages((GroupLayout) layout, forHFA); + + for (int i = 0; i < structStorages.length; i++) { + StorageCalculator.StructStorage structStorage = structStorages[i]; + if (i < structStorages.length - 1) { + bindings.dup(); + } + bindings.bufferLoad(structStorage.offset(), structStorage.carrier(), structStorage.byteWidth()) + .vmStore(structStorage.storage(), structStorage.carrier()); + } + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + bindings.copy(layout) + .unboxAddress(); + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, SharedUtils.C_POINTER); + bindings.vmStore(storage, long.class); + } + case POINTER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, (ValueLayout) layout); + if (useAddressPairs) { + bindings.dup() + .segmentBase() + .vmStore(storage, Object.class) + .segmentOffsetAllowHeap() + .vmStore(null, long.class); + } else { + bindings.unboxAddress(); + bindings.vmStore(storage, long.class); + } + } + case INTEGER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, (ValueLayout) layout); + bindings.vmStore(storage, carrier); + } + case FLOAT -> { + boolean forVariadicFunctionArgs = forArguments && forVariadicFunction; + boolean useIntReg = forVariadicFunctionArgs && useIntRegsForVariadicFloatingPointArgs(); + + int type = useIntReg ? StorageType.INTEGER : StorageType.VECTOR; + VMStorage storage = storageCalculator.nextStorage(type, (ValueLayout) layout); + bindings.vmStore(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } + + class BoxBindingCalculator extends BindingCalculator { + BoxBindingCalculator(boolean forArguments) { + super(forArguments, false); + } + + @Override + List getIndirectBindings() { + return Binding.builder() + .vmLoad(INDIRECT_RESULT, long.class) + .boxAddressRaw(Long.MAX_VALUE, 1) + .build(); + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER, STRUCT_HFA -> { + assert carrier == MemorySegment.class; + boolean forHFA = argumentClass == TypeClass.STRUCT_HFA; + bindings.allocate(layout); + StorageCalculator.StructStorage[] structStorages + = storageCalculator.structStorages((GroupLayout) layout, forHFA); + + for (StorageCalculator.StructStorage structStorage : structStorages) { + bindings.dup(); + bindings.vmLoad(structStorage.storage(), structStorage.carrier()) + .bufferStore(structStorage.offset(), structStorage.carrier(), structStorage.byteWidth()); + } + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, SharedUtils.C_POINTER); + bindings.vmLoad(storage, long.class) + .boxAddress(layout); + } + case POINTER -> { + AddressLayout addressLayout = (AddressLayout) layout; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, addressLayout); + bindings.vmLoad(storage, long.class) + .boxAddressRaw(Utils.pointeeByteSize(addressLayout), Utils.pointeeByteAlign(addressLayout)); + } + case INTEGER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, (ValueLayout) layout); + bindings.vmLoad(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.VECTOR, (ValueLayout) layout); + bindings.vmLoad(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/TypeClass.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/TypeClass.class new file mode 100644 index 00000000..ba1bfc64 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/TypeClass.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/TypeClass.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/TypeClass.java new file mode 100644 index 00000000..4c655de4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/TypeClass.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.ValueLayout; +import java.util.List; +import java.util.ArrayList; + +public enum TypeClass { + STRUCT_REGISTER, + STRUCT_REFERENCE, + STRUCT_HFA, + POINTER, + INTEGER, + FLOAT; + + private static final int MAX_AGGREGATE_REGS_SIZE = 2; + + private static TypeClass classifyValueType(ValueLayout type) { + Class carrier = type.carrier(); + if (carrier == boolean.class || carrier == byte.class || carrier == char.class || + carrier == short.class || carrier == int.class || carrier == long.class) { + return INTEGER; + } else if (carrier == float.class || carrier == double.class) { + return FLOAT; + } else if (carrier == MemorySegment.class) { + return POINTER; + } else { + throw new IllegalStateException("Cannot get here: " + carrier.getName()); + } + } + + static boolean isRegisterAggregate(MemoryLayout type) { + return type.byteSize() <= MAX_AGGREGATE_REGS_SIZE * 8; + } + + static List scalarLayouts(GroupLayout gl) { + List out = new ArrayList<>(); + scalarLayoutsInternal(out, gl); + return out; + } + + private static void scalarLayoutsInternal(List out, GroupLayout gl) { + for (MemoryLayout member : gl.memberLayouts()) { + if (member instanceof GroupLayout memberGl) { + scalarLayoutsInternal(out, memberGl); + } else if (member instanceof SequenceLayout memberSl) { + for (long i = 0; i < memberSl.elementCount(); i++) { + out.add(memberSl.elementLayout()); + } + } else { + // padding or value layouts + out.add(member); + } + } + } + + static boolean isHomogeneousFloatAggregate(MemoryLayout type) { + if (!(type instanceof GroupLayout groupLayout)) + return false; + + List scalarLayouts = scalarLayouts(groupLayout); + + final int numElements = scalarLayouts.size(); + if (numElements > 4 || numElements == 0) + return false; + + MemoryLayout baseType = scalarLayouts.get(0); + + if (!(baseType instanceof ValueLayout)) + return false; + + TypeClass baseArgClass = classifyValueType((ValueLayout) baseType); + if (baseArgClass != FLOAT) + return false; + + for (MemoryLayout elem : scalarLayouts) { + if (!(elem instanceof ValueLayout)) + return false; + + TypeClass argClass = classifyValueType((ValueLayout) elem); + if (elem.byteSize() != baseType.byteSize() || + elem.byteAlignment() != baseType.byteAlignment() || + baseArgClass != argClass) { + return false; + } + } + + return true; + } + + private static TypeClass classifyStructType(MemoryLayout layout) { + if (isHomogeneousFloatAggregate(layout)) { + return TypeClass.STRUCT_HFA; + } else if (isRegisterAggregate(layout)) { + return TypeClass.STRUCT_REGISTER; + } + return TypeClass.STRUCT_REFERENCE; + } + + public static TypeClass classifyLayout(MemoryLayout type) { + if (type instanceof ValueLayout) { + return classifyValueType((ValueLayout) type); + } else if (type instanceof GroupLayout) { + return classifyStructType(type); + } else { + throw new IllegalArgumentException("Unsupported layout: " + type); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64CallArranger.class new file mode 100644 index 00000000..27c5f154 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64CallArranger.java new file mode 100644 index 00000000..d79b3337 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64CallArranger.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64.linux; + +import jdk.internal.foreign.abi.aarch64.CallArranger; +import jdk.internal.foreign.abi.ABIDescriptor; + +/** + * AArch64 CallArranger specialized for Linux ABI. + */ +public class LinuxAArch64CallArranger extends CallArranger { + + @Override + protected boolean varArgsOnStack() { + // Variadic arguments are passed as normal arguments + return false; + } + + @Override + protected boolean requiresSubSlotStackPacking() { + return false; + } + + @Override + protected ABIDescriptor abiDescriptor() { + return C; + } + + @Override + protected boolean useIntRegsForVariadicFloatingPointArgs() { + return false; + } + + @Override + protected boolean spillsVariadicStructsPartially() { + return false; + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker$1Holder.class new file mode 100644 index 00000000..10472d11 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.class new file mode 100644 index 00000000..5d25f66f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java new file mode 100644 index 00000000..4ffd15ae --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64.linux; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.aarch64.CallArranger; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +/** + * ABI implementation based on ARM document "Procedure Call Standard for + * the ARM 64-bit Architecture". + */ +public final class LinuxAArch64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static LinuxAArch64Linker getInstance() { + final class Holder { + private static final LinuxAArch64Linker INSTANCE = new LinuxAArch64Linker(); + } + + return Holder.INSTANCE; + } + + private LinuxAArch64Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.LINUX.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.LINUX.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64CallArranger.class new file mode 100644 index 00000000..10d2f252 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64CallArranger.java new file mode 100644 index 00000000..e099d8cd --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64CallArranger.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64.macos; + +import jdk.internal.foreign.abi.aarch64.CallArranger; +import jdk.internal.foreign.abi.ABIDescriptor; + +/** + * AArch64 CallArranger specialized for macOS ABI. + */ +public class MacOsAArch64CallArranger extends CallArranger { + + @Override + protected boolean varArgsOnStack() { + // Variadic arguments are always passed on the stack + return true; + } + + @Override + protected boolean requiresSubSlotStackPacking() { + return true; + } + + @Override + protected ABIDescriptor abiDescriptor() { + return C; + } + + @Override + protected boolean useIntRegsForVariadicFloatingPointArgs() { + return false; + } + + @Override + protected boolean spillsVariadicStructsPartially() { + return false; + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker$1Holder.class new file mode 100644 index 00000000..d203c5a2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.class new file mode 100644 index 00000000..34286060 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java new file mode 100644 index 00000000..c60f0152 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64.macos; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.aarch64.CallArranger; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +/** + * ABI implementation for macOS on Apple Silicon. Based on AAPCS with + * changes to va_list and passing arguments on the stack. + */ +public final class MacOsAArch64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static MacOsAArch64Linker getInstance() { + final class Holder { + private static final MacOsAArch64Linker INSTANCE = new MacOsAArch64Linker(); + } + + return Holder.INSTANCE; + } + + private MacOsAArch64Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.MACOS.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.MACOS.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.class new file mode 100644 index 00000000..18fa12d4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java new file mode 100644 index 00000000..18df7387 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Arm Limited. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64.windows; + +import jdk.internal.foreign.abi.aarch64.CallArranger; +import jdk.internal.foreign.abi.aarch64.TypeClass; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.VMStorage; + +import java.lang.foreign.MemoryLayout; + +import static jdk.internal.foreign.abi.aarch64.AArch64Architecture.*; +import static jdk.internal.foreign.abi.aarch64.AArch64Architecture.Regs.*; + +/** + * AArch64 CallArranger specialized for Windows ABI. + */ +public class WindowsAArch64CallArranger extends CallArranger { + + private static final VMStorage INDIRECT_RESULT = r8; + + // This is derived from the AAPCS64 spec, restricted to what's + // possible when calling to/from C code. + // + // The indirect result register, r8, is used to return a large + // struct by value. It's treated as an input here as the caller is + // responsible for allocating storage and passing this into the + // function. + // + // Although the AAPCS64 says r0-7 and v0-7 are all valid return + // registers, it's not possible to generate a C function that uses + // r2-7 and v4-7 so, they are omitted here. + private static final ABIDescriptor WindowsAArch64AbiDescriptor = abiFor( + new VMStorage[] { r0, r1, r2, r3, r4, r5, r6, r7, INDIRECT_RESULT}, + new VMStorage[] { v0, v1, v2, v3, v4, v5, v6, v7 }, + new VMStorage[] { r0, r1 }, + new VMStorage[] { v0, v1, v2, v3 }, + new VMStorage[] { r9, r10, r11, r12, r13, r14, r15, r16, r17 }, + new VMStorage[] { v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30, v31 }, + 16, // Stack is always 16 byte aligned on AArch64 + 0, // No shadow space + r9, // target addr reg + r10 // return buffer addr reg + ); + + @Override + protected ABIDescriptor abiDescriptor() { + return WindowsAArch64AbiDescriptor; + } + + @Override + protected boolean varArgsOnStack() { + return false; + } + + @Override + protected boolean requiresSubSlotStackPacking() { + return false; + } + + @Override + protected boolean useIntRegsForVariadicFloatingPointArgs() { + // The Windows ABI requires floating point arguments to be passed in + // general purpose registers when calling variadic functions. + return true; + } + + @Override + protected boolean spillsVariadicStructsPartially() { + return true; + } + + @Override + protected TypeClass getArgumentClassForBindings(MemoryLayout layout, boolean forVariadicFunction) { + TypeClass argumentClass = TypeClass.classifyLayout(layout); + + // HFA struct arguments are classified as STRUCT_REGISTER when + // general purpose registers are being used to pass floating point + // arguments. If the HFA is too big to pass entirely in general + // purpose registers, it is classified as an ordinary struct + // (i.e. as a STRUCT_REFERENCE). + if (argumentClass == TypeClass.STRUCT_HFA && forVariadicFunction) { + // The Windows ABI requires the members of the variadic HFA to be + // passed in general purpose registers but only a STRUCT_HFA that + // is at most 16 bytes can be passed in general purpose registers. + argumentClass = layout.byteSize() <= 16 ? TypeClass.STRUCT_REGISTER : TypeClass.STRUCT_REFERENCE; + } + + return argumentClass; + } +} \ No newline at end of file diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker$1Holder.class new file mode 100644 index 00000000..acb899da Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.class new file mode 100644 index 00000000..0596b5ea Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java new file mode 100644 index 00000000..23fb046a --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, Arm Limited. All rights reserved. + * Copyright (c) 2021, 2022, Microsoft. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.aarch64.windows; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.aarch64.CallArranger; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +/** + * ABI implementation for Windows/AArch64. Based on AAPCS with + * changes to va_list. + */ +public final class WindowsAArch64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR); + + public static WindowsAArch64Linker getInstance() { + class Holder { + private static final WindowsAArch64Linker INSTANCE = new WindowsAArch64Linker(); + } + return Holder.INSTANCE; + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.WINDOWS.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.WINDOWS.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIABI.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIABI.class new file mode 100644 index 00000000..03d74fd0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIABI.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIABI.java b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIABI.java new file mode 100644 index 00000000..89062f5c --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIABI.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.fallback; + +/** + * enum which maps the {@code ffi_abi} enum + */ +enum FFIABI { + DEFAULT(LibFallback.defaultABI()); + + private final int value; + + FFIABI(int abi) { + this.value = abi; + } + + int value() { + return value; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIStatus.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIStatus.class new file mode 100644 index 00000000..2ae18233 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIStatus.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIStatus.java b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIStatus.java new file mode 100644 index 00000000..5ac6a95e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIStatus.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.fallback; + +/** + * See doc: + *

+ * typedef enum { + * FFI_OK = 0, + * FFI_BAD_TYPEDEF, + * FFI_BAD_ABI, + * FFI_BAD_ARGTYPE + * } ffi_status; + */ +enum FFIStatus { + FFI_OK, + FFI_BAD_TYPEDEF, + FFI_BAD_ABI, + FFI_BAD_ARGTYPE; + + static FFIStatus of(int code) { + return switch (code) { + case 0 -> FFI_OK; + case 1 -> FFI_BAD_TYPEDEF; + case 2 -> FFI_BAD_ABI; + case 3 -> FFI_BAD_ARGTYPE; + default -> throw new IllegalArgumentException("Unknown status code: " + code); + }; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIType.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIType.class new file mode 100644 index 00000000..82e5a6a7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIType.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIType.java b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIType.java new file mode 100644 index 00000000..c8fa7297 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FFIType.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.fallback; + +import jdk.internal.foreign.Utils; + +import java.lang.foreign.Arena; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.UnionLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.VarHandle; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Predicate; + +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_BYTE; +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; +import static java.lang.foreign.ValueLayout.JAVA_SHORT; + +/** + * typedef struct _ffi_type + * { + * size_t size; + * unsigned short alignment; + * unsigned short type; + * struct _ffi_type **elements; + * } ffi_type; + */ +class FFIType { + + static final ValueLayout SIZE_T = layoutFor((int)ADDRESS.byteSize()); + private static final ValueLayout UNSIGNED_SHORT = JAVA_SHORT; + private static final StructLayout LAYOUT = Utils.computePaddedStructLayout( + SIZE_T, UNSIGNED_SHORT, UNSIGNED_SHORT.withName("type"), ADDRESS.withName("elements")); + + private static final VarHandle VH_TYPE = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("type")); + private static final VarHandle VH_ELEMENTS = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("elements")); + private static final VarHandle VH_SIZE_T = SIZE_T.varHandle(); + + private static MemorySegment make(List elements, FFIABI abi, Arena scope) { + MemorySegment elementsSeg = scope.allocate((elements.size() + 1) * ADDRESS.byteSize()); + int i = 0; + for (; i < elements.size(); i++) { + MemoryLayout elementLayout = elements.get(i); + MemorySegment elementType = toFFIType(elementLayout, abi, scope); + elementsSeg.setAtIndex(ADDRESS, i, elementType); + } + // elements array is null-terminated + elementsSeg.setAtIndex(ADDRESS, i, MemorySegment.NULL); + + MemorySegment ffiType = scope.allocate(LAYOUT); + VH_TYPE.set(ffiType, 0L, LibFallback.structTag()); + VH_ELEMENTS.set(ffiType, 0L, elementsSeg); + + return ffiType; + } + + private static final Map, MemorySegment> CARRIER_TO_TYPE = Map.of( + boolean.class, LibFallback.uint8Type(), + byte.class, LibFallback.sint8Type(), + short.class, LibFallback.sint16Type(), + char.class, LibFallback.uint16Type(), + int.class, LibFallback.sint32Type(), + long.class, LibFallback.sint64Type(), + float.class, LibFallback.floatType(), + double.class, LibFallback.doubleType(), + MemorySegment.class, LibFallback.pointerType() + ); + + static MemorySegment toFFIType(MemoryLayout layout, FFIABI abi, Arena scope) { + if (layout instanceof GroupLayout grpl) { + if (grpl instanceof StructLayout strl) { + // libffi doesn't want our padding + List filteredLayouts = strl.memberLayouts().stream() + .filter(Predicate.not(PaddingLayout.class::isInstance)) + .toList(); + MemorySegment structType = make(filteredLayouts, abi, scope); + verifyStructType(strl, filteredLayouts, structType, abi); + return structType; + } + assert grpl instanceof UnionLayout; + // JDK-8301800 + throw new IllegalArgumentException("Fallback linker does not support by-value unions: " + grpl); + } else if (layout instanceof SequenceLayout sl) { + List elements = Collections.nCopies(Math.toIntExact(sl.elementCount()), sl.elementLayout()); + return make(elements, abi, scope); + } + return Objects.requireNonNull(CARRIER_TO_TYPE.get(((ValueLayout) layout).carrier())); + } + + // verify layout against what libffi sets + private static void verifyStructType(StructLayout structLayout, List filteredLayouts, MemorySegment structType, + FFIABI abi) { + try (Arena verifyArena = Arena.ofConfined()) { + MemorySegment offsetsOut = verifyArena.allocate(SIZE_T.byteSize() * filteredLayouts.size()); + LibFallback.getStructOffsets(structType, offsetsOut, abi); + long expectedOffset = 0; + int offsetIdx = 0; + for (MemoryLayout element : structLayout.memberLayouts()) { + if (!(element instanceof PaddingLayout)) { + long ffiOffset = sizeTAtIndex(offsetsOut, offsetIdx++); + if (ffiOffset != expectedOffset) { + throw new IllegalArgumentException("Invalid group layout." + + " Offset of '" + element.name().orElse("") + + "': " + expectedOffset + " != " + ffiOffset); + } + } + expectedOffset += element.byteSize(); + } + } + } + + static ValueLayout layoutFor(int byteSize) { + return switch (byteSize) { + case 1 -> JAVA_BYTE; + case 2 -> JAVA_SHORT; + case 4 -> JAVA_INT; + case 8 -> JAVA_LONG; + default -> throw new IllegalStateException("Unsupported size: " + byteSize); + }; + } + + private static long sizeTAtIndex(MemorySegment segment, int index) { + long offset = SIZE_T.scale(0, index); + if (VH_SIZE_T.varType() == long.class) { + return (long) VH_SIZE_T.get(segment, offset); + } else { + return (int) VH_SIZE_T.get(segment, offset); // 'erase' to long + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$1Holder.class new file mode 100644 index 00000000..9e4f0497 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$2Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$2Holder.class new file mode 100644 index 00000000..bba3563e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$2Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$DowncallData.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$DowncallData.class new file mode 100644 index 00000000..f2e18ac9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$DowncallData.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$UpcallData.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$UpcallData.class new file mode 100644 index 00000000..306de5d1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker$UpcallData.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker.class new file mode 100644 index 00000000..9cec6f1a Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker.java b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker.java new file mode 100644 index 00000000..59d3810e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/fallback/FallbackLinker.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.fallback; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.foreign.ValueLayout; +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN; +import static java.lang.foreign.ValueLayout.JAVA_BYTE; +import static java.lang.foreign.ValueLayout.JAVA_CHAR; +import static java.lang.foreign.ValueLayout.JAVA_DOUBLE; +import static java.lang.foreign.ValueLayout.JAVA_FLOAT; +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; +import static java.lang.foreign.ValueLayout.JAVA_SHORT; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import static java.lang.invoke.MethodHandles.foldArguments; +import java.lang.invoke.MethodType; +import java.lang.ref.Reference; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import jdk.internal.foreign.AbstractMemorySegmentImpl; +import jdk.internal.foreign.MemorySessionImpl; +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.CapturableState; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; + +public final class FallbackLinker extends AbstractLinker { + + private static final MethodHandle MH_DO_DOWNCALL; + private static final MethodHandle MH_DO_UPCALL; + + static { + try { + MH_DO_DOWNCALL = MethodHandles.lookup().findStatic(FallbackLinker.class, "doDowncall", + MethodType.methodType(Object.class, SegmentAllocator.class, Object[].class, FallbackLinker.DowncallData.class)); + MH_DO_UPCALL = MethodHandles.lookup().findStatic(FallbackLinker.class, "doUpcall", + MethodType.methodType(void.class, MethodHandle.class, MemorySegment.class, MemorySegment.class, UpcallData.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + public static FallbackLinker getInstance() { + class Holder { + static final FallbackLinker INSTANCE = new FallbackLinker(); + } + return Holder.INSTANCE; + } + + public static boolean isSupported() { + return LibFallback.SUPPORTED; + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + MemorySegment cif = makeCif(inferredMethodType, function, options, Arena.ofAuto()); + + int capturedStateMask = options.capturedCallState() + .mapToInt(CapturableState::mask) + .reduce(0, (a, b) -> a | b); + DowncallData invData = new DowncallData(cif, function.returnLayout().orElse(null), + function.argumentLayouts(), capturedStateMask, options.allowsHeapAccess()); + + MethodHandle target = MethodHandles.insertArguments(MH_DO_DOWNCALL, 2, invData); + + int leadingArguments = 1; // address + MethodType type = inferredMethodType.insertParameterTypes(0, SegmentAllocator.class, MemorySegment.class); + if (capturedStateMask != 0) { + leadingArguments++; + type = type.insertParameterTypes(2, MemorySegment.class); + } + target = target.asCollector(1, Object[].class, inferredMethodType.parameterCount() + leadingArguments); + target = target.asType(type); + target = foldArguments(target, 1, SharedUtils.MH_CHECK_SYMBOL); + target = SharedUtils.swapArguments(target, 0, 1); // normalize parameter order + + return target; + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + MemorySegment cif = makeCif(targetType, function, options, Arena.ofAuto()); + + UpcallData invData = new UpcallData(function.returnLayout().orElse(null), function.argumentLayouts(), cif); + MethodHandle doUpcallMH = MethodHandles.insertArguments(MH_DO_UPCALL, 3, invData); + + return (target, scope) -> { + target = MethodHandles.insertArguments(doUpcallMH, 0, target); + return LibFallback.createClosure(cif, target, scope); + }; + } + + private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, LinkerOptions options, Arena scope) { + FFIABI abi = FFIABI.DEFAULT; + + MemorySegment argTypes = scope.allocate(function.argumentLayouts().size() * ADDRESS.byteSize()); + List argLayouts = function.argumentLayouts(); + for (int i = 0; i < argLayouts.size(); i++) { + MemoryLayout layout = argLayouts.get(i); + argTypes.setAtIndex(ADDRESS, i, FFIType.toFFIType(layout, abi, scope)); + } + + MemorySegment returnType = methodType.returnType() != void.class + ? FFIType.toFFIType(function.returnLayout().orElseThrow(), abi, scope) + : LibFallback.voidType(); + + if (options.isVariadicFunction()) { + int numFixedArgs = options.firstVariadicArgIndex(); + int numTotalArgs = argLayouts.size(); + return LibFallback.prepCifVar(returnType, numFixedArgs, numTotalArgs, argTypes, abi, scope); + } else { + return LibFallback.prepCif(returnType, argLayouts.size(), argTypes, abi, scope); + } + } + + private record DowncallData(MemorySegment cif, MemoryLayout returnLayout, List argLayouts, + int capturedStateMask, boolean allowsHeapAccess) {} + + private static Object doDowncall(SegmentAllocator returnAllocator, Object[] args, DowncallData invData) { + List acquiredSessions = new ArrayList<>(); + try (Arena arena = Arena.ofConfined()) { + int argStart = 0; + Object[] heapBases = invData.allowsHeapAccess() ? new Object[args.length] : null; + + MemorySegment target = (MemorySegment) args[argStart++]; + MemorySessionImpl targetImpl = ((AbstractMemorySegmentImpl) target).sessionImpl(); + targetImpl.acquire0(); + acquiredSessions.add(targetImpl); + + MemorySegment capturedState = null; + if (invData.capturedStateMask() != 0) { + capturedState = SharedUtils.checkCaptureSegment((MemorySegment) args[argStart++]); + MemorySessionImpl capturedStateImpl = ((AbstractMemorySegmentImpl) capturedState).sessionImpl(); + capturedStateImpl.acquire0(); + acquiredSessions.add(capturedStateImpl); + } + + List argLayouts = invData.argLayouts(); + MemorySegment argPtrs = arena.allocate(argLayouts.size() * ADDRESS.byteSize()); + for (int i = 0; i < argLayouts.size(); i++) { + Object arg = args[argStart + i]; + MemoryLayout layout = argLayouts.get(i); + + if (layout instanceof AddressLayout) { + AbstractMemorySegmentImpl ms = (AbstractMemorySegmentImpl) arg; + MemorySessionImpl sessionImpl = ms.sessionImpl(); + sessionImpl.acquire0(); + acquiredSessions.add(sessionImpl); + if (invData.allowsHeapAccess() && !ms.isNative()) { + heapBases[i] = ms.unsafeGetBase(); + // write the offset to the arg segment, add array ptr to it in native code + layout = JAVA_LONG; + arg = ms.address(); + } + } + + MemorySegment argSeg = arena.allocate(layout); + writeValue(arg, layout, argSeg); + argPtrs.setAtIndex(ADDRESS, i, argSeg); + } + + MemorySegment retSeg = null; + if (invData.returnLayout() != null) { + retSeg = (invData.returnLayout() instanceof GroupLayout ? returnAllocator : arena).allocate(invData.returnLayout); + } + + LibFallback.doDowncall(invData.cif, target, retSeg, argPtrs, capturedState, invData.capturedStateMask(), + heapBases, args.length); + + Reference.reachabilityFence(invData.cif()); + + return readValue(retSeg, invData.returnLayout()); + } finally { + for (MemorySessionImpl session : acquiredSessions) { + session.release0(); + } + } + } + + // note that cif is not used, but we store it here to keep it alive + private record UpcallData(MemoryLayout returnLayout, List argLayouts, MemorySegment cif) {} + + @SuppressWarnings("restricted") + private static void doUpcall(MethodHandle target, MemorySegment retPtr, MemorySegment argPtrs, UpcallData data) throws Throwable { + List argLayouts = data.argLayouts(); + int numArgs = argLayouts.size(); + MemoryLayout retLayout = data.returnLayout(); + try (Arena upcallArena = Arena.ofConfined()) { + MemorySegment argsSeg = argPtrs.reinterpret(numArgs * ADDRESS.byteSize(), upcallArena, null); + MemorySegment retSeg = retLayout != null + ? retPtr.reinterpret(retLayout.byteSize(), upcallArena, null) // restricted + : null; + + Object[] args = new Object[numArgs]; + for (int i = 0; i < numArgs; i++) { + MemoryLayout argLayout = argLayouts.get(i); + MemorySegment argPtr = argsSeg.getAtIndex(ADDRESS, i) + .reinterpret(argLayout.byteSize(), upcallArena, null); // restricted + args[i] = readValue(argPtr, argLayout); + } + + Object result = target.invokeWithArguments(args); + + writeValue(result, data.returnLayout(), retSeg); + } + } + + // where + private static void writeValue(Object arg, MemoryLayout layout, MemorySegment argSeg) { + switch (layout) { + case ValueLayout.OfBoolean bl -> argSeg.set(bl, 0, (Boolean) arg); + case ValueLayout.OfByte bl -> argSeg.set(bl, 0, (Byte) arg); + case ValueLayout.OfShort sl -> argSeg.set(sl, 0, (Short) arg); + case ValueLayout.OfChar cl -> argSeg.set(cl, 0, (Character) arg); + case ValueLayout.OfInt il -> argSeg.set(il, 0, (Integer) arg); + case ValueLayout.OfLong ll -> argSeg.set(ll, 0, (Long) arg); + case ValueLayout.OfFloat fl -> argSeg.set(fl, 0, (Float) arg); + case ValueLayout.OfDouble dl -> argSeg.set(dl, 0, (Double) arg); + case AddressLayout al -> argSeg.set(al, 0, (MemorySegment) arg); + case GroupLayout _ -> + MemorySegment.copy((MemorySegment) arg, 0, argSeg, 0, argSeg.byteSize()); // by-value struct + case null, default -> { + assert layout == null; + } + } + } + + private static Object readValue(MemorySegment seg, MemoryLayout layout) { + if (layout instanceof ValueLayout.OfBoolean bl) { + return seg.get(bl, 0); + } else if (layout instanceof ValueLayout.OfByte bl) { + return seg.get(bl, 0); + } else if (layout instanceof ValueLayout.OfShort sl) { + return seg.get(sl, 0); + } else if (layout instanceof ValueLayout.OfChar cl) { + return seg.get(cl, 0); + } else if (layout instanceof ValueLayout.OfInt il) { + return seg.get(il, 0); + } else if (layout instanceof ValueLayout.OfLong ll) { + return seg.get(ll, 0); + } else if (layout instanceof ValueLayout.OfFloat fl) { + return seg.get(fl, 0); + } else if (layout instanceof ValueLayout.OfDouble dl) { + return seg.get(dl, 0); + } else if (layout instanceof AddressLayout al) { + return seg.get(al, 0); + } else if (layout instanceof GroupLayout) { + return seg; + } + assert layout == null; + return null; + } + + @Override + public Map canonicalLayouts() { + // Avoid eager dependency on LibFallback, so we can safely check LibFallback.SUPPORTED + class Holder { + static final Map CANONICAL_LAYOUTS; + + static { + int wchar_size = LibFallback.wcharSize(); + MemoryLayout wchartLayout = switch(wchar_size) { + case 2 -> JAVA_CHAR; // prefer JAVA_CHAR + default -> FFIType.layoutFor(wchar_size); + }; + + CANONICAL_LAYOUTS = Map.ofEntries( + // specified canonical layouts + Map.entry("bool", JAVA_BOOLEAN), + Map.entry("char", JAVA_BYTE), + Map.entry("float", JAVA_FLOAT), + Map.entry("long long", JAVA_LONG.withByteAlignment(LibFallback.longLongAlign())), + Map.entry("double", JAVA_DOUBLE.withByteAlignment(LibFallback.doubleAlign())), + Map.entry("void*", ADDRESS), + // platform-dependent sizes + Map.entry("size_t", FFIType.SIZE_T), + Map.entry("short", FFIType.layoutFor(LibFallback.shortSize())), + Map.entry("int", FFIType.layoutFor(LibFallback.intSize())), + Map.entry("long", FFIType.layoutFor(LibFallback.longSize())), + Map.entry("wchar_t", wchartLayout), + // JNI types + Map.entry("jboolean", JAVA_BOOLEAN), + Map.entry("jchar", JAVA_CHAR), + Map.entry("jbyte", JAVA_BYTE), + Map.entry("jshort", JAVA_SHORT), + Map.entry("jint", JAVA_INT), + Map.entry("jlong", JAVA_LONG), + Map.entry("jfloat", JAVA_FLOAT), + Map.entry("jdouble", JAVA_DOUBLE) + ); + } + } + + return Holder.CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback$1.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback$1.class new file mode 100644 index 00000000..c81df3fe Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback$NativeConstants.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback$NativeConstants.class new file mode 100644 index 00000000..9e25d36c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback$NativeConstants.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback.class b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback.class new file mode 100644 index 00000000..ee055875 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback.java b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback.java new file mode 100644 index 00000000..58d6baf8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/fallback/LibFallback.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.fallback; + +import jdk.internal.foreign.abi.SharedUtils; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + +final class LibFallback { + private LibFallback() {} + + static final boolean SUPPORTED = tryLoadLibrary(); + + @SuppressWarnings("removal") + private static boolean tryLoadLibrary() { + return java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction<>() { + public Boolean run() { + try { + System.loadLibrary("fallbackLinker"); + } catch (UnsatisfiedLinkError ule) { + return false; + } + if (!init()) { + // library failed to initialize. Do not silently mark as unsupported + throw new ExceptionInInitializerError("Fallback library failed to initialize"); + } + return true; + } + }); + } + + static int defaultABI() { return NativeConstants.DEFAULT_ABI; } + + static MemorySegment uint8Type() { return NativeConstants.UINT8_TYPE; } + static MemorySegment sint8Type() { return NativeConstants.SINT8_TYPE; } + static MemorySegment uint16Type() { return NativeConstants.UINT16_TYPE; } + static MemorySegment sint16Type() { return NativeConstants.SINT16_TYPE; } + static MemorySegment sint32Type() { return NativeConstants.SINT32_TYPE; } + static MemorySegment sint64Type() { return NativeConstants.SINT64_TYPE; } + static MemorySegment floatType() { return NativeConstants.FLOAT_TYPE; } + static MemorySegment doubleType() { return NativeConstants.DOUBLE_TYPE; } + static MemorySegment pointerType() { return NativeConstants.POINTER_TYPE; } + static MemorySegment voidType() { return NativeConstants.VOID_TYPE; } + + // platform-dependent types + static int shortSize() { return NativeConstants.SIZEOF_SHORT; } + static int intSize() { return NativeConstants.SIZEOF_INT; } + static int longSize() {return NativeConstants.SIZEOF_LONG; } + static int wcharSize() {return NativeConstants.SIZEOF_WCHAR; } + static int longLongAlign() { return NativeConstants.ALIGNOF_LONG_LONG; } + static int doubleAlign() { return NativeConstants.ALIGNOF_DOUBLE; } + + static short structTag() { return NativeConstants.STRUCT_TAG; } + + private static final MethodType UPCALL_TARGET_TYPE = MethodType.methodType(void.class, MemorySegment.class, MemorySegment.class); + + /** + * Do a libffi based downcall. This method wraps the {@code ffi_call} function + * + * @param cif a pointer to a {@code ffi_cif} struct + * @param target the address of the target function + * @param retPtr a pointer to a buffer into which the return value shall be written, or {@code null} if the target + * function does not return a value + * @param argPtrs a pointer to an array of pointers, which each point to an argument value + * @param capturedState a pointer to a buffer into which captured state is written, or {@code null} if no state is + * to be captured + * @param capturedStateMask the bit mask indicating which state to capture + * + * @see jdk.internal.foreign.abi.CapturableState + */ + static void doDowncall(MemorySegment cif, MemorySegment target, MemorySegment retPtr, MemorySegment argPtrs, + MemorySegment capturedState, int capturedStateMask, + Object[] heapBases, int numArgs) { + doDowncall(cif.address(), target.address(), + retPtr == null ? 0 : retPtr.address(), argPtrs.address(), + capturedState == null ? 0 : capturedState.address(), capturedStateMask, + heapBases, numArgs); + } + + /** + * Wrapper for {@code ffi_prep_cif} + * + * @param returnType a pointer to an @{code ffi_type} describing the return type + * @param numArgs the number of arguments + * @param paramTypes a pointer to an array of pointers, which each point to an {@code ffi_type} describing a + * parameter type + * @param abi the abi to be used + * @param scope the scope into which to allocate the returned {@code ffi_cif} struct + * @return a pointer to a prepared {@code ffi_cif} struct + * + * @throws IllegalStateException if the call to {@code ffi_prep_cif} returns a non-zero status code + */ + static MemorySegment prepCif(MemorySegment returnType, int numArgs, MemorySegment paramTypes, FFIABI abi, + Arena scope) throws IllegalStateException { + MemorySegment cif = scope.allocate(NativeConstants.SIZEOF_CIF); + checkStatus(ffi_prep_cif(cif.address(), abi.value(), numArgs, returnType.address(), paramTypes.address())); + return cif; + } + + /** + * Wrapper for {@code ffi_prep_cif_var}. The variadic version of prep_cif + * + * @param returnType a pointer to an @{code ffi_type} describing the return type + * @param numFixedArgs the number of fixed arguments + * @param numTotalArgs the number of total arguments + * @param paramTypes a pointer to an array of pointers, which each point to an {@code ffi_type} describing a + * parameter type + * @param abi the abi to be used + * @param scope the scope into which to allocate the returned {@code ffi_cif} struct + * @return a pointer to a prepared {@code ffi_cif} struct + * + * @throws IllegalStateException if the call to {@code ffi_prep_cif} returns a non-zero status code + */ + static MemorySegment prepCifVar(MemorySegment returnType, int numFixedArgs, int numTotalArgs, MemorySegment paramTypes, FFIABI abi, + Arena scope) throws IllegalStateException { + MemorySegment cif = scope.allocate(NativeConstants.SIZEOF_CIF); + checkStatus(ffi_prep_cif_var(cif.address(), abi.value(), numFixedArgs, numTotalArgs, returnType.address(), paramTypes.address())); + return cif; + } + + /** + * Create an upcallStub-style closure. This method wraps the {@code ffi_closure_alloc} + * and {@code ffi_prep_closure_loc} functions. + *

+ * The closure will end up calling into {@link #doUpcall(long, long, MethodHandle)} + *

+ * The target method handle should have the type {@code (MemorySegment, MemorySegment) -> void}. The first + * argument is a pointer to the buffer into which the native return value should be written. The second argument + * is a pointer to an array of pointers, which each point to a native argument value. + * + * @param cif a pointer to a {@code ffi_cif} struct + * @param target a method handle that points to the target function + * @param arena the scope to which to attach the created upcall stub + * @return the created upcall stub + * + * @throws IllegalStateException if the call to {@code ffi_prep_closure_loc} returns a non-zero status code + * @throws IllegalArgumentException if {@code target} does not have the right type + */ + @SuppressWarnings("restricted") + static MemorySegment createClosure(MemorySegment cif, MethodHandle target, Arena arena) + throws IllegalStateException, IllegalArgumentException { + if (target.type() != UPCALL_TARGET_TYPE) { + throw new IllegalArgumentException("Target handle has wrong type: " + target.type() + " != " + UPCALL_TARGET_TYPE); + } + + long[] ptrs = new long[3]; + checkStatus(createClosure(cif.address(), target, ptrs)); + long closurePtr = ptrs[0]; + long execPtr = ptrs[1]; + long globalTarget = ptrs[2]; + + return MemorySegment.ofAddress(execPtr) + .reinterpret(arena, unused -> freeClosure(closurePtr, globalTarget)); // restricted + } + + // the target function for a closure call + private static void doUpcall(long retPtr, long argPtrs, MethodHandle target) { + try { + target.invokeExact(MemorySegment.ofAddress(retPtr), MemorySegment.ofAddress(argPtrs)); + } catch (Throwable t) { + SharedUtils.handleUncaughtException(t); + } + } + + /** + * Wrapper for {@code ffi_get_struct_offsets} + * + * @param structType a pointer to an {@code ffi_type} representing a struct + * @param offsetsOut a pointer to an array of {@code size_t}, with one element for each element of the struct. + * This is an 'out' parameter that will be filled in by this call + * @param abi the abi to be used + * + * @throws IllegalStateException if the call to {@code ffi_get_struct_offsets} returns a non-zero status code + */ + static void getStructOffsets(MemorySegment structType, MemorySegment offsetsOut, FFIABI abi) + throws IllegalStateException { + checkStatus(ffi_get_struct_offsets(abi.value(), structType.address(), offsetsOut.address())); + } + + private static void checkStatus(int code) { + FFIStatus status = FFIStatus.of(code); + if (status != FFIStatus.FFI_OK) { + throw new IllegalStateException("libffi call failed with status: " + status); + } + } + + private static native boolean init(); + + private static native long sizeofCif(); + + private static native int createClosure(long cif, Object userData, long[] ptrs); + private static native void freeClosure(long closureAddress, long globalTarget); + private static native void doDowncall(long cif, long fn, long rvalue, long avalues, + long capturedState, int capturedStateMask, + Object[] heapBases, int numArgs); + + private static native int ffi_prep_cif(long cif, int abi, int nargs, long rtype, long atypes); + private static native int ffi_prep_cif_var(long cif, int abi, int nfixedargs, int ntotalargs, long rtype, long atypes); + private static native int ffi_get_struct_offsets(int abi, long type, long offsets); + + private static native int ffi_default_abi(); + private static native short ffi_type_struct(); + + private static native long ffi_type_void(); + private static native long ffi_type_uint8(); + private static native long ffi_type_sint8(); + private static native long ffi_type_uint16(); + private static native long ffi_type_sint16(); + private static native long ffi_type_uint32(); + private static native long ffi_type_sint32(); + private static native long ffi_type_uint64(); + private static native long ffi_type_sint64(); + private static native long ffi_type_float(); + private static native long ffi_type_double(); + private static native long ffi_type_pointer(); + private static native int ffi_sizeof_short(); + private static native int ffi_sizeof_int(); + private static native int ffi_sizeof_long(); + private static native int ffi_sizeof_wchar(); + + private static native int alignof_long_long(); + private static native int alignof_double(); + + // put these in a separate class to avoid an UnsatisfiedLinkError + // when LibFallback is initialized but the library is not present + private static final class NativeConstants { + private NativeConstants() {} + + static final int DEFAULT_ABI = ffi_default_abi(); + + static final MemorySegment UINT8_TYPE = MemorySegment.ofAddress(ffi_type_uint8()); + static final MemorySegment SINT8_TYPE = MemorySegment.ofAddress(ffi_type_sint8()); + static final MemorySegment UINT16_TYPE = MemorySegment.ofAddress(ffi_type_uint16()); + static final MemorySegment SINT16_TYPE = MemorySegment.ofAddress(ffi_type_sint16()); + static final MemorySegment SINT32_TYPE = MemorySegment.ofAddress(ffi_type_sint32()); + static final MemorySegment SINT64_TYPE = MemorySegment.ofAddress(ffi_type_sint64()); + static final MemorySegment FLOAT_TYPE = MemorySegment.ofAddress(ffi_type_float()); + static final MemorySegment DOUBLE_TYPE = MemorySegment.ofAddress(ffi_type_double()); + static final MemorySegment POINTER_TYPE = MemorySegment.ofAddress(ffi_type_pointer()); + static final int SIZEOF_SHORT = ffi_sizeof_short(); + static final int SIZEOF_INT = ffi_sizeof_int(); + static final int SIZEOF_LONG = ffi_sizeof_long(); + static final int SIZEOF_WCHAR = ffi_sizeof_wchar(); + + static final int ALIGNOF_LONG_LONG = alignof_long_long(); + static final int ALIGNOF_DOUBLE = alignof_double(); + + static final MemorySegment VOID_TYPE = MemorySegment.ofAddress(ffi_type_void()); + static final short STRUCT_TAG = ffi_type_struct(); + static final long SIZEOF_CIF = sizeofCif(); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$1.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$1.class new file mode 100644 index 00000000..babd71ad Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$BindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$BindingCalculator.class new file mode 100644 index 00000000..ac48e679 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$BindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$Bindings.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$Bindings.class new file mode 100644 index 00000000..7a6e3e69 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$Bindings.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$BoxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$BoxBindingCalculator.class new file mode 100644 index 00000000..4ac679a8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$BoxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$HfaRegs.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$HfaRegs.class new file mode 100644 index 00000000..9a6b1857 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$HfaRegs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$StorageCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$StorageCalculator.class new file mode 100644 index 00000000..68227974 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$StorageCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$UnboxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$UnboxBindingCalculator.class new file mode 100644 index 00000000..14fccaa8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger$UnboxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger.class new file mode 100644 index 00000000..5ed653f5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger.java new file mode 100644 index 00000000..d07cd79e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/CallArranger.java @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64; + +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.CallingSequenceBuilder; +import jdk.internal.foreign.abi.DowncallLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.abi.ppc64.aix.AixCallArranger; +import jdk.internal.foreign.abi.ppc64.linux.ABIv1CallArranger; +import jdk.internal.foreign.abi.ppc64.linux.ABIv2CallArranger; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Optional; + +import static jdk.internal.foreign.abi.ppc64.PPC64Architecture.*; +import static jdk.internal.foreign.abi.ppc64.PPC64Architecture.Regs.*; + +/** + * For the PPC64 C ABI specifically, this class uses CallingSequenceBuilder + * to translate a C FunctionDescriptor into a CallingSequence, which can then be turned into a MethodHandle. + * + * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns. + * + * There are minor differences between the ABIs implemented on Linux and AIX + * which are handled in sub-classes. Clients should access these through the provided + * public constants CallArranger.ABIv1/2. + */ +public abstract class CallArranger { + final boolean useABIv2 = useABIv2(); + final boolean isAIX = isAIX(); + + private static final int STACK_SLOT_SIZE = 8; + private static final int MAX_COPY_SIZE = 8; + public static final int MAX_REGISTER_ARGUMENTS = 8; + public static final int MAX_FLOAT_REGISTER_ARGUMENTS = 13; + + // This is derived from the 64-Bit ELF v2 ABI spec, restricted to what's + // possible when calling to/from C code. (v1 is compatible, but uses fewer output registers.) + private final ABIDescriptor C = abiFor( + new VMStorage[] { r3, r4, r5, r6, r7, r8, r9, r10 }, // GP input + new VMStorage[] { f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13 }, // FP intput + new VMStorage[] { r3, r4 }, // GP output + new VMStorage[] { f1, f2, f3, f4, f5, f6, f7, f8 }, // FP output + new VMStorage[] { r0, r2, r11, r12 }, // volatile GP (excluding argument registers) + new VMStorage[] { f0 }, // volatile FP (excluding argument registers) + 16, // Stack is always 16 byte aligned on PPC64 + useABIv2 ? 32 : 48, // ABI header (excluding argument register spill slots) + r11, // scratch reg + r12 // target addr reg, otherwise used as scratch reg + ); + + public record Bindings(CallingSequence callingSequence, boolean isInMemoryReturn) {} + + private record HfaRegs(VMStorage[] first, VMStorage[] second) {} + + protected CallArranger() {} + + public static final CallArranger ABIv1 = new ABIv1CallArranger(); + public static final CallArranger ABIv2 = new ABIv2CallArranger(); + public static final CallArranger AIX = new AixCallArranger(); + + /** + * Select ABI version + */ + protected abstract boolean useABIv2(); + protected abstract boolean isAIX(); + + public Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) { + return getBindings(mt, cDesc, forUpcall, LinkerOptions.empty()); + } + + public Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) { + CallingSequenceBuilder csb = new CallingSequenceBuilder(C, forUpcall, options); + + BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess()); + BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false); + + boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout()); + if (returnInMemory) { + Class carrier = MemorySegment.class; + MemoryLayout layout = SharedUtils.C_POINTER; + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } else if (cDesc.returnLayout().isPresent()) { + Class carrier = mt.returnType(); + MemoryLayout layout = cDesc.returnLayout().get(); + csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout)); + } + + for (int i = 0; i < mt.parameterCount(); i++) { + Class carrier = mt.parameterType(i); + MemoryLayout layout = cDesc.argumentLayouts().get(i); + if (options.isVarargsIndex(i)) { + argCalc.storageCalculator.adjustForVarArgs(); + } + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } + + return new Bindings(csb.build(), returnInMemory); + } + + public MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, false, options); + + MethodHandle handle = new DowncallLinker(C, bindings.callingSequence).getBoundMethodHandle(); + + if (bindings.isInMemoryReturn) { + handle = SharedUtils.adaptDowncallForIMR(handle, cDesc, bindings.callingSequence); + } + + return handle; + } + + public UpcallStubFactory arrangeUpcall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, true, options); + + final boolean dropReturn = true; /* drop return, since we don't have bindings for it */ + return SharedUtils.arrangeUpcallHelper(mt, bindings.isInMemoryReturn, dropReturn, C, + bindings.callingSequence); + } + + private boolean isInMemoryReturn(Optional returnLayout) { + return returnLayout + .filter(GroupLayout.class::isInstance) + .filter(layout -> !TypeClass.isStructHFAorReturnRegisterAggregate(layout, useABIv2)) + .isPresent(); + } + + class StorageCalculator { + private final boolean forArguments; + + private final int[] nRegs = new int[] { 0, 0 }; + private long stackOffset = 0; + + public StorageCalculator(boolean forArguments) { + this.forArguments = forArguments; + } + + VMStorage stackAlloc(long size, long alignment) { + long alignedStackOffset = Utils.alignUp(stackOffset, alignment); + + short encodedSize = (short) size; + assert (encodedSize & 0xFFFF) == size; + + VMStorage storage = PPC64Architecture.stackStorage(encodedSize, (int) alignedStackOffset); + stackOffset = alignedStackOffset + size; + return storage; + } + + VMStorage regAlloc(int type) { + // GP regs always need to get reserved even when float regs are used. + int gpRegCnt = 1; + int fpRegCnt = (type == StorageType.INTEGER) ? 0 : 1; + + // Use stack if not enough registers available. + if (type == StorageType.FLOAT && nRegs[StorageType.FLOAT] + fpRegCnt > MAX_FLOAT_REGISTER_ARGUMENTS) { + type = StorageType.INTEGER; // Try gp reg. + } + if (type == StorageType.INTEGER && nRegs[StorageType.INTEGER] + gpRegCnt > MAX_REGISTER_ARGUMENTS) return null; + + VMStorage[] source = (forArguments ? C.inputStorage : C.outputStorage)[type]; + VMStorage result = source[nRegs[type]]; + + nRegs[StorageType.INTEGER] += gpRegCnt; + nRegs[StorageType.FLOAT] += fpRegCnt; + return result; + } + + // Integers need size for int to long conversion (required by ABI). + // FP loads and stores must use the correct IEEE 754 precision format (32/64 bit). + // Note: Can return a GP reg for a float! + VMStorage nextStorage(int type, boolean is32Bit) { + VMStorage reg = regAlloc(type); + // Stack layout computation: We need to count all arguments in order to get the correct + // offset for the next argument which will really use the stack. + // The reserved space for the Parameter Save Area is determined by the DowncallStubGenerator. + VMStorage stack; + if (!useABIv2 && !isAIX && is32Bit) { + stackAlloc(4, STACK_SLOT_SIZE); // Skip first half of stack slot. + stack = stackAlloc(4, 4); + } else { + stack = stackAlloc(is32Bit ? 4 : 8, STACK_SLOT_SIZE); + } + if (reg == null) return stack; + if (is32Bit) { + reg = new VMStorage(reg.type(), PPC64Architecture.REG32_MASK, reg.indexOrOffset()); + } + return reg; + } + + /* The struct is split into 8-byte chunks, and those chunks are passed in registers or on the stack. + ABIv1 requires shifting if the struct occupies more than one 8-byte chunk and the last one is not full. + Here's an example for passing an 11 byte struct with ABIv1: + offset : 0 .... 32 ..... 64 ..... 96 .... 128 + values : xxxxxxxx|yyyyyyyy|zzzzzz??|???????? (can't touch bits 96..128) + Load into int : V +--------+ + | | + +--------+ | + V V + In register : ????????|??zzzzzz (LSBs are zz...z) + Shift left : zzzzzz00|00000000 (LSBs are 00...0) + Write long : V V + Result : xxxxxxxx|yyyyyyyy|zzzzzz00|00000000 + */ + + // Regular struct, no HFA. + VMStorage[] structAlloc(MemoryLayout layout) { + // Allocate enough gp slots (regs and stack) such that the struct fits in them. + int numChunks = (int) Utils.alignUp(layout.byteSize(), MAX_COPY_SIZE) / MAX_COPY_SIZE; + VMStorage[] result = new VMStorage[numChunks]; + for (int i = 0; i < numChunks; i++) { + result[i] = nextStorage(StorageType.INTEGER, false); + } + return result; + } + + HfaRegs hfaAlloc(List scalarLayouts) { + // Determine count and type. + int count = scalarLayouts.size(); + Class elementCarrier = ((ValueLayout) (scalarLayouts.get(0))).carrier(); + int elementSize = (elementCarrier == float.class) ? 4 : 8; + + // Allocate registers. + int fpRegCnt = count; + // Rest will get put into a struct. Compute number of 64 bit slots. + int structSlots = 0; + boolean needOverlapping = false; // See "no partial DW rule" below. + + int availableFpRegs = MAX_FLOAT_REGISTER_ARGUMENTS - nRegs[StorageType.FLOAT]; + if (count > availableFpRegs) { + fpRegCnt = availableFpRegs; + int remainingElements = count - availableFpRegs; + if (elementCarrier == float.class) { + if ((fpRegCnt & 1) != 0) { + needOverlapping = true; + remainingElements--; // After overlapped one. + } + structSlots = (remainingElements + 1) / 2; + } else { + structSlots = remainingElements; + } + } + + VMStorage[] source = (forArguments ? C.inputStorage : C.outputStorage)[StorageType.FLOAT]; + VMStorage[] result = new VMStorage[fpRegCnt + structSlots], + result2 = new VMStorage[fpRegCnt + structSlots]; // For overlapping. + if (elementCarrier == float.class) { + // Mark elements as single precision (32 bit). + for (int i = 0; i < fpRegCnt; i++) { + VMStorage sourceReg = source[nRegs[StorageType.FLOAT] + i]; + result[i] = new VMStorage(StorageType.FLOAT, PPC64Architecture.REG32_MASK, + sourceReg.indexOrOffset()); + } + } else { + for (int i = 0; i < fpRegCnt; i++) { + result[i] = source[nRegs[StorageType.FLOAT] + i]; + } + } + + nRegs[StorageType.FLOAT] += fpRegCnt; + // Reserve GP regs and stack slots for the packed HFA (when using single precision). + int gpRegCnt = (elementCarrier == float.class) ? ((fpRegCnt + 1) / 2) + : fpRegCnt; + nRegs[StorageType.INTEGER] += gpRegCnt; + stackAlloc(fpRegCnt * elementSize, STACK_SLOT_SIZE); + + if (needOverlapping) { + // "no partial DW rule": Put GP reg or stack slot into result2. + // Note: Can only happen with forArguments = true. + VMStorage overlappingReg; + if (nRegs[StorageType.INTEGER] <= MAX_REGISTER_ARGUMENTS) { + VMStorage allocatedGpReg = C.inputStorage[StorageType.INTEGER][nRegs[StorageType.INTEGER] - 1]; + overlappingReg = new VMStorage(StorageType.INTEGER, + PPC64Architecture.REG64_MASK, allocatedGpReg.indexOrOffset()); + } else { + overlappingReg = new VMStorage(StorageType.STACK, + (short) STACK_SLOT_SIZE, (int) stackOffset - 4); + stackOffset += 4; // We now have a 64 bit slot, but reserved only 32 bit before. + } + result2[fpRegCnt - 1] = overlappingReg; + } + + // Allocate rest as struct. + for (int i = 0; i < structSlots; i++) { + result[fpRegCnt + i] = nextStorage(StorageType.INTEGER, false); + } + + return new HfaRegs(result, result2); + } + + void adjustForVarArgs() { + // PPC64 can pass VarArgs in GP regs. But we're not using FP regs. + nRegs[StorageType.FLOAT] = MAX_FLOAT_REGISTER_ARGUMENTS; + } + } + + abstract class BindingCalculator { + protected final StorageCalculator storageCalculator; + + protected BindingCalculator(boolean forArguments) { + this.storageCalculator = new StorageCalculator(forArguments); + } + + abstract List getBindings(Class carrier, MemoryLayout layout); + } + + // Compute recipe for transfering arguments / return values to C from Java. + class UnboxBindingCalculator extends BindingCalculator { + private final boolean useAddressPairs; + + UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) { + super(forArguments); + this.useAddressPairs = useAddressPairs; + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout, useABIv2, isAIX); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER -> { + assert carrier == MemorySegment.class; + VMStorage[] regs = storageCalculator.structAlloc(layout); + final boolean isLargeABIv1Struct = !useABIv2 && + (isAIX || layout.byteSize() > MAX_COPY_SIZE); + long offset = 0; + for (VMStorage storage : regs) { + // Last slot may be partly used. + final long size = Math.min(layout.byteSize() - offset, MAX_COPY_SIZE); + int shiftAmount = 0; + Class type = SharedUtils.primitiveCarrierForSize(size, false); + if (offset + size < layout.byteSize()) { + bindings.dup(); + } else if (isLargeABIv1Struct) { + // Last slot requires shift. + shiftAmount = MAX_COPY_SIZE - (int) size; + } + bindings.bufferLoad(offset, type, (int) size); + if (shiftAmount != 0) { + bindings.shiftLeft(shiftAmount, type) + .vmStore(storage, long.class); + } else { + bindings.vmStore(storage, type); + } + offset += size; + } + } + case STRUCT_HFA -> { + assert carrier == MemorySegment.class; + List scalarLayouts = TypeClass.scalarLayouts((GroupLayout) layout); + HfaRegs regs = storageCalculator.hfaAlloc(scalarLayouts); + final long baseSize = scalarLayouts.get(0).byteSize(); + long offset = 0; + for (int index = 0; index < regs.first().length; index++) { + VMStorage storage = regs.first()[index]; + // Floats are 4 Bytes, Double, GP reg and stack slots 8 Bytes (except maybe last slot). + long size = (baseSize == 4 && + (storage.type() == StorageType.FLOAT || layout.byteSize() - offset < 8)) ? 4 : 8; + Class type = SharedUtils.primitiveCarrierForSize(size, storage.type() == StorageType.FLOAT); + if (offset + size < layout.byteSize()) { + bindings.dup(); + } + bindings.bufferLoad(offset, type) + .vmStore(storage, type); + VMStorage storage2 = regs.second()[index]; + if (storage2 != null) { + // We have a second slot to fill (always 64 bit GP reg or stack slot). + size = 8; + if (offset + size < layout.byteSize()) { + bindings.dup(); + } + bindings.bufferLoad(offset, long.class) + .vmStore(storage2, long.class); + } + offset += size; + } + } + case POINTER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, false); + if (useAddressPairs) { + bindings.dup() + .segmentBase() + .vmStore(storage, Object.class) + .segmentOffsetAllowHeap() + .vmStore(null, long.class); + } else { + bindings.unboxAddress(); + bindings.vmStore(storage, long.class); + } + } + case INTEGER -> { + // ABI requires all int types to get extended to 64 bit. + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, false); + bindings.vmStore(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.FLOAT, carrier == float.class); + bindings.vmStore(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } + + // Compute recipe for transfering arguments / return values from C to Java. + class BoxBindingCalculator extends BindingCalculator { + BoxBindingCalculator(boolean forArguments) { + super(forArguments); + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout, useABIv2, isAIX); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout); + VMStorage[] regs = storageCalculator.structAlloc(layout); + final boolean isLargeABIv1Struct = !useABIv2 && + (isAIX || layout.byteSize() > MAX_COPY_SIZE); + long offset = 0; + for (VMStorage storage : regs) { + // Last slot may be partly used. + final long size = Math.min(layout.byteSize() - offset, MAX_COPY_SIZE); + int shiftAmount = 0; + Class type = SharedUtils.primitiveCarrierForSize(size, false); + if (isLargeABIv1Struct && offset + size >= layout.byteSize()) { + // Last slot requires shift. + shiftAmount = MAX_COPY_SIZE - (int) size; + } + bindings.dup(); + if (shiftAmount != 0) { + bindings.vmLoad(storage, long.class) + .shiftRight(shiftAmount, type); + } else { + bindings.vmLoad(storage, type); + } + bindings.bufferStore(offset, type, (int) size); + offset += size; + } + } + case STRUCT_HFA -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout); + List scalarLayouts = TypeClass.scalarLayouts((GroupLayout) layout); + HfaRegs regs = storageCalculator.hfaAlloc(scalarLayouts); + final long baseSize = scalarLayouts.get(0).byteSize(); + long offset = 0; + for (int index = 0; index < regs.first().length; index++) { + // Use second if available since first one only contains one 32 bit value. + VMStorage storage = regs.second()[index] == null ? regs.first()[index] : regs.second()[index]; + // Floats are 4 Bytes, Double, GP reg and stack slots 8 Bytes (except maybe last slot). + final long size = (baseSize == 4 && + (storage.type() == StorageType.FLOAT || layout.byteSize() - offset < 8)) ? 4 : 8; + Class type = SharedUtils.primitiveCarrierForSize(size, storage.type() == StorageType.FLOAT); + bindings.dup() + .vmLoad(storage, type) + .bufferStore(offset, type); + offset += size; + } + } + case POINTER -> { + AddressLayout addressLayout = (AddressLayout) layout; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, false); + bindings.vmLoad(storage, long.class) + .boxAddressRaw(Utils.pointeeByteSize(addressLayout), Utils.pointeeByteAlign(addressLayout)); + } + case INTEGER -> { + // We could use carrier != long.class for BoxBindingCalculator, but C always uses 64 bit slots. + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, false); + bindings.vmLoad(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.FLOAT, carrier == float.class); + bindings.vmLoad(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture$Regs.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture$Regs.class new file mode 100644 index 00000000..e0892d33 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture$Regs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture$StorageType.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture$StorageType.class new file mode 100644 index 00000000..069fe794 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture$StorageType.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture.class new file mode 100644 index 00000000..5439dad2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture.java new file mode 100644 index 00000000..d9391e40 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/PPC64Architecture.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.Architecture; +import jdk.internal.foreign.abi.StubLocations; +import jdk.internal.foreign.abi.VMStorage; + +public final class PPC64Architecture implements Architecture { + public static final Architecture INSTANCE = new PPC64Architecture(); + + // Needs to be consistent with vmstorage_ppc.hpp. + public static final short REG32_MASK = 0b0000_0000_0000_0001; + public static final short REG64_MASK = 0b0000_0000_0000_0011; + + private static final int INTEGER_REG_SIZE = 8; + private static final int FLOAT_REG_SIZE = 8; + private static final int STACK_SLOT_SIZE = 8; + + // Suppresses default constructor, ensuring non-instantiability. + private PPC64Architecture() { + } + + @Override + public boolean isStackType(int cls) { + return cls == StorageType.STACK; + } + + @Override + public int typeSize(int cls) { + return switch (cls) { + case StorageType.INTEGER -> INTEGER_REG_SIZE; + case StorageType.FLOAT -> FLOAT_REG_SIZE; + // STACK is deliberately omitted + default -> throw new IllegalArgumentException("Invalid Storage Class: " + cls); + }; + } + + public interface StorageType { + byte INTEGER = 0; + byte FLOAT = 1; + byte STACK = 2; + byte PLACEHOLDER = 3; + } + + public static class Regs { // break circular dependency + public static final VMStorage r0 = integerRegister(0); + public static final VMStorage r1 = integerRegister(1); + public static final VMStorage r2 = integerRegister(2); + public static final VMStorage r3 = integerRegister(3); + public static final VMStorage r4 = integerRegister(4); + public static final VMStorage r5 = integerRegister(5); + public static final VMStorage r6 = integerRegister(6); + public static final VMStorage r7 = integerRegister(7); + public static final VMStorage r8 = integerRegister(8); + public static final VMStorage r9 = integerRegister(9); + public static final VMStorage r10 = integerRegister(10); + public static final VMStorage r11 = integerRegister(11); + public static final VMStorage r12 = integerRegister(12); + public static final VMStorage r13 = integerRegister(13); + public static final VMStorage r14 = integerRegister(14); + public static final VMStorage r15 = integerRegister(15); + public static final VMStorage r16 = integerRegister(16); + public static final VMStorage r17 = integerRegister(17); + public static final VMStorage r18 = integerRegister(18); + public static final VMStorage r19 = integerRegister(19); + public static final VMStorage r20 = integerRegister(20); + public static final VMStorage r21 = integerRegister(21); + public static final VMStorage r22 = integerRegister(22); + public static final VMStorage r23 = integerRegister(23); + public static final VMStorage r24 = integerRegister(24); + public static final VMStorage r25 = integerRegister(25); + public static final VMStorage r26 = integerRegister(26); + public static final VMStorage r27 = integerRegister(27); + public static final VMStorage r28 = integerRegister(28); + public static final VMStorage r29 = integerRegister(29); + public static final VMStorage r30 = integerRegister(30); + public static final VMStorage r31 = integerRegister(31); + + public static final VMStorage f0 = floatRegister(0); + public static final VMStorage f1 = floatRegister(1); + public static final VMStorage f2 = floatRegister(2); + public static final VMStorage f3 = floatRegister(3); + public static final VMStorage f4 = floatRegister(4); + public static final VMStorage f5 = floatRegister(5); + public static final VMStorage f6 = floatRegister(6); + public static final VMStorage f7 = floatRegister(7); + public static final VMStorage f8 = floatRegister(8); + public static final VMStorage f9 = floatRegister(9); + public static final VMStorage f10 = floatRegister(10); + public static final VMStorage f11 = floatRegister(11); + public static final VMStorage f12 = floatRegister(12); + public static final VMStorage f13 = floatRegister(13); + public static final VMStorage f14 = floatRegister(14); + public static final VMStorage f15 = floatRegister(15); + public static final VMStorage f16 = floatRegister(16); + public static final VMStorage f17 = floatRegister(17); + public static final VMStorage f18 = floatRegister(18); + public static final VMStorage f19 = floatRegister(19); + public static final VMStorage f20 = floatRegister(20); + public static final VMStorage f21 = floatRegister(21); + public static final VMStorage f22 = floatRegister(22); + public static final VMStorage f23 = floatRegister(23); + public static final VMStorage f24 = floatRegister(24); + public static final VMStorage f25 = floatRegister(25); + public static final VMStorage f26 = floatRegister(26); + public static final VMStorage f27 = floatRegister(27); + public static final VMStorage f28 = floatRegister(28); + public static final VMStorage f29 = floatRegister(29); + public static final VMStorage f30 = floatRegister(30); + public static final VMStorage f31 = floatRegister(31); + } + + private static VMStorage integerRegister(int index) { + return new VMStorage(StorageType.INTEGER, REG64_MASK, index, "r" + index); + } + + private static VMStorage floatRegister(int index) { + return new VMStorage(StorageType.FLOAT, REG64_MASK, index, "v" + index); + } + + public static VMStorage stackStorage(short size, int byteOffset) { + return new VMStorage(StorageType.STACK, size, byteOffset); + } + + public static ABIDescriptor abiFor(VMStorage[] inputIntRegs, + VMStorage[] inputFloatRegs, + VMStorage[] outputIntRegs, + VMStorage[] outputFloatRegs, + VMStorage[] volatileIntRegs, + VMStorage[] volatileFloatRegs, + int stackAlignment, + int shadowSpace, + VMStorage scratch1, VMStorage scratch2) { + return new ABIDescriptor( + INSTANCE, + new VMStorage[][] { + inputIntRegs, + inputFloatRegs, + }, + new VMStorage[][] { + outputIntRegs, + outputFloatRegs, + }, + new VMStorage[][] { + volatileIntRegs, + volatileFloatRegs, + }, + stackAlignment, + shadowSpace, + scratch1, scratch2, + StubLocations.TARGET_ADDRESS.storage(StorageType.PLACEHOLDER), + StubLocations.RETURN_BUFFER.storage(StorageType.PLACEHOLDER), + StubLocations.CAPTURED_STATE_BUFFER.storage(StorageType.PLACEHOLDER)); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/TypeClass.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/TypeClass.class new file mode 100644 index 00000000..d0577e03 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/TypeClass.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/TypeClass.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/TypeClass.java new file mode 100644 index 00000000..b04d4abf --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/TypeClass.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.ValueLayout; +import java.util.List; +import java.util.ArrayList; + +public enum TypeClass { + STRUCT_REGISTER, + STRUCT_HFA, // Homogeneous Float Aggregate + POINTER, + INTEGER, + FLOAT; + + private static final int MAX_RETURN_AGGREGATE_REGS_SIZE = 2; + + private static TypeClass classifyValueType(ValueLayout type) { + Class carrier = type.carrier(); + if (carrier == boolean.class || carrier == byte.class || carrier == char.class || + carrier == short.class || carrier == int.class || carrier == long.class) { + return INTEGER; + } else if (carrier == float.class || carrier == double.class) { + return FLOAT; + } else if (carrier == MemorySegment.class) { + return POINTER; + } else { + throw new IllegalStateException("Cannot get here: " + carrier.getName()); + } + } + + static boolean isReturnRegisterAggregate(MemoryLayout type) { + return type.byteSize() <= MAX_RETURN_AGGREGATE_REGS_SIZE * 8; + } + + static List scalarLayouts(GroupLayout gl) { + List out = new ArrayList<>(); + scalarLayoutsInternal(out, gl); + return out; + } + + private static void scalarLayoutsInternal(List out, GroupLayout gl) { + for (MemoryLayout member : gl.memberLayouts()) { + if (member instanceof GroupLayout memberGl) { + scalarLayoutsInternal(out, memberGl); + } else if (member instanceof SequenceLayout memberSl) { + for (long i = 0; i < memberSl.elementCount(); i++) { + out.add(memberSl.elementLayout()); + } + } else { + // padding or value layouts + out.add(member); + } + } + } + + static boolean isHomogeneousFloatAggregate(MemoryLayout type, boolean useABIv2) { + List scalarLayouts = scalarLayouts((GroupLayout) type); + + final int numElements = scalarLayouts.size(); + if (numElements > (useABIv2 ? 8 : 1) || numElements == 0) + return false; + + MemoryLayout baseType = scalarLayouts.get(0); + + if (!(baseType instanceof ValueLayout)) + return false; + + TypeClass baseArgClass = classifyValueType((ValueLayout) baseType); + if (baseArgClass != FLOAT) + return false; + + for (MemoryLayout elem : scalarLayouts) { + if (!(elem instanceof ValueLayout)) + return false; + + TypeClass argClass = classifyValueType((ValueLayout) elem); + if (elem.byteSize() != baseType.byteSize() || + elem.byteAlignment() != baseType.byteAlignment() || + baseArgClass != argClass) { + return false; + } + } + + return true; + } + + private static TypeClass classifyStructType(MemoryLayout layout, boolean useABIv2, boolean isAIX) { + if (!isAIX && isHomogeneousFloatAggregate(layout, useABIv2)) { + return TypeClass.STRUCT_HFA; + } + return TypeClass.STRUCT_REGISTER; + } + + static boolean isStructHFAorReturnRegisterAggregate(MemoryLayout layout, boolean useABIv2) { + if (!(layout instanceof GroupLayout) || !useABIv2) return false; + return isHomogeneousFloatAggregate(layout, true) || isReturnRegisterAggregate(layout); + } + + public static TypeClass classifyLayout(MemoryLayout type, boolean useABIv2, boolean isAIX) { + if (type instanceof ValueLayout) { + return classifyValueType((ValueLayout) type); + } else if (type instanceof GroupLayout) { + return classifyStructType(type, useABIv2, isAIX); + } else { + throw new IllegalArgumentException("Unhandled type " + type); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixCallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixCallArranger.class new file mode 100644 index 00000000..51dcd112 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixCallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixCallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixCallArranger.java new file mode 100644 index 00000000..9ee9ad1e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixCallArranger.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64.aix; + +import jdk.internal.foreign.abi.ppc64.CallArranger; + +/** + * PPC64 CallArranger specialized for AIX. + */ +public class AixCallArranger extends CallArranger { + + @Override + protected boolean useABIv2() { + return false; + } + + @Override + protected boolean isAIX() { + return true; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker$1Holder.class new file mode 100644 index 00000000..591a227d Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker.class new file mode 100644 index 00000000..745bbb7b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker.java new file mode 100644 index 00000000..503dac3e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/aix/AixPPC64Linker.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64.aix; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.ppc64.CallArranger; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +public final class AixPPC64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static AixPPC64Linker getInstance() { + final class Holder { + private static final AixPPC64Linker INSTANCE = new AixPPC64Linker(); + } + + return Holder.INSTANCE; + } + + private AixPPC64Linker() { + // Ensure there is only one instance + } + + @Override + protected void checkStructMember(MemoryLayout member, long offset) { + // special case double members that are not the first member + // see: https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes + // Note: It is possible to enforce 8-byte alignment by #pragma align (natural) + // Therefore, we use normal checks if we are already 8-byte aligned. + if ((offset % 8 != 0) && (member instanceof ValueLayout vl && vl.carrier() == double.class)) { + if (vl.byteAlignment() != 4) { + throw new IllegalArgumentException("double struct member " + vl + " at offset " + offset + " should be 4-byte aligned"); + } + if (vl.order() != ByteOrder.BIG_ENDIAN) { + throw new IllegalArgumentException("double struct member " + vl + " at offset " + offset + " has an unexpected byte order"); + } + } else { + super.checkStructMember(member, offset); + } + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.AIX.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.AIX.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv1CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv1CallArranger.class new file mode 100644 index 00000000..19d06764 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv1CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv1CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv1CallArranger.java new file mode 100644 index 00000000..a9bdeffd --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv1CallArranger.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64.linux; + +import jdk.internal.foreign.abi.ppc64.CallArranger; + +/** + * PPC64 CallArranger specialized for ABI v1. + */ +public class ABIv1CallArranger extends CallArranger { + + @Override + protected boolean useABIv2() { + return false; + } + + @Override + protected boolean isAIX() { + return false; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv2CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv2CallArranger.class new file mode 100644 index 00000000..a3b3d9ab Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv2CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv2CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv2CallArranger.java new file mode 100644 index 00000000..82cbaa0d --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/ABIv2CallArranger.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64.linux; + +import jdk.internal.foreign.abi.ppc64.CallArranger; + +/** + * PPC64 CallArranger specialized for ABI v2. + */ +public class ABIv2CallArranger extends CallArranger { + + @Override + protected boolean useABIv2() { + return true; + } + + @Override + protected boolean isAIX() { + return false; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker$1Holder.class new file mode 100644 index 00000000..82921a80 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.class new file mode 100644 index 00000000..02177873 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java new file mode 100644 index 00000000..a9f5bb4d --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64.linux; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.ppc64.CallArranger; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +public final class LinuxPPC64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static LinuxPPC64Linker getInstance() { + final class Holder { + private static final LinuxPPC64Linker INSTANCE = new LinuxPPC64Linker(); + } + + return Holder.INSTANCE; + } + + private LinuxPPC64Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.ABIv1.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.ABIv1.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker$1Holder.class new file mode 100644 index 00000000..b0268681 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.class b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.class new file mode 100644 index 00000000..063b9a58 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java new file mode 100644 index 00000000..e8305669 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 SAP SE. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.ppc64.linux; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.ppc64.CallArranger; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +public final class LinuxPPC64leLinker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static LinuxPPC64leLinker getInstance() { + final class Holder { + private static final LinuxPPC64leLinker INSTANCE = new LinuxPPC64leLinker(); + } + + return Holder.INSTANCE; + } + + private LinuxPPC64leLinker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.ABIv2.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.ABIv2.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture$Regs.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture$Regs.class new file mode 100644 index 00000000..b2f4bffc Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture$Regs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture$StorageType.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture$StorageType.class new file mode 100644 index 00000000..439f677b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture$StorageType.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.class new file mode 100644 index 00000000..22b6f2f4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java new file mode 100644 index 00000000..409b685b --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Institute of Software, Chinese Academy of Sciences. + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign.abi.riscv64; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.Architecture; +import jdk.internal.foreign.abi.StubLocations; +import jdk.internal.foreign.abi.VMStorage; + +public final class RISCV64Architecture implements Architecture { + public static final Architecture INSTANCE = new RISCV64Architecture(); + + private static final short REG64_MASK = 0b0000_0000_0000_0001; + private static final short FP_MASK = 0b0000_0000_0000_0001; + + private static final int INTEGER_REG_SIZE = 8; // bytes + private static final int FLOAT_REG_SIZE = 8; + + // Suppresses default constructor, ensuring non-instantiability. + private RISCV64Architecture() {} + + @Override + public boolean isStackType(int cls) { + return cls == StorageType.STACK; + } + + @Override + public int typeSize(int cls) { + return switch (cls) { + case StorageType.INTEGER -> INTEGER_REG_SIZE; + case StorageType.FLOAT -> FLOAT_REG_SIZE; + // STACK is deliberately omitted + default -> throw new IllegalArgumentException("Invalid Storage Class: " + cls); + }; + } + + public interface StorageType { + byte INTEGER = 0; + byte FLOAT = 1; + byte STACK = 2; + byte PLACEHOLDER = 3; + } + + public static class Regs { // break circular dependency + public static final VMStorage x0 = integerRegister(0, "zr"); + public static final VMStorage x1 = integerRegister(1, "ra"); + public static final VMStorage x2 = integerRegister(2, "sp"); + public static final VMStorage x3 = integerRegister(3, "gp"); + public static final VMStorage x4 = integerRegister(4, "tp"); + public static final VMStorage x5 = integerRegister(5, "t0"); + public static final VMStorage x6 = integerRegister(6, "t1"); + public static final VMStorage x7 = integerRegister(7, "t2"); + public static final VMStorage x8 = integerRegister(8, "s0/fp"); + public static final VMStorage x9 = integerRegister(9, "s1"); + public static final VMStorage x10 = integerRegister(10, "a0"); + public static final VMStorage x11 = integerRegister(11, "a1"); + public static final VMStorage x12 = integerRegister(12, "a2"); + public static final VMStorage x13 = integerRegister(13, "a3"); + public static final VMStorage x14 = integerRegister(14, "a4"); + public static final VMStorage x15 = integerRegister(15, "a5"); + public static final VMStorage x16 = integerRegister(16, "a6"); + public static final VMStorage x17 = integerRegister(17, "a7"); + public static final VMStorage x18 = integerRegister(18, "s2"); + public static final VMStorage x19 = integerRegister(19, "s3"); + public static final VMStorage x20 = integerRegister(20, "s4"); + public static final VMStorage x21 = integerRegister(21, "s5"); + public static final VMStorage x22 = integerRegister(22, "s6"); + public static final VMStorage x23 = integerRegister(23, "s7"); + public static final VMStorage x24 = integerRegister(24, "s8"); + public static final VMStorage x25 = integerRegister(25, "s9"); + public static final VMStorage x26 = integerRegister(26, "s10"); + public static final VMStorage x27 = integerRegister(27, "s11"); + public static final VMStorage x28 = integerRegister(28, "t3"); + public static final VMStorage x29 = integerRegister(29, "t4"); + public static final VMStorage x30 = integerRegister(30, "t5"); + public static final VMStorage x31 = integerRegister(31, "t6"); + + public static final VMStorage f0 = floatRegister(0, "ft0"); + public static final VMStorage f1 = floatRegister(1, "ft1"); + public static final VMStorage f2 = floatRegister(2, "ft2"); + public static final VMStorage f3 = floatRegister(3, "ft3"); + public static final VMStorage f4 = floatRegister(4, "ft4"); + public static final VMStorage f5 = floatRegister(5, "ft5"); + public static final VMStorage f6 = floatRegister(6, "ft6"); + public static final VMStorage f7 = floatRegister(7, "ft7"); + public static final VMStorage f8 = floatRegister(8, "fs0"); + public static final VMStorage f9 = floatRegister(9, "fs1"); + public static final VMStorage f10 = floatRegister(10, "fa0"); + public static final VMStorage f11 = floatRegister(11, "fa1"); + public static final VMStorage f12 = floatRegister(12, "fa2"); + public static final VMStorage f13 = floatRegister(13, "fa3"); + public static final VMStorage f14 = floatRegister(14, "fa4"); + public static final VMStorage f15 = floatRegister(15, "fa5"); + public static final VMStorage f16 = floatRegister(16, "fa6"); + public static final VMStorage f17 = floatRegister(17, "fa7"); + public static final VMStorage f18 = floatRegister(18, "fs2"); + public static final VMStorage f19 = floatRegister(19, "fs3"); + public static final VMStorage f20 = floatRegister(20, "fs4"); + public static final VMStorage f21 = floatRegister(21, "fs5"); + public static final VMStorage f22 = floatRegister(22, "fs6"); + public static final VMStorage f23 = floatRegister(23, "fs7"); + public static final VMStorage f24 = floatRegister(24, "fs8"); + public static final VMStorage f25 = floatRegister(25, "fs9"); + public static final VMStorage f26 = floatRegister(26, "fs10"); + public static final VMStorage f27 = floatRegister(27, "fs11"); + public static final VMStorage f28 = floatRegister(28, "ft8"); + public static final VMStorage f29 = floatRegister(29, "ft9"); + public static final VMStorage f30 = floatRegister(30, "ft10"); + public static final VMStorage f31 = floatRegister(31, "ft11"); + } + + private static VMStorage integerRegister(int index, String debugName) { + return new VMStorage(StorageType.INTEGER, REG64_MASK, index, debugName); + } + + private static VMStorage floatRegister(int index, String debugName) { + return new VMStorage(StorageType.FLOAT, FP_MASK, index, debugName); + } + + public static VMStorage stackStorage(short size, int byteOffset) { + return new VMStorage(StorageType.STACK, size, byteOffset); + } + + public static ABIDescriptor abiFor(VMStorage[] inputIntRegs, + VMStorage[] inputFloatRegs, + VMStorage[] outputIntRegs, + VMStorage[] outputFloatRegs, + VMStorage[] volatileIntRegs, + VMStorage[] volatileFloatRegs, + int stackAlignment, + int shadowSpace, + VMStorage scratch1, VMStorage scratch2) { + return new ABIDescriptor( + INSTANCE, + new VMStorage[][]{ + inputIntRegs, + inputFloatRegs, + }, + new VMStorage[][]{ + outputIntRegs, + outputFloatRegs, + }, + new VMStorage[][]{ + volatileIntRegs, + volatileFloatRegs, + }, + stackAlignment, + shadowSpace, + scratch1, scratch2, + StubLocations.TARGET_ADDRESS.storage(StorageType.PLACEHOLDER), + StubLocations.RETURN_BUFFER.storage(StorageType.PLACEHOLDER), + StubLocations.CAPTURED_STATE_BUFFER.storage(StorageType.PLACEHOLDER)); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$1.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$1.class new file mode 100644 index 00000000..50fab6b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$BindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$BindingCalculator.class new file mode 100644 index 00000000..c0ba6674 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$BindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$Bindings.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$Bindings.class new file mode 100644 index 00000000..0000435e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$Bindings.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$BoxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$BoxBindingCalculator.class new file mode 100644 index 00000000..8653bfa4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$BoxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$StorageCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$StorageCalculator.class new file mode 100644 index 00000000..653dbd23 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$StorageCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$UnboxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$UnboxBindingCalculator.class new file mode 100644 index 00000000..13ca2c7c Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger$UnboxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.class new file mode 100644 index 00000000..5a599695 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java new file mode 100644 index 00000000..e8ac3305 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Institute of Software, Chinese Academy of Sciences. + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign.abi.riscv64.linux; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.CallingSequenceBuilder; +import jdk.internal.foreign.abi.DowncallLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.Utils; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static jdk.internal.foreign.abi.riscv64.linux.TypeClass.*; +import static jdk.internal.foreign.abi.riscv64.RISCV64Architecture.*; +import static jdk.internal.foreign.abi.riscv64.RISCV64Architecture.Regs.*; + +/** + * For the RISCV64 C ABI specifically, this class uses CallingSequenceBuilder + * to translate a C FunctionDescriptor into a CallingSequence, which can then be turned into a MethodHandle. + * + * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns. + */ +public class LinuxRISCV64CallArranger { + private static final int STACK_SLOT_SIZE = 8; + public static final int MAX_REGISTER_ARGUMENTS = 8; + private static final ABIDescriptor CLinux = abiFor( + new VMStorage[]{x10, x11, x12, x13, x14, x15, x16, x17}, + new VMStorage[]{f10, f11, f12, f13, f14, f15, f16, f17}, + new VMStorage[]{x10, x11}, + new VMStorage[]{f10, f11}, + new VMStorage[]{x5, x6, x7, x28, x29, x30, x31}, + new VMStorage[]{f0, f1, f2, f3, f4, f5, f6, f7, f28, f29, f30, f31}, + 16, // stackAlignment + 0, // no shadow space + x28, x29 // scratch 1 & 2 + ); + + public record Bindings(CallingSequence callingSequence, + boolean isInMemoryReturn) { + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) { + return getBindings(mt, cDesc, forUpcall, LinkerOptions.empty()); + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) { + CallingSequenceBuilder csb = new CallingSequenceBuilder(CLinux, forUpcall, options); + BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess()); + BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false); + + boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout()); + if (returnInMemory) { + Class carrier = MemorySegment.class; + MemoryLayout layout = SharedUtils.C_POINTER; + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout, false)); + } else if (cDesc.returnLayout().isPresent()) { + Class carrier = mt.returnType(); + MemoryLayout layout = cDesc.returnLayout().get(); + csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout, false)); + } + + for (int i = 0; i < mt.parameterCount(); i++) { + Class carrier = mt.parameterType(i); + MemoryLayout layout = cDesc.argumentLayouts().get(i); + boolean isVar = options.isVarargsIndex(i); + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout, isVar)); + } + + return new Bindings(csb.build(), returnInMemory); + } + + public static MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, false, options); + + MethodHandle handle = new DowncallLinker(CLinux, bindings.callingSequence).getBoundMethodHandle(); + + if (bindings.isInMemoryReturn) { + handle = SharedUtils.adaptDowncallForIMR(handle, cDesc, bindings.callingSequence); + } + + return handle; + } + + public static UpcallStubFactory arrangeUpcall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, true, options); + final boolean dropReturn = true; /* drop return, since we don't have bindings for it */ + return SharedUtils.arrangeUpcallHelper(mt, bindings.isInMemoryReturn, dropReturn, CLinux, + bindings.callingSequence); + } + + private static boolean isInMemoryReturn(Optional returnLayout) { + return returnLayout + .filter(GroupLayout.class::isInstance) + .filter(g -> TypeClass.classifyLayout(g) == TypeClass.STRUCT_REFERENCE) + .isPresent(); + } + + static class StorageCalculator { + private final boolean forArguments; + // next available register index. 0=integerRegIdx, 1=floatRegIdx + private final int IntegerRegIdx = 0; + private final int FloatRegIdx = 1; + private final int[] nRegs = {0, 0}; + + private long stackOffset = 0; + + public StorageCalculator(boolean forArguments) { + this.forArguments = forArguments; + } + + // Aggregates or scalars passed on the stack are aligned to the greatest of + // the type alignment and XLEN bits, but never more than the stack alignment. + void alignStack(long alignment) { + alignment = Utils.alignUp(Math.clamp(alignment, STACK_SLOT_SIZE, 16), STACK_SLOT_SIZE); + stackOffset = Utils.alignUp(stackOffset, alignment); + } + + VMStorage stackAlloc() { + assert forArguments : "no stack returns"; + VMStorage storage = stackStorage((short) STACK_SLOT_SIZE, (int) stackOffset); + stackOffset += STACK_SLOT_SIZE; + return storage; + } + + Optional regAlloc(int storageClass) { + if (nRegs[storageClass] < MAX_REGISTER_ARGUMENTS) { + VMStorage[] source = (forArguments ? CLinux.inputStorage : CLinux.outputStorage)[storageClass]; + Optional result = Optional.of(source[nRegs[storageClass]]); + nRegs[storageClass] += 1; + return result; + } + return Optional.empty(); + } + + VMStorage getStorage(int storageClass) { + Optional storage = regAlloc(storageClass); + if (storage.isPresent()) { + return storage.get(); + } + // If storageClass is StorageType.FLOAT, and no floating-point register is available, + // try to allocate an integer register. + if (storageClass == StorageType.FLOAT) { + storage = regAlloc(StorageType.INTEGER); + if (storage.isPresent()) { + return storage.get(); + } + } + return stackAlloc(); + } + + VMStorage[] getStorages(MemoryLayout layout, boolean isVariadicArg) { + int regCnt = (int) SharedUtils.alignUp(layout.byteSize(), 8) / 8; + if (isVariadicArg && layout.byteAlignment() == 16 && layout.byteSize() <= 16) { + alignStorage(); + // Two registers or stack slots will be allocated, even layout.byteSize <= 8B. + regCnt = 2; + } + VMStorage[] storages = new VMStorage[regCnt]; + for (int i = 0; i < regCnt; i++) { + // use integer calling convention. + storages[i] = getStorage(StorageType.INTEGER); + } + return storages; + } + + boolean regsAvailable(int integerRegs, int floatRegs) { + return nRegs[IntegerRegIdx] + integerRegs <= MAX_REGISTER_ARGUMENTS && + nRegs[FloatRegIdx] + floatRegs <= MAX_REGISTER_ARGUMENTS; + } + + // Variadic arguments with 2 * XLEN-bit alignment and size at most 2 * XLEN bits + // are passed in an aligned register pair (i.e., the first register in the pair + // is even-numbered), or on the stack by value if none is available. + // After a variadic argument has been passed on the stack, all future arguments + // will also be passed on the stack. + void alignStorage() { + if (nRegs[IntegerRegIdx] + 2 <= MAX_REGISTER_ARGUMENTS) { + nRegs[IntegerRegIdx] = (nRegs[IntegerRegIdx] + 1) & -2; + } else { + nRegs[IntegerRegIdx] = MAX_REGISTER_ARGUMENTS; + stackOffset = Utils.alignUp(stackOffset, 16); + } + } + + @Override + public String toString() { + String nReg = "iReg: " + nRegs[IntegerRegIdx] + ", fReg: " + nRegs[FloatRegIdx]; + String stack = ", stackOffset: " + stackOffset; + return "{" + nReg + stack + "}"; + } + } + + abstract static class BindingCalculator { + protected final StorageCalculator storageCalculator; + + @Override + public String toString() { + return storageCalculator.toString(); + } + + protected BindingCalculator(boolean forArguments) { + this.storageCalculator = new LinuxRISCV64CallArranger.StorageCalculator(forArguments); + } + + abstract List getBindings(Class carrier, MemoryLayout layout, boolean isVariadicArg); + + // When handling variadic part, integer calling convention should be used. + static final Map conventionConverterMap = + Map.ofEntries(Map.entry(FLOAT, INTEGER), + Map.entry(STRUCT_REGISTER_F, STRUCT_REGISTER_X), + Map.entry(STRUCT_REGISTER_XF, STRUCT_REGISTER_X)); + } + + static final class UnboxBindingCalculator extends BindingCalculator { + protected final boolean forArguments; + private final boolean useAddressPairs; + + UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) { + super(forArguments); + this.forArguments = forArguments; + this.useAddressPairs = useAddressPairs; + } + + @Override + List getBindings(Class carrier, MemoryLayout layout, boolean isVariadicArg) { + TypeClass typeClass = TypeClass.classifyLayout(layout); + if (isVariadicArg) { + typeClass = BindingCalculator.conventionConverterMap.getOrDefault(typeClass, typeClass); + } + return getBindings(carrier, layout, typeClass, isVariadicArg); + } + + List getBindings(Class carrier, MemoryLayout layout, TypeClass argumentClass, boolean isVariadicArg) { + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case INTEGER -> { + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER); + bindings.vmStore(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT); + bindings.vmStore(storage, carrier); + } + case POINTER -> { + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER); + if (useAddressPairs) { + bindings.dup() + .segmentBase() + .vmStore(storage, Object.class) + .segmentOffsetAllowHeap() + .vmStore(null, long.class); + } else { + bindings.unboxAddress(); + bindings.vmStore(storage, long.class); + } + } + case STRUCT_REGISTER_X -> { + assert carrier == MemorySegment.class; + + // When no register is available, struct will be passed by stack. + // Before allocation, stack must be aligned. + if (!storageCalculator.regsAvailable(1, 0)) { + storageCalculator.alignStack(layout.byteAlignment()); + } + VMStorage[] locations = storageCalculator.getStorages(layout, isVariadicArg); + int locIndex = 0; + long offset = 0; + while (offset < layout.byteSize()) { + final long copy = Math.min(layout.byteSize() - offset, 8); + VMStorage storage = locations[locIndex++]; + Class type = SharedUtils.primitiveCarrierForSize(copy, false); + if (offset + copy < layout.byteSize()) { + bindings.dup(); + } + bindings.bufferLoad(offset, type, (int) copy) + .vmStore(storage, type); + offset += copy; + } + } + case STRUCT_REGISTER_F -> { + assert carrier == MemorySegment.class; + List descs = getFlattenedFields((GroupLayout) layout); + if (storageCalculator.regsAvailable(0, descs.size())) { + for (int i = 0; i < descs.size(); i++) { + FlattenedFieldDesc desc = descs.get(i); + Class type = desc.layout().carrier(); + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT); + if (i < descs.size() - 1) { + bindings.dup(); + } + bindings.bufferLoad(desc.offset(), type) + .vmStore(storage, type); + } + } else { + // If there is not enough register can be used, then fall back to integer calling convention. + return getBindings(carrier, layout, STRUCT_REGISTER_X, isVariadicArg); + } + } + case STRUCT_REGISTER_XF -> { + assert carrier == MemorySegment.class; + if (storageCalculator.regsAvailable(1, 1)) { + List descs = getFlattenedFields((GroupLayout) layout); + for (int i = 0; i < 2; i++) { + FlattenedFieldDesc desc = descs.get(i); + int storageClass; + if (desc.typeClass() == INTEGER) { + storageClass = StorageType.INTEGER; + } else { + storageClass = StorageType.FLOAT; + } + VMStorage storage = storageCalculator.getStorage(storageClass); + Class type = desc.layout().carrier(); + if (i < 1) { + bindings.dup(); + } + bindings.bufferLoad(desc.offset(), type) + .vmStore(storage, type); + } + } else { + return getBindings(carrier, layout, STRUCT_REGISTER_X, isVariadicArg); + } + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + bindings.copy(layout) + .unboxAddress(); + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER); + bindings.vmStore(storage, long.class); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + + return bindings.build(); + } + } + + static class BoxBindingCalculator extends BindingCalculator { + + BoxBindingCalculator(boolean forArguments) { + super(forArguments); + } + + @Override + List getBindings(Class carrier, MemoryLayout layout, boolean isVariadicArg) { + TypeClass typeClass = TypeClass.classifyLayout(layout); + if (isVariadicArg) { + typeClass = BindingCalculator.conventionConverterMap.getOrDefault(typeClass, typeClass); + } + return getBindings(carrier, layout, typeClass, isVariadicArg); + } + + List getBindings(Class carrier, MemoryLayout layout, TypeClass argumentClass, boolean isVariadicArg) { + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case INTEGER -> { + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER); + bindings.vmLoad(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT); + bindings.vmLoad(storage, carrier); + } + case POINTER -> { + AddressLayout addressLayout = (AddressLayout) layout; + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER); + bindings.vmLoad(storage, long.class) + .boxAddressRaw(Utils.pointeeByteSize(addressLayout), Utils.pointeeByteAlign(addressLayout)); + } + case STRUCT_REGISTER_X -> { + assert carrier == MemorySegment.class; + + // When no register is available, struct will be passed by stack. + // Before allocation, stack must be aligned. + if (!storageCalculator.regsAvailable(1, 0)) { + storageCalculator.alignStack(layout.byteAlignment()); + } + bindings.allocate(layout); + VMStorage[] locations = storageCalculator.getStorages(layout, isVariadicArg); + int locIndex = 0; + long offset = 0; + while (offset < layout.byteSize()) { + final long copy = Math.min(layout.byteSize() - offset, 8); + VMStorage storage = locations[locIndex++]; + Class type = SharedUtils.primitiveCarrierForSize(copy, false); + bindings.dup().vmLoad(storage, type) + .bufferStore(offset, type, (int) copy); + offset += copy; + } + } + case STRUCT_REGISTER_F -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout); + List descs = getFlattenedFields((GroupLayout) layout); + if (storageCalculator.regsAvailable(0, descs.size())) { + for (FlattenedFieldDesc desc : descs) { + Class type = desc.layout().carrier(); + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT); + bindings.dup() + .vmLoad(storage, type) + .bufferStore(desc.offset(), type); + } + } else { + return getBindings(carrier, layout, STRUCT_REGISTER_X, isVariadicArg); + } + } + case STRUCT_REGISTER_XF -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout); + if (storageCalculator.regsAvailable(1, 1)) { + List descs = getFlattenedFields((GroupLayout) layout); + for (int i = 0; i < 2; i++) { + FlattenedFieldDesc desc = descs.get(i); + int storageClass; + if (desc.typeClass() == INTEGER) { + storageClass = StorageType.INTEGER; + } else { + storageClass = StorageType.FLOAT; + } + VMStorage storage = storageCalculator.getStorage(storageClass); + Class type = desc.layout().carrier(); + bindings.dup() + .vmLoad(storage, type) + .bufferStore(desc.offset(), type); + } + } else { + return getBindings(carrier, layout, STRUCT_REGISTER_X, isVariadicArg); + } + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER); + bindings.vmLoad(storage, long.class) + .boxAddress(layout); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + + return bindings.build(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker$1Holder.class new file mode 100644 index 00000000..4c325922 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.class new file mode 100644 index 00000000..4f22e473 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java new file mode 100644 index 00000000..706fdacf --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Institute of Software, Chinese Academy of Sciences. + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign.abi.riscv64.linux; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +public final class LinuxRISCV64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static LinuxRISCV64Linker getInstance() { + final class Holder { + private static final LinuxRISCV64Linker INSTANCE = new LinuxRISCV64Linker(); + } + + return Holder.INSTANCE; + } + + private LinuxRISCV64Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return LinuxRISCV64CallArranger.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return LinuxRISCV64CallArranger.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass$FieldCounter.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass$FieldCounter.class new file mode 100644 index 00000000..14f75b5e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass$FieldCounter.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass$FlattenedFieldDesc.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass$FlattenedFieldDesc.class new file mode 100644 index 00000000..8fbdc333 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass$FlattenedFieldDesc.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass.class b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass.class new file mode 100644 index 00000000..d32172d1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java new file mode 100644 index 00000000..e00771d2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Institute of Software, Chinese Academy of Sciences. + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package jdk.internal.foreign.abi.riscv64.linux; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.UnionLayout; +import java.lang.foreign.ValueLayout; +import java.util.ArrayList; +import java.util.List; + +public enum TypeClass { + /* + * STRUCT_REFERENCE: Aggregates larger than 2 * XLEN bits are passed by reference and are replaced + * in the argument list with the address. The address will be passed in a register if at least + * one register is available, otherwise it will be passed on the stack. + * + * STRUCT_REGISTER_F: A struct containing just one floating-point real is passed as though it were + * a standalone floating-point real. A struct containing two floating-point reals is passed in two + * floating-point registers, if neither real is more than ABI_FLEN bits wide and at least two + * floating-point argument registers are available. (The registers need not be an aligned pair.) + * Otherwise, it is passed according to the integer calling convention. + * + * STRUCT_REGISTER_XF: A struct containing one floating-point real and one integer (or bitfield), in either + * order, is passed in a floating-point register and an integer register, provided the floating-point real + * is no more than ABI_FLEN bits wide and the integer is no more than XLEN bits wide, and at least one + * floating-point argument register and at least one integer argument register is available. If the struct + * is not passed in this manner, then it is passed according to the integer calling convention. + * + * STRUCT_REGISTER_X: Aggregates whose total size is no more than XLEN bits are passed in a register, with the + * fields laid out as though they were passed in memory. If no register is available, the aggregate is + * passed on the stack. Aggregates whose total size is no more than 2 * XLEN bits are passed in a pair of + * registers; if only one register is available, the first XLEN bits are passed in a register and the + * remaining bits are passed on the stack. If no registers are available, the aggregate is passed on the stack. + * + * See https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc + * */ + INTEGER, + FLOAT, + POINTER, + STRUCT_REFERENCE, + STRUCT_REGISTER_F, + STRUCT_REGISTER_XF, + STRUCT_REGISTER_X; + + private static final int MAX_AGGREGATE_REGS_SIZE = 2; + + /* + * Struct will be flattened while classifying. That is, struct{struct{int, double}} will be treated + * same as struct{int, double} and struct{int[2]} will be treated same as struct{int, int}. + * */ + private record FieldCounter(long integerCnt, long floatCnt, long pointerCnt) { + static final FieldCounter EMPTY = new FieldCounter(0, 0, 0); + static final FieldCounter SINGLE_INTEGER = new FieldCounter(1, 0, 0); + static final FieldCounter SINGLE_FLOAT = new FieldCounter(0, 1, 0); + static final FieldCounter SINGLE_POINTER = new FieldCounter(0, 0, 1); + + static FieldCounter flatten(MemoryLayout layout) { + switch (layout) { + case ValueLayout valueLayout -> { + return switch (classifyValueType(valueLayout)) { + case INTEGER -> FieldCounter.SINGLE_INTEGER; + case FLOAT -> FieldCounter.SINGLE_FLOAT; + case POINTER -> FieldCounter.SINGLE_POINTER; + default -> throw new IllegalStateException("Should not reach here."); + }; + } + case GroupLayout groupLayout -> { + FieldCounter currCounter = FieldCounter.EMPTY; + for (MemoryLayout memberLayout : groupLayout.memberLayouts()) { + if (memberLayout instanceof PaddingLayout) { + continue; + } + currCounter = currCounter.add(flatten(memberLayout)); + } + return currCounter; + } + case SequenceLayout sequenceLayout -> { + long elementCount = sequenceLayout.elementCount(); + if (elementCount == 0) { + return FieldCounter.EMPTY; + } + return flatten(sequenceLayout.elementLayout()).mul(elementCount); + } + default -> throw new IllegalStateException("Cannot get here: " + layout); + } + } + + FieldCounter mul(long m) { + return new FieldCounter(integerCnt * m, + floatCnt * m, + pointerCnt * m); + } + + FieldCounter add(FieldCounter other) { + return new FieldCounter(integerCnt + other.integerCnt, + floatCnt + other.floatCnt, + pointerCnt + other.pointerCnt); + } + } + + public record FlattenedFieldDesc(TypeClass typeClass, long offset, ValueLayout layout) { } + + private static List getFlattenedFieldsInner(long offset, MemoryLayout layout) { + return switch (layout) { + case ValueLayout valueLayout -> { + TypeClass typeClass = classifyValueType(valueLayout); + yield List.of(switch (typeClass) { + case INTEGER, FLOAT -> new FlattenedFieldDesc(typeClass, offset, valueLayout); + default -> throw new IllegalStateException("Should not reach here."); + }); + } + case GroupLayout groupLayout -> { + List fields = new ArrayList<>(); + for (MemoryLayout memberLayout : groupLayout.memberLayouts()) { + if (memberLayout instanceof PaddingLayout) { + offset += memberLayout.byteSize(); + continue; + } + fields.addAll(getFlattenedFieldsInner(offset, memberLayout)); + offset += memberLayout.byteSize(); + } + yield fields; + } + case SequenceLayout sequenceLayout -> { + List fields = new ArrayList<>(); + MemoryLayout elementLayout = sequenceLayout.elementLayout(); + for (long i = 0; i < sequenceLayout.elementCount(); i++) { + fields.addAll(getFlattenedFieldsInner(offset, elementLayout)); + offset += elementLayout.byteSize(); + } + yield fields; + } + case null, default -> throw new IllegalStateException("Cannot get here: " + layout); + }; + } + + public static List getFlattenedFields(GroupLayout layout) { + return getFlattenedFieldsInner(0, layout); + } + + // ValueLayout will be classified by its carrier type. + private static TypeClass classifyValueType(ValueLayout type) { + Class carrier = type.carrier(); + if (carrier == boolean.class || carrier == byte.class || carrier == char.class || + carrier == short.class || carrier == int.class || carrier == long.class) { + return INTEGER; + } else if (carrier == float.class || carrier == double.class) { + return FLOAT; + } else if (carrier == MemorySegment.class) { + return POINTER; + } else { + throw new IllegalStateException("Cannot get here: " + carrier.getName()); + } + } + + private static boolean isRegisterAggregate(MemoryLayout type) { + return type.byteSize() <= MAX_AGGREGATE_REGS_SIZE * 8; + } + + private static TypeClass classifyStructType(GroupLayout layout) { + if (layout instanceof UnionLayout) { + return isRegisterAggregate(layout) ? STRUCT_REGISTER_X : STRUCT_REFERENCE; + } + + if (!isRegisterAggregate(layout)) { + return STRUCT_REFERENCE; + } + + // classify struct by its fields. + FieldCounter counter = FieldCounter.flatten(layout); + if (counter.integerCnt == 0 && counter.pointerCnt == 0 && + (counter.floatCnt == 1 || counter.floatCnt == 2)) { + return STRUCT_REGISTER_F; + } else if (counter.integerCnt == 1 && counter.floatCnt == 1 && + counter.pointerCnt == 0) { + return STRUCT_REGISTER_XF; + } else { + return STRUCT_REGISTER_X; + } + } + + public static TypeClass classifyLayout(MemoryLayout type) { + if (type instanceof ValueLayout vt) { + return classifyValueType(vt); + } else if (type instanceof GroupLayout gt) { + return classifyStructType(gt); + } else { + throw new IllegalArgumentException("Unsupported layout: " + type); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture$Regs.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture$Regs.class new file mode 100644 index 00000000..25a477b2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture$Regs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture$StorageType.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture$StorageType.class new file mode 100644 index 00000000..79a70fe0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture$StorageType.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture.class new file mode 100644 index 00000000..3c944d24 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture.java b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture.java new file mode 100644 index 00000000..146536f0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/s390/S390Architecture.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 IBM Corp. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.s390; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.Architecture; +import jdk.internal.foreign.abi.StubLocations; +import jdk.internal.foreign.abi.VMStorage; + +public final class S390Architecture implements Architecture { + public static final Architecture INSTANCE = new S390Architecture(); + + // Needs to be consistent with vmstorage_s390.hpp. + public static final short REG32_MASK = 0b0000_0000_0000_0001; + public static final short REG64_MASK = 0b0000_0000_0000_0011; + + private static final int INTEGER_REG_SIZE = 8; + private static final int FLOAT_REG_SIZE = 8; + private static final int STACK_SLOT_SIZE = 8; + + // Suppresses default constructor, ensuring non-instantiability. + private S390Architecture() { + } + + @Override + public boolean isStackType(int cls) { + return cls == StorageType.STACK; + } + + @Override + public int typeSize(int cls) { + return switch (cls) { + case StorageType.INTEGER -> INTEGER_REG_SIZE; + case StorageType.FLOAT -> FLOAT_REG_SIZE; + // STACK is deliberately omitted + default -> throw new IllegalArgumentException("Invalid Storage Class: " + cls); + }; + } + + public interface StorageType { + byte INTEGER = 0; + byte FLOAT = 1; + byte STACK = 2; + byte PLACEHOLDER = 3; + } + + public static class Regs { // break circular dependency + public static final VMStorage r0 = integerRegister(0); + public static final VMStorage r1 = integerRegister(1); + public static final VMStorage r2 = integerRegister(2); + public static final VMStorage r3 = integerRegister(3); + public static final VMStorage r4 = integerRegister(4); + public static final VMStorage r5 = integerRegister(5); + public static final VMStorage r6 = integerRegister(6); + public static final VMStorage r7 = integerRegister(7); + public static final VMStorage r8 = integerRegister(8); + public static final VMStorage r9 = integerRegister(9); + public static final VMStorage r10 = integerRegister(10); + public static final VMStorage r11 = integerRegister(11); + public static final VMStorage r12 = integerRegister(12); + public static final VMStorage r13 = integerRegister(13); + public static final VMStorage r14 = integerRegister(14); + public static final VMStorage r15 = integerRegister(15); + + public static final VMStorage f0 = floatRegister(0); + public static final VMStorage f1 = floatRegister(1); + public static final VMStorage f2 = floatRegister(2); + public static final VMStorage f3 = floatRegister(3); + public static final VMStorage f4 = floatRegister(4); + public static final VMStorage f5 = floatRegister(5); + public static final VMStorage f6 = floatRegister(6); + public static final VMStorage f7 = floatRegister(7); + public static final VMStorage f8 = floatRegister(8); + public static final VMStorage f9 = floatRegister(9); + public static final VMStorage f10 = floatRegister(10); + public static final VMStorage f11 = floatRegister(11); + public static final VMStorage f12 = floatRegister(12); + public static final VMStorage f13 = floatRegister(13); + public static final VMStorage f14 = floatRegister(14); + public static final VMStorage f15 = floatRegister(15); + } + + private static VMStorage integerRegister(int index) { + return new VMStorage(StorageType.INTEGER, REG64_MASK, index, "r" + index); + } + + private static VMStorage floatRegister(int index) { + return new VMStorage(StorageType.FLOAT, REG64_MASK, index, "f" + index); + } + + public static VMStorage stackStorage(short size, int byteOffset) { + return new VMStorage(StorageType.STACK, size, byteOffset); + } + + public static ABIDescriptor abiFor(VMStorage[] inputIntRegs, + VMStorage[] inputFloatRegs, + VMStorage[] outputIntRegs, + VMStorage[] outputFloatRegs, + VMStorage[] volatileIntRegs, + VMStorage[] volatileFloatRegs, + int stackAlignment, + int shadowSpace, + VMStorage scratch1, VMStorage scratch2) { + return new ABIDescriptor( + INSTANCE, + new VMStorage[][] { + inputIntRegs, + inputFloatRegs, + }, + new VMStorage[][] { + outputIntRegs, + outputFloatRegs, + }, + new VMStorage[][] { + volatileIntRegs, + volatileFloatRegs, + }, + stackAlignment, + shadowSpace, + scratch1, scratch2, + StubLocations.TARGET_ADDRESS.storage(StorageType.PLACEHOLDER), + StubLocations.RETURN_BUFFER.storage(StorageType.PLACEHOLDER), + StubLocations.CAPTURED_STATE_BUFFER.storage(StorageType.PLACEHOLDER)); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$1.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$1.class new file mode 100644 index 00000000..ce1df3f8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$BindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$BindingCalculator.class new file mode 100644 index 00000000..8bca04a7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$BindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$Bindings.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$Bindings.class new file mode 100644 index 00000000..a7ce8538 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$Bindings.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$BoxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$BoxBindingCalculator.class new file mode 100644 index 00000000..1f4a4c40 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$BoxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$StorageCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$StorageCalculator.class new file mode 100644 index 00000000..5324b0ec Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$StorageCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$UnboxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$UnboxBindingCalculator.class new file mode 100644 index 00000000..664f79c3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger$UnboxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.class new file mode 100644 index 00000000..5692b92b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java new file mode 100644 index 00000000..7e6c2540 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 IBM Corp. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.s390.linux; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.CallingSequenceBuilder; +import jdk.internal.foreign.abi.DowncallLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.Utils; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Optional; + +import static jdk.internal.foreign.abi.s390.S390Architecture.*; +import static jdk.internal.foreign.abi.s390.S390Architecture.Regs.*; + +/** + * For the S390 C ABI specifically, this class uses CallingSequenceBuilder + * to translate a C FunctionDescriptor into a CallingSequence, which can then be turned into a MethodHandle. + * + * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns. + */ +public class LinuxS390CallArranger { + + private static final int STACK_SLOT_SIZE = 8; + public static final int MAX_REGISTER_ARGUMENTS = 5; + public static final int MAX_FLOAT_REGISTER_ARGUMENTS = 4; + + private static final ABIDescriptor CLinux = abiFor( + new VMStorage[] { r2, r3, r4, r5, r6, }, // GP input + new VMStorage[] { f0, f2, f4, f6 }, // FP input + new VMStorage[] { r2, }, // GP output + new VMStorage[] { f0, }, // FP output + new VMStorage[] { r0, r1, r2, r3, r4, r5, r14 }, // volatile GP + new VMStorage[] { f1, f3, f5, f7 }, // volatile FP (excluding argument registers) + 8, // Stack is always 8 byte aligned on S390 + 160, // ABI header + r0, r1 // scratch reg r0 & r1 + ); + + public record Bindings(CallingSequence callingSequence, boolean isInMemoryReturn) {} + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) { + return getBindings(mt, cDesc, forUpcall, LinkerOptions.empty()); + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) { + CallingSequenceBuilder csb = new CallingSequenceBuilder(CLinux, forUpcall, options); + + BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess()); + BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false); + + boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout()); + if (returnInMemory) { + Class carrier = MemorySegment.class; + MemoryLayout layout =SharedUtils.C_POINTER; + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } else if (cDesc.returnLayout().isPresent()) { + Class carrier = mt.returnType(); + MemoryLayout layout = cDesc.returnLayout().get(); + csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout)); + } + + for (int i = 0; i < mt.parameterCount(); i++) { + Class carrier = mt.parameterType(i); + MemoryLayout layout = cDesc.argumentLayouts().get(i); + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } + + return new Bindings(csb.build(), returnInMemory); + } + + public static MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, false, options); + + MethodHandle handle = new DowncallLinker(CLinux, bindings.callingSequence).getBoundMethodHandle(); + + if (bindings.isInMemoryReturn) { + handle = SharedUtils.adaptDowncallForIMR(handle, cDesc, bindings.callingSequence); + } + + return handle; + } + + public static UpcallStubFactory arrangeUpcall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, true, options); + + final boolean dropReturn = true; /* drop return, since we don't have bindings for it */ + return SharedUtils.arrangeUpcallHelper(mt, bindings.isInMemoryReturn, dropReturn, CLinux, + bindings.callingSequence); + } + + private static boolean isInMemoryReturn(Optional returnLayout) { + return returnLayout + .filter(layout -> layout instanceof GroupLayout) + .isPresent(); + } + + static class StorageCalculator { + private final boolean forArguments; + + private final int[] nRegs = new int[] { 0, 0 }; + private long stackOffset = 0; + + public StorageCalculator(boolean forArguments) { + this.forArguments = forArguments; + } + + VMStorage stackAlloc(long size, long alignment) { + long alignedStackOffset = Utils.alignUp(stackOffset, alignment); + + short encodedSize = (short) size; + assert (encodedSize & 0xFFFF) == size; + + VMStorage storage = stackStorage(encodedSize, (int) alignedStackOffset); + stackOffset = alignedStackOffset + size; + return storage; + } + + VMStorage regAlloc(int type) { + int gpRegCnt = (type == StorageType.INTEGER) ? 1 : 0; + int fpRegCnt = (type == StorageType.FLOAT) ? 1 : 0; + + // Use stack if not enough registers available. + if ((type == StorageType.FLOAT && (nRegs[StorageType.FLOAT] + fpRegCnt) > MAX_FLOAT_REGISTER_ARGUMENTS) + || (type == StorageType.INTEGER && (nRegs[StorageType.INTEGER] + gpRegCnt) > MAX_REGISTER_ARGUMENTS)) return null; + + VMStorage[] source = (forArguments ? CLinux.inputStorage : CLinux.outputStorage)[type]; + VMStorage result = source[nRegs[type]]; + + nRegs[StorageType.INTEGER] += gpRegCnt; + nRegs[StorageType.FLOAT] += fpRegCnt; + return result; + + } + VMStorage getStorage(int type, boolean is32Bit) { + VMStorage reg = regAlloc(type); + if (reg != null) { + if (is32Bit) { + reg = new VMStorage(reg.type(), REG32_MASK, reg.indexOrOffset()); + } + return reg; + } + VMStorage stack; + if (is32Bit) { + stackAlloc(4, STACK_SLOT_SIZE); // Skip first half of stack slot. + stack = stackAlloc(4, 4); + } else + stack = stackAlloc(8, STACK_SLOT_SIZE); + + return stack; + } + } + + abstract static class BindingCalculator { + protected final StorageCalculator storageCalculator; + + protected BindingCalculator(boolean forArguments) { + this.storageCalculator = new LinuxS390CallArranger.StorageCalculator(forArguments); + } + + abstract List getBindings(Class carrier, MemoryLayout layout); + } + + // Compute recipe for transferring arguments / return values to C from Java. + static class UnboxBindingCalculator extends BindingCalculator { + private final boolean useAddressPairs; + + UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) { + super(forArguments); + this.useAddressPairs = useAddressPairs; + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + Class type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), false); + bindings.bufferLoad(0, type) + .vmStore(storage, type); + } + case STRUCT_SFA -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT, layout.byteSize() == 4); + Class type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), true); + bindings.bufferLoad(0, type) + .vmStore(storage, type); + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + bindings.copy(layout) + .unboxAddress(); + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + bindings.vmStore(storage, long.class); + } + case POINTER -> { + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + if (useAddressPairs) { + bindings.dup() + .segmentBase() + .vmStore(storage, Object.class) + .segmentOffsetAllowHeap() + .vmStore(null, long.class); + } else { + bindings.unboxAddress(); + bindings.vmStore(storage, long.class); + } + } + case INTEGER -> { + // ABI requires all int types to get extended to 64 bit. + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + bindings.vmStore(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT, carrier == float.class); + bindings.vmStore(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } + + // Compute recipe for transferring arguments / return values from C to Java. + static class BoxBindingCalculator extends BindingCalculator { + BoxBindingCalculator(boolean forArguments) { + super(forArguments); + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout) + .dup(); + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + Class type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), false); + bindings.vmLoad(storage, type) + .bufferStore(0, type); + } + case STRUCT_SFA -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout) + .dup(); + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT, layout.byteSize() == 4); + Class type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), true); + bindings.vmLoad(storage, type) + .bufferStore(0, type); + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + bindings.vmLoad(storage, long.class) + .boxAddress(layout); + } + case POINTER -> { + AddressLayout addressLayout = (AddressLayout) layout; + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + bindings.vmLoad(storage, long.class) + .boxAddressRaw(Utils.pointeeByteSize(addressLayout), Utils.pointeeByteAlign(addressLayout)); + } + case INTEGER -> { + // We could use carrier != long.class for BoxBindingCalculator, but C always uses 64 bit slots. + VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false); + bindings.vmLoad(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.getStorage(StorageType.FLOAT, carrier == float.class); + bindings.vmLoad(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker$1Holder.class new file mode 100644 index 00000000..39128f8a Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.class new file mode 100644 index 00000000..1ba9ff7e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java new file mode 100644 index 00000000..9810fbf1 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 IBM Corp. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.s390.linux; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +public final class LinuxS390Linker extends AbstractLinker { + + private static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static LinuxS390Linker getInstance() { + final class Holder { + private static final LinuxS390Linker INSTANCE = new LinuxS390Linker(); + } + + return Holder.INSTANCE; + } + + private LinuxS390Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return LinuxS390CallArranger.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return LinuxS390CallArranger.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/TypeClass.class b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/TypeClass.class new file mode 100644 index 00000000..a8d04819 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/TypeClass.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/TypeClass.java b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/TypeClass.java new file mode 100644 index 00000000..1f96fa4b --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/s390/linux/TypeClass.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023 IBM Corp. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.s390.linux; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.ValueLayout; +import java.util.List; +import java.util.ArrayList; + +public enum TypeClass { + STRUCT_REGISTER, + STRUCT_SFA, // Single Float Aggregate + STRUCT_REFERENCE, + POINTER, + INTEGER, + FLOAT; + + private static TypeClass classifyValueType(ValueLayout type) { + Class carrier = type.carrier(); + if (carrier == boolean.class || carrier == byte.class || carrier == char.class || + carrier == short.class || carrier == int.class || carrier == long.class) { + return INTEGER; + } else if (carrier == float.class || carrier == double.class) { + return FLOAT; + } else if (carrier == MemorySegment.class) { + return POINTER; + } else { + throw new IllegalStateException("Cannot get here: " + carrier.getName()); + } + } + + private static boolean isRegisterAggregate(MemoryLayout type) { + long byteSize = type.byteSize(); + if (byteSize > 8 || byteSize == 3 || byteSize == 5 || byteSize == 6 || byteSize == 7) + return false; + return true; + } + + static List scalarLayouts(GroupLayout gl) { + List out = new ArrayList<>(); + scalarLayoutsInternal(out, gl); + return out; + } + + private static void scalarLayoutsInternal(List out, GroupLayout gl) { + for (MemoryLayout member : gl.memberLayouts()) { + if (member instanceof GroupLayout memberGl) { + scalarLayoutsInternal(out, memberGl); + } else if (member instanceof SequenceLayout memberSl) { + for (long i = 0; i < memberSl.elementCount(); i++) { + out.add(memberSl.elementLayout()); + } + } else { + // padding or value layouts + out.add(member); + } + } + } + + static boolean isSingleFloatAggregate(MemoryLayout type) { + List scalarLayouts = scalarLayouts((GroupLayout) type); + + final int numElements = scalarLayouts.size(); + if (numElements > 1 || numElements == 0) + return false; + + MemoryLayout baseType = scalarLayouts.get(0); + + if (!(baseType instanceof ValueLayout)) + return false; + + TypeClass baseArgClass = classifyValueType((ValueLayout) baseType); + return baseArgClass == FLOAT; + } + + private static TypeClass classifyStructType(MemoryLayout layout) { + + if (!isRegisterAggregate(layout)) { + return TypeClass.STRUCT_REFERENCE; + } + + if (isSingleFloatAggregate(layout)) { + return TypeClass.STRUCT_SFA; + } + return TypeClass.STRUCT_REGISTER; + } + + public static TypeClass classifyLayout(MemoryLayout type) { + if (type instanceof ValueLayout) { + return classifyValueType((ValueLayout) type); + } else if (type instanceof GroupLayout) { + return classifyStructType(type); + } else { + throw new IllegalArgumentException("Unsupported layout: " + type); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture$Regs.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture$Regs.class new file mode 100644 index 00000000..962f9e12 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture$Regs.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture$StorageType.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture$StorageType.class new file mode 100644 index 00000000..49cda54e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture$StorageType.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture.class new file mode 100644 index 00000000..644e4603 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture.java new file mode 100644 index 00000000..03fc39d0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/X86_64Architecture.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64; + +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.Architecture; +import jdk.internal.foreign.abi.StubLocations; +import jdk.internal.foreign.abi.VMStorage; + +import java.util.stream.IntStream; + +public final class X86_64Architecture implements Architecture { + public static final Architecture INSTANCE = new X86_64Architecture(); + + private static final short REG8_H_MASK = 0b0000_0000_0000_0010; + private static final short REG8_L_MASK = 0b0000_0000_0000_0001; + private static final short REG16_MASK = 0b0000_0000_0000_0011; + private static final short REG32_MASK = 0b0000_0000_0000_0111; + private static final short REG64_MASK = 0b0000_0000_0000_1111; + private static final short XMM_MASK = 0b0000_0000_0000_0001; + private static final short YMM_MASK = 0b0000_0000_0000_0011; + private static final short ZMM_MASK = 0b0000_0000_0000_0111; + private static final short STP_MASK = 0b0000_0000_0000_0001; + + private static final int INTEGER_REG_SIZE = 8; // bytes + private static final int VECTOR_REG_SIZE = 16; // size of XMM register + private static final int X87_REG_SIZE = 16; + + // Suppresses default constructor, ensuring non-instantiability. + private X86_64Architecture() {} + + @Override + public boolean isStackType(int cls) { + return cls == StorageType.STACK; + } + + @Override + public int typeSize(int cls) { + return switch (cls) { + case StorageType.INTEGER -> INTEGER_REG_SIZE; + case StorageType.VECTOR -> VECTOR_REG_SIZE; + case StorageType.X87 -> X87_REG_SIZE; + // STACK is deliberately omitted + default -> throw new IllegalArgumentException("Invalid Storage Class: " +cls); + }; + } + + // must keep in sync with StorageType in VM code + public interface StorageType { + byte INTEGER = 0; + byte VECTOR = 1; + byte X87 = 2; + byte STACK = 3; + byte PLACEHOLDER = 4; + } + + public static class Regs { // break circular dependency + public static final VMStorage rax = integerRegister(0, "rax"); + public static final VMStorage rcx = integerRegister(1, "rcx"); + public static final VMStorage rdx = integerRegister(2, "rdx"); + public static final VMStorage rbx = integerRegister(3, "rbx"); + public static final VMStorage rsp = integerRegister(4, "rsp"); + public static final VMStorage rbp = integerRegister(5, "rbp"); + public static final VMStorage rsi = integerRegister(6, "rsi"); + public static final VMStorage rdi = integerRegister(7, "rdi"); + public static final VMStorage r8 = integerRegister(8, "r8"); + public static final VMStorage r9 = integerRegister(9, "r9"); + public static final VMStorage r10 = integerRegister(10, "r10"); + public static final VMStorage r11 = integerRegister(11, "r11"); + public static final VMStorage r12 = integerRegister(12, "r12"); + public static final VMStorage r13 = integerRegister(13, "r13"); + public static final VMStorage r14 = integerRegister(14, "r14"); + public static final VMStorage r15 = integerRegister(15, "r15"); + + public static final VMStorage xmm0 = vectorRegister(0, "xmm0"); + public static final VMStorage xmm1 = vectorRegister(1, "xmm1"); + public static final VMStorage xmm2 = vectorRegister(2, "xmm2"); + public static final VMStorage xmm3 = vectorRegister(3, "xmm3"); + public static final VMStorage xmm4 = vectorRegister(4, "xmm4"); + public static final VMStorage xmm5 = vectorRegister(5, "xmm5"); + public static final VMStorage xmm6 = vectorRegister(6, "xmm6"); + public static final VMStorage xmm7 = vectorRegister(7, "xmm7"); + public static final VMStorage xmm8 = vectorRegister(8, "xmm8"); + public static final VMStorage xmm9 = vectorRegister(9, "xmm9"); + public static final VMStorage xmm10 = vectorRegister(10, "xmm10"); + public static final VMStorage xmm11 = vectorRegister(11, "xmm11"); + public static final VMStorage xmm12 = vectorRegister(12, "xmm12"); + public static final VMStorage xmm13 = vectorRegister(13, "xmm13"); + public static final VMStorage xmm14 = vectorRegister(14, "xmm14"); + public static final VMStorage xmm15 = vectorRegister(15, "xmm15"); + public static final VMStorage xmm16 = vectorRegister(16, "xmm16"); + public static final VMStorage xmm17 = vectorRegister(17, "xmm17"); + public static final VMStorage xmm18 = vectorRegister(18, "xmm18"); + public static final VMStorage xmm19 = vectorRegister(19, "xmm19"); + public static final VMStorage xmm20 = vectorRegister(20, "xmm20"); + public static final VMStorage xmm21 = vectorRegister(21, "xmm21"); + public static final VMStorage xmm22 = vectorRegister(22, "xmm22"); + public static final VMStorage xmm23 = vectorRegister(23, "xmm23"); + public static final VMStorage xmm24 = vectorRegister(24, "xmm24"); + public static final VMStorage xmm25 = vectorRegister(25, "xmm25"); + public static final VMStorage xmm26 = vectorRegister(26, "xmm26"); + public static final VMStorage xmm27 = vectorRegister(27, "xmm27"); + public static final VMStorage xmm28 = vectorRegister(28, "xmm28"); + public static final VMStorage xmm29 = vectorRegister(29, "xmm29"); + public static final VMStorage xmm30 = vectorRegister(30, "xmm30"); + public static final VMStorage xmm31 = vectorRegister(31, "xmm31"); + } + + private static VMStorage integerRegister(int index, String debugName) { + return new VMStorage(StorageType.INTEGER, REG64_MASK, index, debugName); + } + + private static VMStorage vectorRegister(int index, String debugName) { + return new VMStorage(StorageType.VECTOR, XMM_MASK, index, debugName); + } + + public static VMStorage stackStorage(short size, int byteOffset) { + return new VMStorage(StorageType.STACK, size, byteOffset); + } + + public static VMStorage x87Storage(int index) { + return new VMStorage(StorageType.X87, STP_MASK, index, "X87(" + index + ")"); + } + + public static ABIDescriptor abiFor(VMStorage[] inputIntRegs, VMStorage[] inputVectorRegs, VMStorage[] outputIntRegs, + VMStorage[] outputVectorRegs, int numX87Outputs, VMStorage[] volatileIntRegs, + VMStorage[] volatileVectorRegs, int stackAlignment, int shadowSpace, + VMStorage scratch1, VMStorage scratch2) { + return new ABIDescriptor( + INSTANCE, + new VMStorage[][] { + inputIntRegs, + inputVectorRegs, + }, + new VMStorage[][] { + outputIntRegs, + outputVectorRegs, + IntStream.range(0, numX87Outputs) + .mapToObj(X86_64Architecture::x87Storage) + .toArray(VMStorage[]::new) + }, + new VMStorage[][] { + volatileIntRegs, + volatileVectorRegs, + }, + stackAlignment, + shadowSpace, + scratch1, scratch2, + StubLocations.TARGET_ADDRESS.storage(StorageType.PLACEHOLDER), + StubLocations.RETURN_BUFFER.storage(StorageType.PLACEHOLDER), + StubLocations.CAPTURED_STATE_BUFFER.storage(StorageType.PLACEHOLDER)); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/ArgumentClassImpl.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/ArgumentClassImpl.class new file mode 100644 index 00000000..7d3710ca Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/ArgumentClassImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/ArgumentClassImpl.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/ArgumentClassImpl.java new file mode 100644 index 00000000..7bd9dc61 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/ArgumentClassImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64.sysv; + +public enum ArgumentClassImpl { + POINTER, INTEGER, SSE, SSEUP, X87, X87UP, COMPLEX_X87, NO_CLASS, MEMORY; + + public ArgumentClassImpl merge(ArgumentClassImpl other) { + if (this == other) { + return this; + } + + if (other == NO_CLASS) { + return this; + } + if (this == NO_CLASS) { + return other; + } + + if (this == MEMORY || other == MEMORY) { + return MEMORY; + } + + if (this == POINTER || other == POINTER) { + return POINTER; + } + + if (this == INTEGER || other == INTEGER) { + return INTEGER; + } + + if (this == X87 || this == X87UP || this == COMPLEX_X87) { + return MEMORY; + } + if (other == X87 || other == X87UP || other == COMPLEX_X87) { + return MEMORY; + } + + return SSE; + } + + public boolean isIntegral() { + return this == INTEGER || this == POINTER; + } + + public boolean isPointer() { + return this == POINTER; + } + + public boolean isIndirect() { + return this == MEMORY; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$1.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$1.class new file mode 100644 index 00000000..b47a70f3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$BindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$BindingCalculator.class new file mode 100644 index 00000000..6b63ce79 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$BindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$Bindings.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$Bindings.class new file mode 100644 index 00000000..6b0d00b6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$Bindings.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$BoxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$BoxBindingCalculator.class new file mode 100644 index 00000000..b5169ce6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$BoxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$StorageCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$StorageCalculator.class new file mode 100644 index 00000000..c7a8e18f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$StorageCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$UnboxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$UnboxBindingCalculator.class new file mode 100644 index 00000000..0b35cb01 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger$UnboxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger.class new file mode 100644 index 00000000..251c3e76 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger.java new file mode 100644 index 00000000..78db1ca8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/CallArranger.java @@ -0,0 +1,354 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.abi.x64.sysv; + +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.CallingSequenceBuilder; +import jdk.internal.foreign.abi.DowncallLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.abi.x64.X86_64Architecture; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Optional; + +import static jdk.internal.foreign.abi.Binding.vmStore; +import static jdk.internal.foreign.abi.x64.X86_64Architecture.*; +import static jdk.internal.foreign.abi.x64.X86_64Architecture.Regs.*; + +/** + * For the SysV x64 C ABI specifically, this class uses namely CallingSequenceBuilder + * to translate a C FunctionDescriptor into a CallingSequence, which can then be turned into a MethodHandle. + * + * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns. + */ +public class CallArranger { + private static final int STACK_SLOT_SIZE = 8; + private static final int MAX_INTEGER_ARGUMENT_REGISTERS = 6; + private static final int MAX_VECTOR_ARGUMENT_REGISTERS = 8; + + /** + * The {@code long} native type. + */ + public static final ValueLayout.OfLong C_LONG = ValueLayout.JAVA_LONG; + + private static final ABIDescriptor CSysV = X86_64Architecture.abiFor( + new VMStorage[] { rdi, rsi, rdx, rcx, r8, r9, rax }, + new VMStorage[] { xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 }, + new VMStorage[] { rax, rdx }, + new VMStorage[] { xmm0, xmm1 }, + 2, + new VMStorage[] { r10, r11 }, + new VMStorage[] { xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15, + xmm16, xmm17, xmm18, xmm19, xmm20, xmm21, xmm22, xmm23, + xmm24, xmm25, xmm26, xmm27, xmm28, xmm29, xmm30, xmm31 }, + 16, + 0, //no shadow space + r10, r11 // scratch 1 & 2 + ); + + public record Bindings( + CallingSequence callingSequence, + boolean isInMemoryReturn, + int nVectorArgs) { + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) { + return getBindings(mt, cDesc, forUpcall, LinkerOptions.empty()); + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) { + CallingSequenceBuilder csb = new CallingSequenceBuilder(CSysV, forUpcall, options); + + BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess()); + BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false); + + boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout()); + if (returnInMemory) { + Class carrier = MemorySegment.class; + MemoryLayout layout = SharedUtils.C_POINTER; + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } else if (cDesc.returnLayout().isPresent()) { + Class carrier = mt.returnType(); + MemoryLayout layout = cDesc.returnLayout().get(); + csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout)); + } + + for (int i = 0; i < mt.parameterCount(); i++) { + Class carrier = mt.parameterType(i); + MemoryLayout layout = cDesc.argumentLayouts().get(i); + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout)); + } + + if (!forUpcall && options.isVariadicFunction()) { + //add extra binding for number of used vector registers (used for variadic calls) + csb.addArgumentBindings(long.class, C_LONG, + List.of(vmStore(rax, long.class))); + } + + return new Bindings(csb.build(), returnInMemory, argCalc.storageCalculator.nVectorReg); + } + + public static MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, false, options); + + MethodHandle handle = new DowncallLinker(CSysV, bindings.callingSequence).getBoundMethodHandle(); + if (options.isVariadicFunction()) { + handle = MethodHandles.insertArguments(handle, handle.type().parameterCount() - 1, bindings.nVectorArgs); + } + + if (bindings.isInMemoryReturn) { + handle = SharedUtils.adaptDowncallForIMR(handle, cDesc, bindings.callingSequence); + } + + return handle; + } + + public static UpcallStubFactory arrangeUpcall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, true, options); + final boolean dropReturn = true; /* drop return, since we don't have bindings for it */ + return SharedUtils.arrangeUpcallHelper(mt, bindings.isInMemoryReturn, dropReturn, CSysV, + bindings.callingSequence); + } + + private static boolean isInMemoryReturn(Optional returnLayout) { + return returnLayout + .filter(GroupLayout.class::isInstance) + .filter(g -> TypeClass.classifyLayout(g).inMemory()) + .isPresent(); + } + + static class StorageCalculator { + private final boolean forArguments; + + private int nVectorReg = 0; + private int nIntegerReg = 0; + private long stackOffset = 0; + + public StorageCalculator(boolean forArguments) { + this.forArguments = forArguments; + } + + private int maxRegisterArguments(int type) { + return type == StorageType.INTEGER ? + MAX_INTEGER_ARGUMENT_REGISTERS : + MAX_VECTOR_ARGUMENT_REGISTERS; + } + + VMStorage stackAlloc() { + assert forArguments : "no stack returns"; + VMStorage storage = X86_64Architecture.stackStorage((short) STACK_SLOT_SIZE, (int)stackOffset); + stackOffset += STACK_SLOT_SIZE; + return storage; + } + + VMStorage nextStorage(int type) { + int registerCount = registerCount(type); + if (registerCount < maxRegisterArguments(type)) { + VMStorage[] source = + (forArguments ? CSysV.inputStorage : CSysV.outputStorage)[type]; + incrementRegisterCount(type); + return source[registerCount]; + } else { + return stackAlloc(); + } + } + + VMStorage[] structStorages(TypeClass typeClass) { + if (typeClass.inMemory()) { + return typeClass.classes.stream().map(c -> stackAlloc()).toArray(VMStorage[]::new); + } + long nIntegerReg = typeClass.nIntegerRegs(); + + if (this.nIntegerReg + nIntegerReg > MAX_INTEGER_ARGUMENT_REGISTERS) { + //not enough registers - pass on stack + return typeClass.classes.stream().map(c -> stackAlloc()).toArray(VMStorage[]::new); + } + + long nVectorReg = typeClass.nVectorRegs(); + + if (this.nVectorReg + nVectorReg > MAX_VECTOR_ARGUMENT_REGISTERS) { + //not enough registers - pass on stack + return typeClass.classes.stream().map(c -> stackAlloc()).toArray(VMStorage[]::new); + } + + //ok, let's pass on registers + VMStorage[] storage = new VMStorage[(int)(nIntegerReg + nVectorReg)]; + for (int i = 0 ; i < typeClass.classes.size() ; i++) { + boolean sse = typeClass.classes.get(i) == ArgumentClassImpl.SSE; + storage[i] = nextStorage(sse ? StorageType.VECTOR : StorageType.INTEGER); + } + return storage; + } + + int registerCount(int type) { + return switch (type) { + case StorageType.INTEGER -> nIntegerReg; + case StorageType.VECTOR -> nVectorReg; + default -> throw new IllegalStateException(); + }; + } + + void incrementRegisterCount(int type) { + switch (type) { + case StorageType.INTEGER -> nIntegerReg++; + case StorageType.VECTOR -> nVectorReg++; + default -> throw new IllegalStateException(); + } + } + } + + abstract static class BindingCalculator { + protected final StorageCalculator storageCalculator; + + protected BindingCalculator(boolean forArguments) { + this.storageCalculator = new StorageCalculator(forArguments); + } + + abstract List getBindings(Class carrier, MemoryLayout layout); + } + + static class UnboxBindingCalculator extends BindingCalculator { + private final boolean useAddressPairs; + + UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) { + super(forArguments); + this.useAddressPairs = useAddressPairs; + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass.kind()) { + case STRUCT -> { + assert carrier == MemorySegment.class; + VMStorage[] regs = storageCalculator.structStorages(argumentClass); + int regIndex = 0; + long offset = 0; + while (offset < layout.byteSize()) { + final long copy = Math.min(layout.byteSize() - offset, 8); + VMStorage storage = regs[regIndex++]; + if (offset + copy < layout.byteSize()) { + bindings.dup(); + } + boolean useFloat = storage.type() == StorageType.VECTOR; + Class type = SharedUtils.primitiveCarrierForSize(copy, useFloat); + bindings.bufferLoad(offset, type, (int) copy) + .vmStore(storage, type); + offset += copy; + } + } + case POINTER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + if (useAddressPairs) { + bindings.dup() + .segmentBase() + .vmStore(storage, Object.class) + .segmentOffsetAllowHeap() + .vmStore(null, long.class); + } else { + bindings.unboxAddress(); + bindings.vmStore(storage, long.class); + } + } + case INTEGER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmStore(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.VECTOR); + bindings.vmStore(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } + + static class BoxBindingCalculator extends BindingCalculator { + + BoxBindingCalculator(boolean forArguments) { + super(forArguments); + } + + @Override + List getBindings(Class carrier, MemoryLayout layout) { + TypeClass argumentClass = TypeClass.classifyLayout(layout); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass.kind()) { + case STRUCT -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout); + VMStorage[] regs = storageCalculator.structStorages(argumentClass); + int regIndex = 0; + long offset = 0; + while (offset < layout.byteSize()) { + final long copy = Math.min(layout.byteSize() - offset, 8); + VMStorage storage = regs[regIndex++]; + bindings.dup(); + boolean useFloat = storage.type() == StorageType.VECTOR; + Class type = SharedUtils.primitiveCarrierForSize(copy, useFloat); + bindings.vmLoad(storage, type) + .bufferStore(offset, type, (int) copy); + offset += copy; + } + } + case POINTER -> { + AddressLayout addressLayout = (AddressLayout) layout; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmLoad(storage, long.class) + .boxAddressRaw(Utils.pointeeByteSize(addressLayout), Utils.pointeeByteAlign(addressLayout)); + } + case INTEGER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmLoad(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.VECTOR); + bindings.vmLoad(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker$1Holder.class new file mode 100644 index 00000000..cc2eee31 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.class new file mode 100644 index 00000000..1feba96a Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java new file mode 100644 index 00000000..b1fce35a --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64.sysv; + + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +/** + * ABI implementation based on System V ABI AMD64 supplement v.0.99.6 + */ +public final class SysVx64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + + public static SysVx64Linker getInstance() { + final class Holder { + private static final SysVx64Linker INSTANCE = new SysVx64Linker(); + } + + return Holder.INSTANCE; + } + + private SysVx64Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass$1.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass$1.class new file mode 100644 index 00000000..f59eada1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass$Kind.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass$Kind.class new file mode 100644 index 00000000..2011c3f4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass$Kind.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass.class new file mode 100644 index 00000000..39ba18f3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass.java new file mode 100644 index 00000000..429b29e7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/sysv/TypeClass.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64.sysv; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.ValueLayout; +import jdk.internal.foreign.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +class TypeClass { + enum Kind { + STRUCT, + POINTER, + INTEGER, + FLOAT + } + + private final Kind kind; + final List classes; + + private TypeClass(Kind kind, List classes) { + this.kind = kind; + this.classes = classes; + } + + public static TypeClass ofValue(ValueLayout layout) { + final Kind kind; + ArgumentClassImpl argClass = argumentClassFor(layout); + kind = switch (argClass) { + case POINTER -> Kind.POINTER; + case INTEGER -> Kind.INTEGER; + case SSE -> Kind.FLOAT; + default -> throw new IllegalStateException("Unexpected argument class: " + argClass); + }; + return new TypeClass(kind, List.of(argClass)); + } + + public static TypeClass ofStruct(GroupLayout layout) { + return new TypeClass(Kind.STRUCT, classifyStructType(layout)); + } + + boolean inMemory() { + return classes.stream().anyMatch(c -> c == ArgumentClassImpl.MEMORY); + } + + private long numClasses(ArgumentClassImpl clazz) { + return classes.stream().filter(c -> c == clazz).count(); + } + + public long nIntegerRegs() { + return numClasses(ArgumentClassImpl.INTEGER) + numClasses(ArgumentClassImpl.POINTER); + } + + public long nVectorRegs() { + return numClasses(ArgumentClassImpl.SSE); + } + + public Kind kind() { + return kind; + } + + // layout classification + + // The AVX 512 enlightened ABI says "eight eightbytes" + // Although AMD64 0.99.6 states 4 eightbytes + private static final int MAX_AGGREGATE_REGS_SIZE = 8; + static final List COMPLEX_X87_CLASSES = List.of( + ArgumentClassImpl.X87, + ArgumentClassImpl.X87UP, + ArgumentClassImpl.X87, + ArgumentClassImpl.X87UP + ); + + private static List createMemoryClassArray(long size) { + return IntStream.range(0, (int)size) + .mapToObj(i -> ArgumentClassImpl.MEMORY) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private static ArgumentClassImpl argumentClassFor(ValueLayout layout) { + Class carrier = layout.carrier(); + if (carrier == boolean.class || carrier == byte.class || carrier == char.class || + carrier == short.class || carrier == int.class || carrier == long.class) { + return ArgumentClassImpl.INTEGER; + } else if (carrier == float.class || carrier == double.class) { + return ArgumentClassImpl.SSE; + } else if (carrier == MemorySegment.class) { + return ArgumentClassImpl.POINTER; + } else { + throw new IllegalStateException("Cannot get here: " + carrier.getName()); + } + } + + // TODO: handle zero length arrays + private static List classifyStructType(GroupLayout type) { + List[] eightbytes = groupByEightBytes(type); + long nWords = eightbytes.length; + if (nWords > MAX_AGGREGATE_REGS_SIZE) { + return createMemoryClassArray(nWords); + } + + ArrayList classes = new ArrayList<>(); + + for (int idx = 0; idx < nWords; idx++) { + List subclasses = eightbytes[idx]; + ArgumentClassImpl result = subclasses.stream() + .reduce(ArgumentClassImpl.NO_CLASS, ArgumentClassImpl::merge); + classes.add(result); + } + + for (int i = 0; i < classes.size(); i++) { + ArgumentClassImpl c = classes.get(i); + + if (c == ArgumentClassImpl.MEMORY) { + // if any of the eightbytes was passed in memory, pass the whole thing in memory + return createMemoryClassArray(classes.size()); + } + + if (c == ArgumentClassImpl.X87UP) { + if (i == 0) { + throw new IllegalArgumentException("Unexpected leading X87UP class"); + } + + if (classes.get(i - 1) != ArgumentClassImpl.X87) { + return createMemoryClassArray(classes.size()); + } + } + } + + if (classes.size() > 2) { + if (classes.get(0) != ArgumentClassImpl.SSE) { + return createMemoryClassArray(classes.size()); + } + + for (int i = 1; i < classes.size(); i++) { + if (classes.get(i) != ArgumentClassImpl.SSEUP) { + return createMemoryClassArray(classes.size()); + } + } + } + + return classes; + } + + static TypeClass classifyLayout(MemoryLayout type) { + try { + if (type instanceof ValueLayout valueLayout) { + return ofValue(valueLayout); + } else if (type instanceof GroupLayout groupLayout) { + return ofStruct(groupLayout); + } else { + throw new IllegalArgumentException("Unsupported layout: " + type); + } + } catch (UnsupportedOperationException e) { + System.err.println("Failed to classify layout: " + type); + throw e; + } + } + + private static List[] groupByEightBytes(GroupLayout group) { + long offset = 0L; + int nEightbytes; + try { + // alignUp can overflow the value, but it's okay since toIntExact still catches it + nEightbytes = Math.toIntExact(Utils.alignUp(group.byteSize(), 8) / 8); + } catch (ArithmeticException e) { + throw new IllegalArgumentException("GroupLayout is too large: " + group, e); + } + @SuppressWarnings({"unchecked", "rawtypes"}) + List[] groups = new List[nEightbytes]; + for (MemoryLayout l : group.memberLayouts()) { + groupByEightBytes(l, offset, groups); + if (group instanceof StructLayout) { + offset += l.byteSize(); + } + } + return groups; + } + + private static void groupByEightBytes(MemoryLayout layout, + long offset, + List[] groups) { + switch (layout) { + case GroupLayout group -> { + for (MemoryLayout m : group.memberLayouts()) { + groupByEightBytes(m, offset, groups); + if (group instanceof StructLayout) { + offset += m.byteSize(); + } + } + } + case PaddingLayout _ -> { + } + case SequenceLayout seq -> { + MemoryLayout elem = seq.elementLayout(); + for (long i = 0; i < seq.elementCount(); i++) { + groupByEightBytes(elem, offset, groups); + offset += elem.byteSize(); + } + } + case ValueLayout vl -> { + List layouts = groups[(int) offset / 8]; + if (layouts == null) { + layouts = new ArrayList<>(); + groups[(int) offset / 8] = layouts; + } + // if the aggregate contains unaligned fields, it has class MEMORY + ArgumentClassImpl argumentClass = (offset % vl.byteAlignment()) == 0 ? + argumentClassFor(vl) : + ArgumentClassImpl.MEMORY; + layouts.add(argumentClass); + } + case null, default -> throw new IllegalStateException("Unexpected layout: " + layout); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$1.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$1.class new file mode 100644 index 00000000..a198a239 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$1.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$1CallingSequenceBuilderHelper.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$1CallingSequenceBuilderHelper.class new file mode 100644 index 00000000..a13462b2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$1CallingSequenceBuilderHelper.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$BindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$BindingCalculator.class new file mode 100644 index 00000000..8f1ac566 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$BindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$Bindings.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$Bindings.class new file mode 100644 index 00000000..82a0cf35 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$Bindings.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$BoxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$BoxBindingCalculator.class new file mode 100644 index 00000000..eadabcfc Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$BoxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$StorageCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$StorageCalculator.class new file mode 100644 index 00000000..d772d638 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$StorageCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$UnboxBindingCalculator.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$UnboxBindingCalculator.class new file mode 100644 index 00000000..c9cbf654 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger$UnboxBindingCalculator.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger.class new file mode 100644 index 00000000..e8bcdeff Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger.java new file mode 100644 index 00000000..cc69e79a --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/CallArranger.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64.windows; + +import jdk.internal.foreign.Utils; +import jdk.internal.foreign.abi.ABIDescriptor; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.Binding; +import jdk.internal.foreign.abi.CallingSequence; +import jdk.internal.foreign.abi.CallingSequenceBuilder; +import jdk.internal.foreign.abi.DowncallLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.foreign.abi.VMStorage; +import jdk.internal.foreign.abi.x64.X86_64Architecture; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Optional; + +import static jdk.internal.foreign.abi.x64.X86_64Architecture.*; +import static jdk.internal.foreign.abi.x64.X86_64Architecture.Regs.*; + +/** + * For the Windowx x64 C ABI specifically, this class uses CallingSequenceBuilder + * to translate a C FunctionDescriptor into a CallingSequence, which can then be turned into a MethodHandle. + * + * This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns. + */ +public class CallArranger { + public static final int MAX_REGISTER_ARGUMENTS = 4; + private static final int STACK_SLOT_SIZE = 8; + + private static final ABIDescriptor CWindows = X86_64Architecture.abiFor( + new VMStorage[] { rcx, rdx, r8, r9 }, + new VMStorage[] { xmm0, xmm1, xmm2, xmm3 }, + new VMStorage[] { rax }, + new VMStorage[] { xmm0 }, + 0, + new VMStorage[] { rax, r10, r11 }, + new VMStorage[] { xmm4, xmm5, + xmm16, xmm17, xmm18, xmm19, xmm20, xmm21, xmm22, xmm23, + xmm24, xmm25, xmm26, xmm27, xmm28, xmm29, xmm30, xmm31 }, + 16, + 32, + r10, r11 // scratch 1 & 2 + ); + + public record Bindings( + CallingSequence callingSequence, + boolean isInMemoryReturn) { + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall) { + return getBindings(mt, cDesc, forUpcall, LinkerOptions.empty()); + } + + public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) { + class CallingSequenceBuilderHelper { + final CallingSequenceBuilder csb = new CallingSequenceBuilder(CWindows, forUpcall, options); + final BindingCalculator argCalc = + forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess()); + final BindingCalculator retCalc = + forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false); + + void addArgumentBindings(Class carrier, MemoryLayout layout, boolean isVararg) { + csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout, isVararg)); + } + + void setReturnBindings(Class carrier, MemoryLayout layout) { + csb.setReturnBindings(carrier, layout, retCalc.getBindings(carrier, layout, false)); + } + } + var csb = new CallingSequenceBuilderHelper(); + + boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout()); + if (returnInMemory) { + Class carrier = MemorySegment.class; + MemoryLayout layout = SharedUtils.C_POINTER; + csb.addArgumentBindings(carrier, layout, false); + if (forUpcall) { + csb.setReturnBindings(carrier, layout); + } + } else if (cDesc.returnLayout().isPresent()) { + csb.setReturnBindings(mt.returnType(), cDesc.returnLayout().get()); + } + + for (int i = 0; i < mt.parameterCount(); i++) { + csb.addArgumentBindings(mt.parameterType(i), cDesc.argumentLayouts().get(i), options.isVarargsIndex(i)); + } + + return new Bindings(csb.csb.build(), returnInMemory); + } + + public static MethodHandle arrangeDowncall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, false, options); + + MethodHandle handle = new DowncallLinker(CWindows, bindings.callingSequence).getBoundMethodHandle(); + + if (bindings.isInMemoryReturn) { + handle = SharedUtils.adaptDowncallForIMR(handle, cDesc, bindings.callingSequence); + } + + return handle; + } + + public static UpcallStubFactory arrangeUpcall(MethodType mt, FunctionDescriptor cDesc, LinkerOptions options) { + Bindings bindings = getBindings(mt, cDesc, true, options); + final boolean dropReturn = false; /* need the return value as well */ + return SharedUtils.arrangeUpcallHelper(mt, bindings.isInMemoryReturn, dropReturn, CWindows, + bindings.callingSequence); + } + + private static boolean isInMemoryReturn(Optional returnLayout) { + return returnLayout + .filter(GroupLayout.class::isInstance) + .filter(g -> !TypeClass.isRegisterAggregate(g)) + .isPresent(); + } + + static class StorageCalculator { + private final boolean forArguments; + + private int nRegs = 0; + private long stackOffset = 0; + + public StorageCalculator(boolean forArguments) { + this.forArguments = forArguments; + } + + VMStorage nextStorage(int type) { + if (nRegs >= MAX_REGISTER_ARGUMENTS) { + assert forArguments : "no stack returns"; + // stack + assert stackOffset == Utils.alignUp(stackOffset, STACK_SLOT_SIZE); // should always be aligned + + VMStorage storage = X86_64Architecture.stackStorage((short) STACK_SLOT_SIZE, (int) stackOffset); + stackOffset += STACK_SLOT_SIZE; + return storage; + } + return (forArguments + ? CWindows.inputStorage + : CWindows.outputStorage) + [type][nRegs++]; + } + + public VMStorage extraVarargsStorage() { + assert forArguments; + return CWindows.inputStorage[StorageType.INTEGER][nRegs - 1]; + } + } + + private interface BindingCalculator { + List getBindings(Class carrier, MemoryLayout layout, boolean isVararg); + } + + static class UnboxBindingCalculator implements BindingCalculator { + private final StorageCalculator storageCalculator; + private final boolean useAddressPairs; + + UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) { + this.storageCalculator = new StorageCalculator(forArguments); + assert !useAddressPairs || forArguments; + this.useAddressPairs = useAddressPairs; + } + + @Override + public List getBindings(Class carrier, MemoryLayout layout, boolean isVararg) { + TypeClass argumentClass = TypeClass.typeClassFor(layout, isVararg); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + Class type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), false); + bindings.bufferLoad(0, type) + .vmStore(storage, type); + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + bindings.copy(layout) + .unboxAddress(); + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmStore(storage, long.class); + } + case POINTER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + if (useAddressPairs) { + bindings.dup() + .segmentBase() + .vmStore(storage, Object.class) + .segmentOffsetAllowHeap() + .vmStore(null, long.class); + } else { + bindings.unboxAddress(); + bindings.vmStore(storage, long.class); + } + } + case INTEGER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmStore(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.VECTOR); + bindings.vmStore(storage, carrier); + } + case VARARG_FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.VECTOR); + if (!INSTANCE.isStackType(storage.type())) { // need extra for register arg + VMStorage extraStorage = storageCalculator.extraVarargsStorage(); + bindings.dup() + .vmStore(extraStorage, carrier); + } + + bindings.vmStore(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } + + static class BoxBindingCalculator implements BindingCalculator { + private final StorageCalculator storageCalculator; + + BoxBindingCalculator(boolean forArguments) { + this.storageCalculator = new StorageCalculator(forArguments); + } + + @Override + public List getBindings(Class carrier, MemoryLayout layout, boolean isVararg) { + TypeClass argumentClass = TypeClass.typeClassFor(layout, isVararg); + Binding.Builder bindings = Binding.builder(); + switch (argumentClass) { + case STRUCT_REGISTER -> { + assert carrier == MemorySegment.class; + bindings.allocate(layout) + .dup(); + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + Class type = SharedUtils.primitiveCarrierForSize(layout.byteSize(), false); + bindings.vmLoad(storage, type) + .bufferStore(0, type); + } + case STRUCT_REFERENCE -> { + assert carrier == MemorySegment.class; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmLoad(storage, long.class) + .boxAddress(layout); + } + case POINTER -> { + AddressLayout addressLayout = (AddressLayout) layout; + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmLoad(storage, long.class) + .boxAddressRaw(Utils.pointeeByteSize(addressLayout), Utils.pointeeByteAlign(addressLayout)); + } + case INTEGER -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER); + bindings.vmLoad(storage, carrier); + } + case FLOAT -> { + VMStorage storage = storageCalculator.nextStorage(StorageType.VECTOR); + bindings.vmLoad(storage, carrier); + } + default -> throw new UnsupportedOperationException("Unhandled class " + argumentClass); + } + return bindings.build(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/TypeClass.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/TypeClass.class new file mode 100644 index 00000000..f3ad0e2e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/TypeClass.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/TypeClass.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/TypeClass.java new file mode 100644 index 00000000..04839b74 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/TypeClass.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64.windows; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +enum TypeClass { + STRUCT_REGISTER, + STRUCT_REFERENCE, + POINTER, + INTEGER, + FLOAT, + VARARG_FLOAT; + + private static TypeClass classifyValueType(ValueLayout type, boolean isVararg) { + // No 128-bit integers in the Windows C ABI. There are __m128(i|d) intrinsic types but, they act just + // like a struct when passing as an argument (passed by pointer). + // https://docs.microsoft.com/en-us/cpp/cpp/m128?view=vs-2019 + + // x87 is ignored on Windows: + // "The x87 register stack is unused, and may be used by the callee, + // but must be considered volatile across function calls." + // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019 + + Class carrier = type.carrier(); + if (carrier == boolean.class || carrier == byte.class || carrier == char.class || + carrier == short.class || carrier == int.class || carrier == long.class) { + return INTEGER; + } else if (carrier == float.class || carrier == double.class) { + if (isVararg) { + return VARARG_FLOAT; + } else { + return FLOAT; + } + } else if (carrier == MemorySegment.class) { + return POINTER; + } else { + throw new IllegalStateException("Cannot get here: " + carrier.getName()); + } + } + + static boolean isRegisterAggregate(MemoryLayout type) { + long size = type.byteSize(); + return size == 1 + || size == 2 + || size == 4 + || size == 8; + } + + private static TypeClass classifyStructType(MemoryLayout layout) { + if (isRegisterAggregate(layout)) { + return STRUCT_REGISTER; + } + return STRUCT_REFERENCE; + } + + static TypeClass typeClassFor(MemoryLayout type, boolean isVararg) { + if (type instanceof ValueLayout) { + return classifyValueType((ValueLayout) type, isVararg); + } else if (type instanceof GroupLayout) { + return classifyStructType(type); + } else { + throw new IllegalArgumentException("Unsupported layout: " + type); + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker$1Holder.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker$1Holder.class new file mode 100644 index 00000000..ac703af3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.class b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.class new file mode 100644 index 00000000..673457d7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java new file mode 100644 index 00000000..30e4a5bd --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.foreign.abi.x64.windows; + +import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.nio.ByteOrder; +import java.util.Map; + +/** + * ABI implementation based on Windows ABI AMD64 supplement v.0.99.6 + */ +public final class Windowsx64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR); + + public static Windowsx64Linker getInstance() { + final class Holder { + private static final Windowsx64Linker INSTANCE = new Windowsx64Linker(); + } + + return Holder.INSTANCE; + } + + private Windowsx64Linker() { + // Ensure there is only one instance + } + + @Override + protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.arrangeDowncall(inferredMethodType, function, options); + } + + @Override + protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return CallArranger.arrangeUpcall(targetType, function, options); + } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout$Kind.class b/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout$Kind.class new file mode 100644 index 00000000..55e94cb4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout$Kind.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout.class b/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout.class new file mode 100644 index 00000000..b5fa454e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout.java b/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout.java new file mode 100644 index 00000000..a2e6ff36 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/AbstractGroupLayout.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import java.lang.foreign.MemoryLayout; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * A compound layout that aggregates multiple member layouts. There are two ways in which member layouts + * can be combined: if member layouts are laid out one after the other, the resulting group layout is said to be a struct + * (see {@link MemoryLayout#structLayout(MemoryLayout...)}); conversely, if all member layouts are laid out at the same starting offset, + * the resulting group layout is said to be a union (see {@link MemoryLayout#unionLayout(MemoryLayout...)}). + * + * @implSpec + * This class is immutable, thread-safe and value-based. + */ +abstract sealed class AbstractGroupLayout & MemoryLayout> + extends AbstractLayout + permits StructLayoutImpl, UnionLayoutImpl { + + private final Kind kind; + private final List elements; + final long minByteAlignment; + + AbstractGroupLayout(Kind kind, List elements, long byteSize, long byteAlignment, long minByteAlignment, Optional name) { + super(byteSize, byteAlignment, name); // Subclassing creates toctou problems here + this.kind = kind; + this.elements = List.copyOf(elements); + this.minByteAlignment = minByteAlignment; + } + + /** + * Returns the member layouts associated with this group. + * + * @apiNote the order in which member layouts are returned is the same order in which member layouts have + * been passed to one of the group layout factory methods (see {@link MemoryLayout#structLayout(MemoryLayout...)}, + * {@link MemoryLayout#unionLayout(MemoryLayout...)}). + * + * @return the member layouts associated with this group. + */ + public final List memberLayouts() { + return elements; // "elements" are already unmodifiable. + } + + /** + * {@inheritDoc} + */ + @Override + public final String toString() { + return decorateLayoutString(elements.stream() + .map(Object::toString) + .collect(Collectors.joining(kind.delimTag, "[", "]"))); + } + + @Override + public L withByteAlignment(long byteAlignment) { + if (byteAlignment < minByteAlignment) { + throw new IllegalArgumentException("Invalid alignment constraint"); + } + return super.withByteAlignment(byteAlignment); + } + + /** + * {@inheritDoc} + */ + @Override + public final boolean equals(Object other) { + return this == other || + other instanceof AbstractGroupLayout otherGroup && + super.equals(other) && + kind == otherGroup.kind && + elements.equals(otherGroup.elements); + } + + /** + * {@inheritDoc} + */ + @Override + public final int hashCode() { + return Objects.hash(super.hashCode(), kind, elements); + } + + @Override + public final boolean hasNaturalAlignment() { + return byteAlignment() == minByteAlignment; + } + + /** + * The group kind. + */ + enum Kind { + /** + * A 'struct' kind. + */ + STRUCT(""), + /** + * A 'union' kind. + */ + UNION("|"); + + final String delimTag; + + Kind(String delimTag) { + this.delimTag = delimTag; + } + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout$1Holder.class b/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout$1Holder.class new file mode 100644 index 00000000..69abec25 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout$1Holder.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout.class b/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout.class new file mode 100644 index 00000000..e425e257 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout.java b/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout.java new file mode 100644 index 00000000..796f0027 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/AbstractLayout.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import jdk.internal.foreign.LayoutPath; +import jdk.internal.foreign.Utils; + +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemoryLayout.PathElement; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.UnionLayout; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +public abstract sealed class AbstractLayout & MemoryLayout> + permits AbstractGroupLayout, PaddingLayoutImpl, SequenceLayoutImpl, ValueLayouts.AbstractValueLayout { + + private final long byteSize; + private final long byteAlignment; + private final Optional name; + + AbstractLayout(long byteSize, long byteAlignment, Optional name) { + this.byteSize = MemoryLayoutUtil.requireByteSizeValid(byteSize, true); + this.byteAlignment = requirePowerOfTwoAndGreaterOrEqualToOne(byteAlignment); + this.name = Objects.requireNonNull(name); + } + + public final L withName(String name) { + return dup(byteAlignment(), Optional.of(name)); + } + + @SuppressWarnings("unchecked") + public final L withoutName() { + return name.isPresent() ? dup(byteAlignment(), Optional.empty()) : (L) this; + } + + public final Optional name() { + return name; + } + + public L withByteAlignment(long byteAlignment) { + return dup(byteAlignment, name); + } + + public final long byteAlignment() { + return byteAlignment; + } + + public final long byteSize() { + return byteSize; + } + + public boolean hasNaturalAlignment() { + return byteSize == byteAlignment; + } + + // the following methods have to copy the same Javadoc as in MemoryLayout, or subclasses will just show + // the Object methods javadoc + + /** + * {@return the hash code value for this layout} + */ + @Override + public int hashCode() { + return Objects.hash(name, byteSize, byteAlignment); + } + + /** + * Compares the specified object with this layout for equality. Returns {@code true} if and only if the specified + * object is also a layout, and it is equal to this layout. Two layouts are considered equal if they are of + * the same kind, have the same size, name and alignment constraints. Furthermore, depending on the layout kind, additional + * conditions must be satisfied: + *

    + *
  • two value layouts are considered equal if they have the same {@linkplain ValueLayout#order() order}, + * and {@linkplain ValueLayout#carrier() carrier}
  • + *
  • two sequence layouts are considered equal if they have the same element count (see {@link SequenceLayout#elementCount()}), and + * if their element layouts (see {@link SequenceLayout#elementLayout()}) are also equal
  • + *
  • two group layouts are considered equal if they are of the same type (see {@link StructLayout}, + * {@link UnionLayout}) and if their member layouts (see {@link GroupLayout#memberLayouts()}) are also equal
  • + *
+ * + * @param other the object to be compared for equality with this layout. + * @return {@code true} if the specified object is equal to this layout. + */ + @Override + public boolean equals(Object other) { + return other instanceof AbstractLayout otherLayout && + name.equals(otherLayout.name) && + byteSize == otherLayout.byteSize && + byteAlignment == otherLayout.byteAlignment; + } + + /** + * {@return the string representation of this layout} + */ + @Override + public abstract String toString(); + + abstract L dup(long byteAlignment, Optional name); + + String decorateLayoutString(String s) { + if (name().isPresent()) { + s = String.format("%s(%s)", s, name().get()); + } + if (!hasNaturalAlignment()) { + s = byteAlignment() + "%" + s; + } + return s; + } + + private static long requirePowerOfTwoAndGreaterOrEqualToOne(long value) { + if (!Utils.isPowerOfTwo(value) || // value must be a power of two + value < 1) { // value must be greater or equal to 1 + throw new IllegalArgumentException("Invalid alignment: " + value); + } + return value; + } + + public long scale(long offset, long index) { + Utils.checkNonNegativeArgument(offset, "offset"); + Utils.checkNonNegativeArgument(index, "index"); + return Math.addExact(offset, Math.multiplyExact(byteSize(), index)); + } + + public MethodHandle scaleHandle() { + class Holder { + static final MethodHandle MH_SCALE; + static { + try { + MH_SCALE = MethodHandles.lookup().findVirtual(MemoryLayout.class, "scale", + MethodType.methodType(long.class, long.class, long.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + } + return Holder.MH_SCALE.bindTo(this); + } + + + public long byteOffset(PathElement... elements) { + return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::offset, + Set.of(LayoutPath.SequenceElement.class, LayoutPath.SequenceElementByRange.class, LayoutPath.DereferenceElement.class), + elements); + } + + public MethodHandle byteOffsetHandle(PathElement... elements) { + return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::offsetHandle, + Set.of(LayoutPath.DereferenceElement.class), + elements); + } + + public VarHandle varHandle(PathElement... elements) { + Objects.requireNonNull(elements); + if (this instanceof ValueLayout vl && elements.length == 0) { + return vl.varHandle(); // fast path + } + return varHandleInternal(elements); + } + + public VarHandle varHandleInternal(PathElement... elements) { + return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::dereferenceHandle, + Set.of(), elements); + } + + public VarHandle arrayElementVarHandle(PathElement... elements) { + return MethodHandles.collectCoordinates(varHandle(elements), 1, scaleHandle()); + } + + public MethodHandle sliceHandle(PathElement... elements) { + return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::sliceHandle, + Set.of(LayoutPath.DereferenceElement.class), + elements); + } + + public MemoryLayout select(PathElement... elements) { + return computePathOp(LayoutPath.rootPath((MemoryLayout) this), LayoutPath::layout, + Set.of(LayoutPath.SequenceElementByIndex.class, LayoutPath.SequenceElementByRange.class, LayoutPath.DereferenceElement.class), + elements); + } + + private static Z computePathOp(LayoutPath path, Function finalizer, + Set> badTypes, PathElement... elements) { + Objects.requireNonNull(elements); + for (PathElement e : elements) { + Objects.requireNonNull(e); + if (badTypes.contains(e.getClass())) { + throw new IllegalArgumentException("Invalid selection in layout path: " + e); + } + @SuppressWarnings("unchecked") + UnaryOperator pathOp = (UnaryOperator) e; + path = pathOp.apply(path); + } + return finalizer.apply(path); + } +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/MemoryLayoutUtil.class b/tests/test_data/std/jdk/internal/foreign/layout/MemoryLayoutUtil.class new file mode 100644 index 00000000..58b856d4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/MemoryLayoutUtil.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/MemoryLayoutUtil.java b/tests/test_data/std/jdk/internal/foreign/layout/MemoryLayoutUtil.java new file mode 100644 index 00000000..6b8e7738 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/MemoryLayoutUtil.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +public final class MemoryLayoutUtil { + + private MemoryLayoutUtil() { + } + + public static long requireByteSizeValid(long byteSize, boolean allowZero) { + if ((byteSize == 0 && !allowZero) || byteSize < 0) { + throw new IllegalArgumentException("Invalid byte size: " + byteSize); + } + return byteSize; + } + +} \ No newline at end of file diff --git a/tests/test_data/std/jdk/internal/foreign/layout/PaddingLayoutImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/PaddingLayoutImpl.class new file mode 100644 index 00000000..76e53d3b Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/PaddingLayoutImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/PaddingLayoutImpl.java b/tests/test_data/std/jdk/internal/foreign/layout/PaddingLayoutImpl.java new file mode 100644 index 00000000..63973e1c --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/PaddingLayoutImpl.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import java.lang.foreign.PaddingLayout; +import java.util.Objects; +import java.util.Optional; + +public final class PaddingLayoutImpl extends AbstractLayout implements PaddingLayout { + + private PaddingLayoutImpl(long byteSize) { + this(byteSize, 1, Optional.empty()); + } + + private PaddingLayoutImpl(long byteSize, long byteAlignment, Optional name) { + super(byteSize, byteAlignment, name); + } + + @Override + public String toString() { + return decorateLayoutString("x" + byteSize()); + } + + @Override + public boolean equals(Object other) { + return this == other || + other instanceof PaddingLayoutImpl otherPadding && + super.equals(other) && + byteSize() == otherPadding.byteSize(); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), byteSize()); + } + + @Override + PaddingLayoutImpl dup(long byteAlignment, Optional name) { + return new PaddingLayoutImpl(byteSize(), byteAlignment, name); + } + + @Override + public boolean hasNaturalAlignment() { + return true; + } + + public static PaddingLayout of(long byteSize) { + return new PaddingLayoutImpl(byteSize); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/SequenceLayoutImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/SequenceLayoutImpl.class new file mode 100644 index 00000000..d9fb4aae Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/SequenceLayoutImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/SequenceLayoutImpl.java b/tests/test_data/std/jdk/internal/foreign/layout/SequenceLayoutImpl.java new file mode 100644 index 00000000..16cffdad --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/SequenceLayoutImpl.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import jdk.internal.foreign.Utils; + +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.SequenceLayout; +import java.util.Objects; +import java.util.Optional; + +public final class SequenceLayoutImpl extends AbstractLayout implements SequenceLayout { + + private final long elemCount; + private final MemoryLayout elementLayout; + + private SequenceLayoutImpl(long elemCount, MemoryLayout elementLayout) { + this(elemCount, elementLayout, elementLayout.byteAlignment(), Optional.empty()); + } + + private SequenceLayoutImpl(long elemCount, MemoryLayout elementLayout, long byteAlignment, Optional name) { + super(Math.multiplyExact(elemCount, elementLayout.byteSize()), byteAlignment, name); + this.elemCount = elemCount; + this.elementLayout = elementLayout; + } + + /** + * {@return the element layout associated with this sequence layout} + */ + public MemoryLayout elementLayout() { + return elementLayout; + } + + /** + * {@return the element count of this sequence layout} + */ + public long elementCount() { + return elemCount; + } + + /** + * Returns a sequence layout with the same element layout, alignment constraints and name as this sequence layout, + * but with the specified element count. + * + * @param elementCount the new element count. + * @return a sequence layout with the given element count. + * @throws IllegalArgumentException if {@code elementCount < 0}. + */ + public SequenceLayout withElementCount(long elementCount) { + return Utils.wrapOverflow(() -> + new SequenceLayoutImpl(elementCount, elementLayout, byteAlignment(), name())); + } + + /** + * Re-arrange the elements in this sequence layout into a multi-dimensional sequence layout. + * The resulting layout is a sequence layout where element layouts in the flattened projection of this + * sequence layout (see {@link #flatten()}) are re-arranged into one or more nested sequence layouts + * according to the provided element counts. This transformation preserves the layout size; + * that is, multiplying the provided element counts must yield the same element count + * as the flattened projection of this sequence layout. + *

+ * For instance, given a sequence layout of the kind: + * {@snippet lang = java: + * var seq = MemoryLayout.sequenceLayout(4, MemoryLayout.sequenceLayout(3, ValueLayout.JAVA_INT)); + *} + * calling {@code seq.reshape(2, 6)} will yield the following sequence layout: + * {@snippet lang = java: + * var reshapeSeq = MemoryLayout.sequenceLayout(2, MemoryLayout.sequenceLayout(6, ValueLayout.JAVA_INT)); + *} + *

+ * If one of the provided element count is the special value {@code -1}, then the element + * count in that position will be inferred from the remaining element counts and the + * element count of the flattened projection of this layout. For instance, a layout equivalent to + * the above {@code reshapeSeq} can also be computed in the following ways: + * {@snippet lang = java: + * var reshapeSeqImplicit1 = seq.reshape(-1, 6); + * var reshapeSeqImplicit2 = seq.reshape(2, -1); + *} + * + * @param elementCounts an array of element counts, of which at most one can be {@code -1}. + * @return a sequence layout where element layouts in the flattened projection of this + * sequence layout (see {@link #flatten()}) are re-arranged into one or more nested sequence layouts. + * @throws IllegalArgumentException if two or more element counts are set to {@code -1}, or if one + * or more element count is {@code <= 0} (but other than {@code -1}) or, if, after any required inference, + * multiplying the element counts does not yield the same element count as the flattened projection of this + * sequence layout. + */ + public SequenceLayout reshape(long... elementCounts) { + Objects.requireNonNull(elementCounts); + if (elementCounts.length == 0) { + throw new IllegalArgumentException(); + } + SequenceLayout flat = flatten(); + long expectedCount = flat.elementCount(); + + long actualCount = 1; + int inferPosition = -1; + for (int i = 0; i < elementCounts.length; i++) { + if (elementCounts[i] == -1) { + if (inferPosition == -1) { + inferPosition = i; + } else { + throw new IllegalArgumentException("Too many unspecified element counts"); + } + } else if (elementCounts[i] <= 0) { + throw new IllegalArgumentException("Invalid element count: " + elementCounts[i]); + } else { + actualCount = elementCounts[i] * actualCount; + } + } + + // infer an unspecified element count (if any) + if (inferPosition != -1) { + long inferredCount = expectedCount / actualCount; + elementCounts[inferPosition] = inferredCount; + actualCount = actualCount * inferredCount; + } + + if (actualCount != expectedCount) { + throw new IllegalArgumentException("Element counts do not match expected size: " + expectedCount); + } + + MemoryLayout res = flat.elementLayout(); + for (int i = elementCounts.length - 1; i >= 0; i--) { + res = MemoryLayout.sequenceLayout(elementCounts[i], res); + } + return (SequenceLayoutImpl) res; + } + + /** + * Returns a flattened sequence layout. The element layout of the returned sequence layout + * is the first non-sequence element layout found by recursively traversing the element layouts of this sequence layout. + * This transformation preserves the layout size; nested sequence layout in this sequence layout will + * be dropped and their element counts will be incorporated into that of the returned sequence layout. + * For instance, given a sequence layout of the kind: + * {@snippet lang = java: + * var seq = MemoryLayout.sequenceLayout(4, MemoryLayout.sequenceLayout(3, ValueLayout.JAVA_INT)); + *} + * calling {@code seq.flatten()} will yield the following sequence layout: + * {@snippet lang = java: + * var flattenedSeq = MemoryLayout.sequenceLayout(12, ValueLayout.JAVA_INT); + *} + * + * @return a sequence layout with the same size as this layout (but, possibly, with different + * element count), whose element layout is not a sequence layout. + */ + public SequenceLayout flatten() { + long count = elementCount(); + MemoryLayout elemLayout = elementLayout(); + while (elemLayout instanceof SequenceLayoutImpl elemSeq) { + count = count * elemSeq.elementCount(); + elemLayout = elemSeq.elementLayout(); + } + return MemoryLayout.sequenceLayout(count, elemLayout); + } + + @Override + public String toString() { + boolean max = (Long.MAX_VALUE / Math.max(1, elementLayout.byteSize())) == elemCount; + return decorateLayoutString(String.format("[%s:%s]", + max ? "*" : elemCount, elementLayout)); + } + + @Override + public boolean equals(Object other) { + return this == other || + other instanceof SequenceLayoutImpl otherSeq && + super.equals(other) && + elemCount == otherSeq.elemCount && + elementLayout.equals(otherSeq.elementLayout); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), elemCount, elementLayout); + } + + @Override + SequenceLayoutImpl dup(long byteAlignment, Optional name) { + return new SequenceLayoutImpl(elementCount(), elementLayout, byteAlignment, name); + } + + @Override + public SequenceLayoutImpl withByteAlignment(long byteAlignment) { + if (byteAlignment < elementLayout.byteAlignment()) { + throw new IllegalArgumentException("Invalid alignment constraint"); + } + return super.withByteAlignment(byteAlignment); + } + + @Override + public boolean hasNaturalAlignment() { + return byteAlignment() == elementLayout.byteAlignment(); + } + + public static SequenceLayout of(long elementCount, MemoryLayout elementLayout) { + return new SequenceLayoutImpl(elementCount, elementLayout); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/StructLayoutImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/StructLayoutImpl.class new file mode 100644 index 00000000..f843e6f7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/StructLayoutImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/StructLayoutImpl.java b/tests/test_data/std/jdk/internal/foreign/layout/StructLayoutImpl.java new file mode 100644 index 00000000..9da6594e --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/StructLayoutImpl.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.StructLayout; +import java.util.List; +import java.util.Optional; + +public final class StructLayoutImpl extends AbstractGroupLayout implements StructLayout { + + private StructLayoutImpl(List elements, long byteSize, long byteAlignment, long minByteAlignment, Optional name) { + super(Kind.STRUCT, elements, byteSize, byteAlignment, minByteAlignment, name); + } + + @Override + StructLayoutImpl dup(long byteAlignment, Optional name) { + return new StructLayoutImpl(memberLayouts(), byteSize(), byteAlignment, minByteAlignment, name); + } + + public static StructLayout of(List elements) { + long size = 0; + long align = 1; + for (MemoryLayout elem : elements) { + if (size % elem.byteAlignment() != 0) { + throw new IllegalArgumentException("Invalid alignment constraint for member layout: " + elem); + } + size = Math.addExact(size, elem.byteSize()); + align = Math.max(align, elem.byteAlignment()); + } + return new StructLayoutImpl(elements, size, align, align, Optional.empty()); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/UnionLayoutImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/UnionLayoutImpl.class new file mode 100644 index 00000000..f513eb79 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/UnionLayoutImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/UnionLayoutImpl.java b/tests/test_data/std/jdk/internal/foreign/layout/UnionLayoutImpl.java new file mode 100644 index 00000000..544ca838 --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/UnionLayoutImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.UnionLayout; +import java.util.List; +import java.util.Optional; + +public final class UnionLayoutImpl extends AbstractGroupLayout implements UnionLayout { + + private UnionLayoutImpl(List elements, long byteSize, long byteAlignment, long minByteAlignment, Optional name) { + super(Kind.UNION, elements, byteSize, byteAlignment, minByteAlignment, name); + } + + @Override + UnionLayoutImpl dup(long byteAlignment, Optional name) { + return new UnionLayoutImpl(memberLayouts(), byteSize(), byteAlignment, minByteAlignment, name); + } + + public static UnionLayout of(List elements) { + long size = 0; + long align = 1; + for (MemoryLayout elem : elements) { + size = Math.max(size, elem.byteSize()); + align = Math.max(align, elem.byteAlignment()); + } + return new UnionLayoutImpl(elements, size, align, align, Optional.empty()); + } + +} diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout$1VarHandleCache.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout$1VarHandleCache.class new file mode 100644 index 00000000..e7eb7f84 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout$1VarHandleCache.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout.class new file mode 100644 index 00000000..2c7d3ed3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$AbstractValueLayout.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfAddressImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfAddressImpl.class new file mode 100644 index 00000000..d276401d Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfAddressImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfBooleanImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfBooleanImpl.class new file mode 100644 index 00000000..505a4cfe Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfBooleanImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfByteImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfByteImpl.class new file mode 100644 index 00000000..7ad36f05 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfByteImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfCharImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfCharImpl.class new file mode 100644 index 00000000..f1fa261e Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfCharImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfDoubleImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfDoubleImpl.class new file mode 100644 index 00000000..96708ef7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfDoubleImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfFloatImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfFloatImpl.class new file mode 100644 index 00000000..5115f7f9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfFloatImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfIntImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfIntImpl.class new file mode 100644 index 00000000..e74598f7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfIntImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfLongImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfLongImpl.class new file mode 100644 index 00000000..06adea42 Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfLongImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfShortImpl.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfShortImpl.class new file mode 100644 index 00000000..d21922ad Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts$OfShortImpl.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts.class b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts.class new file mode 100644 index 00000000..c46c880f Binary files /dev/null and b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts.class differ diff --git a/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts.java b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts.java new file mode 100644 index 00000000..4d19879b --- /dev/null +++ b/tests/test_data/std/jdk/internal/foreign/layout/ValueLayouts.java @@ -0,0 +1,412 @@ +/* + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package jdk.internal.foreign.layout; + +import jdk.internal.foreign.Utils; +import jdk.internal.misc.Unsafe; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Stable; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A value layout. A value layout is used to model the memory layout associated with values of basic data types, such as integral types + * (either signed or unsigned) and floating-point types. Each value layout has a size, an alignment (expressed in bytes), + * a {@linkplain ByteOrder byte order}, and a carrier, that is, the Java type that should be used when + * {@linkplain MemorySegment#get(ValueLayout.OfInt, long) accessing} a memory region using the value layout. + *

+ * This class defines useful value layout constants for Java primitive types and addresses. + * The layout constants in this class make implicit alignment and byte-ordering assumption: all layout + * constants in this class are byte-aligned, and their byte order is set to the {@linkplain ByteOrder#nativeOrder() platform default}, + * thus making it easy to work with other APIs, such as arrays and {@link java.nio.ByteBuffer}. + * + * @implSpec This class and its subclasses are immutable, thread-safe and value-based. + */ +public final class ValueLayouts { + + // Suppresses default constructor, ensuring non-instantiability. + private ValueLayouts() {} + + abstract static sealed class AbstractValueLayout & ValueLayout> extends AbstractLayout { + + static final int ADDRESS_SIZE_BYTES = Unsafe.ADDRESS_SIZE; + + private final Class carrier; + private final ByteOrder order; + @Stable + private VarHandle handle; + + AbstractValueLayout(Class carrier, ByteOrder order, long byteSize, long byteAlignment, Optional name) { + super(byteSize, byteAlignment, name); + this.carrier = carrier; + this.order = order; + assertCarrierSize(carrier, byteSize); + } + + /** + * {@return the value's byte order} + */ + public final ByteOrder order() { + return order; + } + + /** + * Returns a value layout with the same carrier, alignment constraints and name as this value layout, + * but with the specified byte order. + * + * @param order the desired byte order. + * @return a value layout with the given byte order. + */ + public final V withOrder(ByteOrder order) { + Objects.requireNonNull(order); + return dup(order, byteAlignment(), name()); + } + + @Override + public String toString() { + char descriptor = carrier.descriptorString().charAt(0); + if (order == ByteOrder.LITTLE_ENDIAN) { + descriptor = Character.toLowerCase(descriptor); + } + return decorateLayoutString(String.format("%s%d", descriptor, byteSize())); + } + + @Override + public boolean equals(Object other) { + return this == other || + other instanceof AbstractValueLayout otherValue && + super.equals(other) && + carrier.equals(otherValue.carrier) && + order.equals(otherValue.order); + } + + /** + * {@return the carrier associated with this value layout} + */ + public final Class carrier() { + return carrier; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), order, carrier); + } + + @Override + final V dup(long byteAlignment, Optional name) { + return dup(order(), byteAlignment, name); + } + + abstract V dup(ByteOrder order, long byteAlignment, Optional name); + + static void assertCarrierSize(Class carrier, long byteSize) { + assert isValidCarrier(carrier); + assert carrier != MemorySegment.class + // MemorySegment byteSize must always equal ADDRESS_SIZE_BYTES + || byteSize == ADDRESS_SIZE_BYTES; + assert !carrier.isPrimitive() || + // Primitive class byteSize must always correspond + byteSize == (carrier == boolean.class ? 1 : + Utils.byteWidthOfPrimitive(carrier)); + } + + static boolean isValidCarrier(Class carrier) { + // void.class is not valid + return carrier == boolean.class + || carrier == byte.class + || carrier == short.class + || carrier == char.class + || carrier == int.class + || carrier == long.class + || carrier == float.class + || carrier == double.class + || carrier == MemorySegment.class; + } + + @ForceInline + public final VarHandle varHandle() { + final class VarHandleCache { + private static final Map HANDLE_MAP = new ConcurrentHashMap<>(); + } + if (handle == null) { + // this store to stable field is safe, because return value of 'makeMemoryAccessVarHandle' has stable identity + handle = VarHandleCache.HANDLE_MAP.computeIfAbsent(self().withoutName(), _ -> varHandleInternal()); + } + return handle; + } + + @SuppressWarnings("unchecked") + final V self() { + return (V) this; + } + } + + public static final class OfBooleanImpl extends AbstractValueLayout implements ValueLayout.OfBoolean { + + private OfBooleanImpl(ByteOrder order, long byteAlignment, Optional name) { + super(boolean.class, order, Byte.BYTES, byteAlignment, name); + } + + @Override + OfBooleanImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfBooleanImpl(order, byteAlignment, name); + } + + public static OfBoolean of(ByteOrder order) { + return new OfBooleanImpl(order, Byte.BYTES, Optional.empty()); + } + } + + public static final class OfByteImpl extends AbstractValueLayout implements ValueLayout.OfByte { + + private OfByteImpl(ByteOrder order, long byteAlignment, Optional name) { + super(byte.class, order, Byte.BYTES, byteAlignment, name); + } + + @Override + OfByteImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfByteImpl(order, byteAlignment, name); + } + + public static OfByte of(ByteOrder order) { + return new OfByteImpl(order, Byte.BYTES, Optional.empty()); + } + } + + public static final class OfCharImpl extends AbstractValueLayout implements ValueLayout.OfChar { + + private OfCharImpl(ByteOrder order, long byteAlignment, Optional name) { + super(char.class, order, Character.BYTES, byteAlignment, name); + } + + @Override + OfCharImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfCharImpl(order, byteAlignment, name); + } + + public static OfChar of(ByteOrder order) { + return new OfCharImpl(order, Character.BYTES, Optional.empty()); + } + } + + public static final class OfShortImpl extends AbstractValueLayout implements ValueLayout.OfShort { + + private OfShortImpl(ByteOrder order, long byteAlignment, Optional name) { + super(short.class, order, Short.BYTES, byteAlignment, name); + } + + @Override + OfShortImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfShortImpl(order, byteAlignment, name); + } + + public static OfShort of(ByteOrder order) { + return new OfShortImpl(order, Short.BYTES, Optional.empty()); + } + } + + public static final class OfIntImpl extends AbstractValueLayout implements ValueLayout.OfInt { + + private OfIntImpl(ByteOrder order, long byteAlignment, Optional name) { + super(int.class, order, Integer.BYTES, byteAlignment, name); + } + + @Override + OfIntImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfIntImpl(order, byteAlignment, name); + } + + public static OfInt of(ByteOrder order) { + return new OfIntImpl(order, Integer.BYTES, Optional.empty()); + } + } + + public static final class OfFloatImpl extends AbstractValueLayout implements ValueLayout.OfFloat { + + private OfFloatImpl(ByteOrder order, long byteAlignment, Optional name) { + super(float.class, order, Float.BYTES, byteAlignment, name); + } + + @Override + OfFloatImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfFloatImpl(order, byteAlignment, name); + } + + public static OfFloat of(ByteOrder order) { + return new OfFloatImpl(order, Float.BYTES, Optional.empty()); + } + } + + public static final class OfLongImpl extends AbstractValueLayout implements ValueLayout.OfLong { + + private OfLongImpl(ByteOrder order, long byteAlignment, Optional name) { + super(long.class, order, Long.BYTES, byteAlignment, name); + } + + @Override + OfLongImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfLongImpl(order, byteAlignment, name); + } + + public static OfLong of(ByteOrder order) { + return new OfLongImpl(order, Long.BYTES, Optional.empty()); + } + } + + public static final class OfDoubleImpl extends AbstractValueLayout implements ValueLayout.OfDouble { + + private OfDoubleImpl(ByteOrder order, long byteAlignment, Optional name) { + super(double.class, order, Double.BYTES, byteAlignment, name); + } + + @Override + OfDoubleImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfDoubleImpl(order, byteAlignment, name); + } + + public static OfDouble of(ByteOrder order) { + return new OfDoubleImpl(order, Double.BYTES, Optional.empty()); + } + + } + + public static final class OfAddressImpl extends AbstractValueLayout implements AddressLayout { + + private final MemoryLayout targetLayout; + + private OfAddressImpl(ByteOrder order, long byteSize, long byteAlignment, MemoryLayout targetLayout, Optional name) { + super(MemorySegment.class, order, byteSize, byteAlignment, name); + this.targetLayout = targetLayout; + } + + @Override + OfAddressImpl dup(ByteOrder order, long byteAlignment, Optional name) { + return new OfAddressImpl(order, byteSize(), byteAlignment,targetLayout, name); + } + + @Override + public boolean equals(Object other) { + return super.equals(other) && + Objects.equals(((OfAddressImpl)other).targetLayout, this.targetLayout); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), targetLayout); + } + + @Override + @CallerSensitive + public AddressLayout withTargetLayout(MemoryLayout layout) { + Reflection.ensureNativeAccess(Reflection.getCallerClass(), AddressLayout.class, "withTargetLayout"); + Objects.requireNonNull(layout); + return new OfAddressImpl(order(), byteSize(), byteAlignment(), layout, name()); + } + + @Override + public AddressLayout withoutTargetLayout() { + return new OfAddressImpl(order(), byteSize(), byteAlignment(), null, name()); + } + + @Override + public Optional targetLayout() { + return Optional.ofNullable(targetLayout); + } + + public static AddressLayout of(ByteOrder order) { + return new OfAddressImpl(order, ADDRESS_SIZE_BYTES, ADDRESS_SIZE_BYTES, null, Optional.empty()); + } + + @Override + public String toString() { + char descriptor = 'A'; + if (order() == ByteOrder.LITTLE_ENDIAN) { + descriptor = Character.toLowerCase(descriptor); + } + String str = decorateLayoutString(String.format("%s%d", descriptor, byteSize())); + if (targetLayout != null) { + str += ":" + targetLayout; + } + return str; + } + } + + /** + * Creates a value layout of given Java carrier and byte order. The type of resulting value layout is determined + * by the carrier provided: + *

    + *
  • {@link ValueLayout.OfBoolean}, for {@code boolean.class}
  • + *
  • {@link ValueLayout.OfByte}, for {@code byte.class}
  • + *
  • {@link ValueLayout.OfShort}, for {@code short.class}
  • + *
  • {@link ValueLayout.OfChar}, for {@code char.class}
  • + *
  • {@link ValueLayout.OfInt}, for {@code int.class}
  • + *
  • {@link ValueLayout.OfFloat}, for {@code float.class}
  • + *
  • {@link ValueLayout.OfLong}, for {@code long.class}
  • + *
  • {@link ValueLayout.OfDouble}, for {@code double.class}
  • + *
  • {@link AddressLayout}, for {@code MemorySegment.class}
  • + *
+ * @param carrier the value layout carrier. + * @param order the value layout's byte order. + * @return a value layout with the given Java carrier and byte-order. + * @throws IllegalArgumentException if the carrier type is not supported. + */ + public static ValueLayout valueLayout(Class carrier, ByteOrder order) { + Objects.requireNonNull(carrier); + Objects.requireNonNull(order); + if (carrier == boolean.class) { + return ValueLayouts.OfBooleanImpl.of(order); + } else if (carrier == char.class) { + return ValueLayouts.OfCharImpl.of(order); + } else if (carrier == byte.class) { + return ValueLayouts.OfByteImpl.of(order); + } else if (carrier == short.class) { + return ValueLayouts.OfShortImpl.of(order); + } else if (carrier == int.class) { + return ValueLayouts.OfIntImpl.of(order); + } else if (carrier == float.class) { + return ValueLayouts.OfFloatImpl.of(order); + } else if (carrier == long.class) { + return ValueLayouts.OfLongImpl.of(order); + } else if (carrier == double.class) { + return ValueLayouts.OfDoubleImpl.of(order); + } else if (carrier == MemorySegment.class) { + return ValueLayouts.OfAddressImpl.of(order); + } else { + throw new IllegalArgumentException("Unsupported carrier: " + carrier.getName()); + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/BMPSet.class b/tests/test_data/std/jdk/internal/icu/impl/BMPSet.class new file mode 100644 index 00000000..5411af0a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/BMPSet.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/BMPSet.java b/tests/test_data/std/jdk/internal/icu/impl/BMPSet.java new file mode 100644 index 00000000..e10bc3db --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/BMPSet.java @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ****************************************************************************** + * + * Copyright (C) 2009-2014, International Business Machines + * Corporation and others. All Rights Reserved. + * + ****************************************************************************** + */ + +package jdk.internal.icu.impl; + +import jdk.internal.icu.text.UnicodeSet.SpanCondition; +import jdk.internal.icu.util.OutputInt; + +/** + * Helper class for frozen UnicodeSets, implements contains() and span() optimized for BMP code points. + * + * Latin-1: Look up bytes. + * 2-byte characters: Bits organized vertically. + * 3-byte characters: Use zero/one/mixed data per 64-block in U+0000..U+FFFF, with mixed for illegal ranges. + * Supplementary characters: Call contains() on the parent set. + */ +public final class BMPSet { + + /** + * One boolean ('true' or 'false') per Latin-1 character. + */ + private boolean[] latin1Contains; + + /** + * One bit per code point from U+0000..U+07FF. The bits are organized vertically; consecutive code points + * correspond to the same bit positions in consecutive table words. With code point parts lead=c{10..6} + * trail=c{5..0} it is set.contains(c)==(table7FF[trail] bit lead) + * + * Bits for 0..7F (non-shortest forms) are set to the result of contains(FFFD) for faster validity checking at + * runtime. + */ + private int[] table7FF; + + /** + * One bit per 64 BMP code points. The bits are organized vertically; consecutive 64-code point blocks + * correspond to the same bit position in consecutive table words. With code point parts lead=c{15..12} + * t1=c{11..6} test bits (lead+16) and lead in bmpBlockBits[t1]. If the upper bit is 0, then the lower bit + * indicates if contains(c) for all code points in the 64-block. If the upper bit is 1, then the block is mixed + * and set.contains(c) must be called. + * + * Bits for 0..7FF (non-shortest forms) and D800..DFFF are set to the result of contains(FFFD) for faster + * validity checking at runtime. + */ + private int[] bmpBlockBits; + + /** + * Inversion list indexes for restricted binary searches in findCodePoint(), from findCodePoint(U+0800, U+1000, + * U+2000, .., U+F000, U+10000). U+0800 is the first 3-byte-UTF-8 code point. Code points below U+0800 are + * always looked up in the bit tables. The last pair of indexes is for finding supplementary code points. + */ + private int[] list4kStarts; + + /** + * The inversion list of the parent set, for the slower contains() implementation for mixed BMP blocks and for + * supplementary code points. The list is terminated with list[listLength-1]=0x110000. + */ + private final int[] list; + private final int listLength; // length used; list may be longer to minimize reallocs + + public BMPSet(final int[] parentList, int parentListLength) { + list = parentList; + listLength = parentListLength; + latin1Contains = new boolean[0x100]; + table7FF = new int[64]; + bmpBlockBits = new int[64]; + list4kStarts = new int[18]; + + /* + * Set the list indexes for binary searches for U+0800, U+1000, U+2000, .., U+F000, U+10000. U+0800 is the + * first 3-byte-UTF-8 code point. Lower code points are looked up in the bit tables. The last pair of + * indexes is for finding supplementary code points. + */ + list4kStarts[0] = findCodePoint(0x800, 0, listLength - 1); + int i; + for (i = 1; i <= 0x10; ++i) { + list4kStarts[i] = findCodePoint(i << 12, list4kStarts[i - 1], listLength - 1); + } + list4kStarts[0x11] = listLength - 1; + + initBits(); + } + + public boolean contains(int c) { + if (c <= 0xff) { + return (latin1Contains[c]); + } else if (c <= 0x7ff) { + return ((table7FF[c & 0x3f] & (1 << (c >> 6))) != 0); + } else if (c < 0xd800 || (c >= 0xe000 && c <= 0xffff)) { + int lead = c >> 12; + int twoBits = (bmpBlockBits[(c >> 6) & 0x3f] >> lead) & 0x10001; + if (twoBits <= 1) { + // All 64 code points with the same bits 15..6 + // are either in the set or not. + return (0 != twoBits); + } else { + // Look up the code point in its 4k block of code points. + return containsSlow(c, list4kStarts[lead], list4kStarts[lead + 1]); + } + } else if (c <= 0x10ffff) { + // surrogate or supplementary code point + return containsSlow(c, list4kStarts[0xd], list4kStarts[0x11]); + } else { + // Out-of-range code points get false, consistent with long-standing + // behavior of UnicodeSet.contains(c). + return false; + } + } + + /** + * Span the initial substring for which each character c has spanCondition==contains(c). It must be + * spanCondition==0 or 1. + * + * @param start The start index + * @param outCount If not null: Receives the number of code points in the span. + * @return the limit (exclusive end) of the span + * + * NOTE: to reduce the overhead of function call to contains(c), it is manually inlined here. Check for + * sufficient length for trail unit for each surrogate pair. Handle single surrogates as surrogate code points + * as usual in ICU. + */ + public final int span(CharSequence s, int start, SpanCondition spanCondition, + OutputInt outCount) { + char c, c2; + int i = start; + int limit = s.length(); + int numSupplementary = 0; + if (SpanCondition.NOT_CONTAINED != spanCondition) { + // span + while (i < limit) { + c = s.charAt(i); + if (c <= 0xff) { + if (!latin1Contains[c]) { + break; + } + } else if (c <= 0x7ff) { + if ((table7FF[c & 0x3f] & (1 << (c >> 6))) == 0) { + break; + } + } else if (c < 0xd800 || + c >= 0xdc00 || (i + 1) == limit || (c2 = s.charAt(i + 1)) < 0xdc00 || c2 >= 0xe000) { + int lead = c >> 12; + int twoBits = (bmpBlockBits[(c >> 6) & 0x3f] >> lead) & 0x10001; + if (twoBits <= 1) { + // All 64 code points with the same bits 15..6 + // are either in the set or not. + if (twoBits == 0) { + break; + } + } else { + // Look up the code point in its 4k block of code points. + if (!containsSlow(c, list4kStarts[lead], list4kStarts[lead + 1])) { + break; + } + } + } else { + // surrogate pair + int supplementary = UCharacterProperty.getRawSupplementary(c, c2); + if (!containsSlow(supplementary, list4kStarts[0x10], list4kStarts[0x11])) { + break; + } + ++numSupplementary; + ++i; + } + ++i; + } + } else { + // span not + while (i < limit) { + c = s.charAt(i); + if (c <= 0xff) { + if (latin1Contains[c]) { + break; + } + } else if (c <= 0x7ff) { + if ((table7FF[c & 0x3f] & (1 << (c >> 6))) != 0) { + break; + } + } else if (c < 0xd800 || + c >= 0xdc00 || (i + 1) == limit || (c2 = s.charAt(i + 1)) < 0xdc00 || c2 >= 0xe000) { + int lead = c >> 12; + int twoBits = (bmpBlockBits[(c >> 6) & 0x3f] >> lead) & 0x10001; + if (twoBits <= 1) { + // All 64 code points with the same bits 15..6 + // are either in the set or not. + if (twoBits != 0) { + break; + } + } else { + // Look up the code point in its 4k block of code points. + if (containsSlow(c, list4kStarts[lead], list4kStarts[lead + 1])) { + break; + } + } + } else { + // surrogate pair + int supplementary = UCharacterProperty.getRawSupplementary(c, c2); + if (containsSlow(supplementary, list4kStarts[0x10], list4kStarts[0x11])) { + break; + } + ++numSupplementary; + ++i; + } + ++i; + } + } + if (outCount != null) { + int spanLength = i - start; + outCount.value = spanLength - numSupplementary; // number of code points + } + return i; + } + + /** + * Symmetrical with span(). + * Span the trailing substring for which each character c has spanCondition==contains(c). It must be s.length >= + * limit and spanCondition==0 or 1. + * + * @return The string index which starts the span (i.e. inclusive). + */ + public final int spanBack(CharSequence s, int limit, SpanCondition spanCondition) { + char c, c2; + + if (SpanCondition.NOT_CONTAINED != spanCondition) { + // span + for (;;) { + c = s.charAt(--limit); + if (c <= 0xff) { + if (!latin1Contains[c]) { + break; + } + } else if (c <= 0x7ff) { + if ((table7FF[c & 0x3f] & (1 << (c >> 6))) == 0) { + break; + } + } else if (c < 0xd800 || + c < 0xdc00 || 0 == limit || (c2 = s.charAt(limit - 1)) < 0xd800 || c2 >= 0xdc00) { + int lead = c >> 12; + int twoBits = (bmpBlockBits[(c >> 6) & 0x3f] >> lead) & 0x10001; + if (twoBits <= 1) { + // All 64 code points with the same bits 15..6 + // are either in the set or not. + if (twoBits == 0) { + break; + } + } else { + // Look up the code point in its 4k block of code points. + if (!containsSlow(c, list4kStarts[lead], list4kStarts[lead + 1])) { + break; + } + } + } else { + // surrogate pair + int supplementary = UCharacterProperty.getRawSupplementary(c2, c); + if (!containsSlow(supplementary, list4kStarts[0x10], list4kStarts[0x11])) { + break; + } + --limit; + } + if (0 == limit) { + return 0; + } + } + } else { + // span not + for (;;) { + c = s.charAt(--limit); + if (c <= 0xff) { + if (latin1Contains[c]) { + break; + } + } else if (c <= 0x7ff) { + if ((table7FF[c & 0x3f] & (1 << (c >> 6))) != 0) { + break; + } + } else if (c < 0xd800 || + c < 0xdc00 || 0 == limit || (c2 = s.charAt(limit - 1)) < 0xd800 || c2 >= 0xdc00) { + int lead = c >> 12; + int twoBits = (bmpBlockBits[(c >> 6) & 0x3f] >> lead) & 0x10001; + if (twoBits <= 1) { + // All 64 code points with the same bits 15..6 + // are either in the set or not. + if (twoBits != 0) { + break; + } + } else { + // Look up the code point in its 4k block of code points. + if (containsSlow(c, list4kStarts[lead], list4kStarts[lead + 1])) { + break; + } + } + } else { + // surrogate pair + int supplementary = UCharacterProperty.getRawSupplementary(c2, c); + if (containsSlow(supplementary, list4kStarts[0x10], list4kStarts[0x11])) { + break; + } + --limit; + } + if (0 == limit) { + return 0; + } + } + } + return limit + 1; + } + + /** + * Set bits in a bit rectangle in "vertical" bit organization. start> 6; // Named for UTF-8 2-byte lead byte with upper 5 bits. + int trail = start & 0x3f; // Named for UTF-8 2-byte trail byte with lower 6 bits. + + // Set one bit indicating an all-one block. + int bits = 1 << lead; + if ((start + 1) == limit) { // Single-character shortcut. + table[trail] |= bits; + return; + } + + int limitLead = limit >> 6; + int limitTrail = limit & 0x3f; + + if (lead == limitLead) { + // Partial vertical bit column. + while (trail < limitTrail) { + table[trail++] |= bits; + } + } else { + // Partial vertical bit column, + // followed by a bit rectangle, + // followed by another partial vertical bit column. + if (trail > 0) { + do { + table[trail++] |= bits; + } while (trail < 64); + ++lead; + } + if (lead < limitLead) { + bits = ~((1 << lead) - 1); + if (limitLead < 0x20) { + bits &= (1 << limitLead) - 1; + } + for (trail = 0; trail < 64; ++trail) { + table[trail] |= bits; + } + } + // limit<=0x800. If limit==0x800 then limitLead=32 and limitTrail=0. + // In that case, bits=1<= 0x100) { + break; + } + do { + latin1Contains[start++] = true; + } while (start < limit && start < 0x100); + } while (limit <= 0x100); + + // Set table7FF[]. + while (start < 0x800) { + set32x64Bits(table7FF, start, limit <= 0x800 ? limit : 0x800); + if (limit > 0x800) { + start = 0x800; + break; + } + + start = list[listIndex++]; + if (listIndex < listLength) { + limit = list[listIndex++]; + } else { + limit = 0x110000; + } + } + + // Set bmpBlockBits[]. + int minStart = 0x800; + while (start < 0x10000) { + if (limit > 0x10000) { + limit = 0x10000; + } + + if (start < minStart) { + start = minStart; + } + if (start < limit) { // Else: Another range entirely in a known mixed-value block. + if (0 != (start & 0x3f)) { + // Mixed-value block of 64 code points. + start >>= 6; + bmpBlockBits[start & 0x3f] |= 0x10001 << (start >> 6); + start = (start + 1) << 6; // Round up to the next block boundary. + minStart = start; // Ignore further ranges in this block. + } + if (start < limit) { + if (start < (limit & ~0x3f)) { + // Multiple all-ones blocks of 64 code points each. + set32x64Bits(bmpBlockBits, start >> 6, limit >> 6); + } + + if (0 != (limit & 0x3f)) { + // Mixed-value block of 64 code points. + limit >>= 6; + bmpBlockBits[limit & 0x3f] |= 0x10001 << (limit >> 6); + limit = (limit + 1) << 6; // Round up to the next block boundary. + minStart = limit; // Ignore further ranges in this block. + } + } + } + + if (limit == 0x10000) { + break; + } + + start = list[listIndex++]; + if (listIndex < listLength) { + limit = list[listIndex++]; + } else { + limit = 0x110000; + } + } + } + + /** + * Same as UnicodeSet.findCodePoint(int c) except that the binary search is restricted for finding code + * points in a certain range. + * + * For restricting the search for finding in the range start..end, pass in lo=findCodePoint(start) and + * hi=findCodePoint(end) with 0<=lo<=hi= hi || c >= list[hi - 1]) + return hi; + // invariant: c >= list[lo] + // invariant: c < list[hi] + for (;;) { + int i = (lo + hi) >>> 1; + if (i == lo) { + break; // Found! + } else if (c < list[i]) { + hi = i; + } else { + lo = i; + } + } + return hi; + } + + private final boolean containsSlow(int c, int lo, int hi) { + return (0 != (findCodePoint(c, lo, hi) & 1)); + } +} + diff --git a/tests/test_data/std/jdk/internal/icu/impl/CharTrie.class b/tests/test_data/std/jdk/internal/icu/impl/CharTrie.class new file mode 100644 index 00000000..686df2b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/CharTrie.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/CharTrie.java b/tests/test_data/std/jdk/internal/icu/impl/CharTrie.java new file mode 100644 index 00000000..e320f670 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/CharTrie.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ****************************************************************************** + * Copyright (C) 1996-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ****************************************************************************** + */ + +package jdk.internal.icu.impl; + +import jdk.internal.icu.text.UTF16; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.IOException; + +/** + * Trie implementation which stores data in char, 16 bits. + * @author synwee + * @see com.ibm.icu.impl.Trie + * @since release 2.1, Jan 01 2002 + */ + + // note that i need to handle the block calculations later, since chartrie + // in icu4c uses the same index array. +public class CharTrie extends Trie +{ + // public constructors --------------------------------------------- + + /** + *

Creates a new Trie with the settings for the trie data.

+ *

Unserialize the 32-bit-aligned input stream and use the data for the + * trie.

+ * @param inputStream file input stream to a ICU data file, containing + * the trie + * @param dataManipulate object which provides methods to parse the char + * data + * @throws IOException thrown when data reading fails + * @draft 2.1 + */ + public CharTrie(InputStream inputStream, + DataManipulate dataManipulate) throws IOException + { + super(inputStream, dataManipulate); + + if (!isCharTrie()) { + throw new IllegalArgumentException( + "Data given does not belong to a char trie."); + } + } + + // public methods -------------------------------------------------- + + /** + * Gets the value associated with the codepoint. + * If no value is associated with the codepoint, a default value will be + * returned. + * @param ch codepoint + * @return offset to data + */ + public final char getCodePointValue(int ch) + { + int offset; + + // fastpath for U+0000..U+D7FF + if(0 <= ch && ch < UTF16.LEAD_SURROGATE_MIN_VALUE) { + // copy of getRawOffset() + offset = (m_index_[ch >> INDEX_STAGE_1_SHIFT_] << INDEX_STAGE_2_SHIFT_) + + (ch & INDEX_STAGE_3_MASK_); + return m_data_[offset]; + } + + // handle U+D800..U+10FFFF + offset = getCodePointOffset(ch); + + // return -1 if there is an error, in this case we return the default + // value: m_initialValue_ + return (offset >= 0) ? m_data_[offset] : m_initialValue_; + } + + /** + * Gets the value to the data which this lead surrogate character points + * to. + * Returned data may contain folding offset information for the next + * trailing surrogate character. + * This method does not guarantee correct results for trail surrogates. + * @param ch lead surrogate character + * @return data value + */ + public final char getLeadValue(char ch) + { + return m_data_[getLeadOffset(ch)]; + } + + // protected methods ----------------------------------------------- + + /** + *

Parses the input stream and stores its trie content into a index and + * data array

+ * @param inputStream data input stream containing trie data + * @exception IOException thrown when data reading fails + */ + protected final void unserialize(InputStream inputStream) + throws IOException + { + DataInputStream input = new DataInputStream(inputStream); + int indexDataLength = m_dataOffset_ + m_dataLength_; + m_index_ = new char[indexDataLength]; + for (int i = 0; i < indexDataLength; i ++) { + m_index_[i] = input.readChar(); + } + m_data_ = m_index_; + m_initialValue_ = m_data_[m_dataOffset_]; + } + + /** + * Gets the offset to the data which the surrogate pair points to. + * @param lead lead surrogate + * @param trail trailing surrogate + * @return offset to data + * @draft 2.1 + */ + protected final int getSurrogateOffset(char lead, char trail) + { + if (m_dataManipulate_ == null) { + throw new NullPointerException( + "The field DataManipulate in this Trie is null"); + } + + // get fold position for the next trail surrogate + int offset = m_dataManipulate_.getFoldingOffset(getLeadValue(lead)); + + // get the real data from the folded lead/trail units + if (offset > 0) { + return getRawOffset(offset, (char)(trail & SURROGATE_MASK_)); + } + + // return -1 if there is an error, in this case we return the default + // value: m_initialValue_ + return -1; + } + + // private data members -------------------------------------------- + + /** + * Default value + */ + private char m_initialValue_; + /** + * Array of char data + */ + private char m_data_[]; +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/CharacterIteratorWrapper.class b/tests/test_data/std/jdk/internal/icu/impl/CharacterIteratorWrapper.class new file mode 100644 index 00000000..d9767767 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/CharacterIteratorWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/CharacterIteratorWrapper.java b/tests/test_data/std/jdk/internal/icu/impl/CharacterIteratorWrapper.java new file mode 100644 index 00000000..79a6264d --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/CharacterIteratorWrapper.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * (C) Copyright IBM Corp. 1996-2005 - All Rights Reserved * + * * + * The original version of this source code and documentation is copyrighted * + * and owned by IBM, These materials are provided under terms of a License * + * Agreement between IBM and Sun. This technology is protected by multiple * + * US and International patents. This notice and attribution to IBM may not * + * to removed. * + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import java.text.CharacterIterator; + +import jdk.internal.icu.text.UCharacterIterator; + +/** + * This class is a wrapper around CharacterIterator and implements the + * UCharacterIterator protocol + * @author ram + */ + +public class CharacterIteratorWrapper extends UCharacterIterator { + + private CharacterIterator iterator; + + public CharacterIteratorWrapper(CharacterIterator iter){ + if(iter==null){ + throw new IllegalArgumentException(); + } + iterator = iter; + } + + /** + * @see UCharacterIterator#current() + */ + public int current() { + int c = iterator.current(); + if(c==CharacterIterator.DONE){ + return DONE; + } + return c; + } + + /** + * @see UCharacterIterator#getLength() + */ + public int getLength() { + return (iterator.getEndIndex() - iterator.getBeginIndex()); + } + + /** + * @see UCharacterIterator#getIndex() + */ + public int getIndex() { + return iterator.getIndex(); + } + + /** + * @see UCharacterIterator#next() + */ + public int next() { + int i = iterator.current(); + iterator.next(); + if(i==CharacterIterator.DONE){ + return DONE; + } + return i; + } + + /** + * @see UCharacterIterator#previous() + */ + public int previous() { + int i = iterator.previous(); + if(i==CharacterIterator.DONE){ + return DONE; + } + return i; + } + + /** + * @see UCharacterIterator#setIndex(int) + */ + public void setIndex(int index) { + iterator.setIndex(index); + } + + /** + * @see UCharacterIterator#getText(char[]) + */ + public int getText(char[] fillIn, int offset){ + int length =iterator.getEndIndex() - iterator.getBeginIndex(); + int currentIndex = iterator.getIndex(); + if(offset < 0 || offset + length > fillIn.length){ + throw new IndexOutOfBoundsException(Integer.toString(length)); + } + + for (char ch = iterator.first(); ch != CharacterIterator.DONE; ch = iterator.next()) { + fillIn[offset++] = ch; + } + iterator.setIndex(currentIndex); + + return length; + } + + /** + * Creates a clone of this iterator. Clones the underlying character iterator. + * @see UCharacterIterator#clone() + */ + public Object clone(){ + try { + CharacterIteratorWrapper result = (CharacterIteratorWrapper) super.clone(); + result.iterator = (CharacterIterator)this.iterator.clone(); + return result; + } catch (CloneNotSupportedException e) { + return null; // only invoked if bad underlying character iterator + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$1.class b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$1.class new file mode 100644 index 00000000..bb320a1c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$1.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$Authenticate.class b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$Authenticate.class new file mode 100644 index 00000000..ecf1fa11 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$Authenticate.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$IsAcceptable.class b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$IsAcceptable.class new file mode 100644 index 00000000..3d8ba26a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary$IsAcceptable.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/ICUBinary.class b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary.class new file mode 100644 index 00000000..8de7540d Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/ICUBinary.java b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary.java new file mode 100644 index 00000000..d8b48e7f --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/ICUBinary.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 1996-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import java.io.DataInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import jdk.internal.icu.util.VersionInfo; + +public final class ICUBinary { + + private static final class IsAcceptable implements Authenticate { + @Override + public boolean isDataVersionAcceptable(byte version[]) { + return version[0] == 1; + } + } + + // public inner interface ------------------------------------------------ + + /** + * Special interface for data authentication + */ + public static interface Authenticate + { + /** + * Method used in ICUBinary.readHeader() to provide data format + * authentication. + * @param version version of the current data + * @return true if dataformat is an acceptable version, false otherwise + */ + public boolean isDataVersionAcceptable(byte version[]); + } + + // public methods -------------------------------------------------------- + + /** + * Loads an ICU binary data file and returns it as a ByteBuffer. + * The buffer contents is normally read-only, but its position etc. can be modified. + * + * @param itemPath Relative ICU data item path, for example "root.res" or "coll/ucadata.icu". + * @return The data as a read-only ByteBuffer. + */ + public static ByteBuffer getRequiredData(String itemPath) { + final Class root = ICUBinary.class; + + try (@SuppressWarnings("removal") InputStream is = AccessController.doPrivileged(new PrivilegedAction() { + public InputStream run() { + return root.getResourceAsStream(itemPath); + } + })) { + + // is.available() may return 0, or 1, or the total number of bytes in the stream, + // or some other number. + // Do not try to use is.available() == 0 to find the end of the stream! + byte[] bytes; + int avail = is.available(); + if (avail > 32) { + // There are more bytes available than just the ICU data header length. + // With luck, it is the total number of bytes. + bytes = new byte[avail]; + } else { + bytes = new byte[128]; // empty .res files are even smaller + } + // Call is.read(...) until one returns a negative value. + int length = 0; + for(;;) { + if (length < bytes.length) { + int numRead = is.read(bytes, length, bytes.length - length); + if (numRead < 0) { + break; // end of stream + } + length += numRead; + } else { + // See if we are at the end of the stream before we grow the array. + int nextByte = is.read(); + if (nextByte < 0) { + break; + } + int capacity = 2 * bytes.length; + if (capacity < 128) { + capacity = 128; + } else if (capacity < 0x4000) { + capacity *= 2; // Grow faster until we reach 16kB. + } + bytes = Arrays.copyOf(bytes, capacity); + bytes[length++] = (byte) nextByte; + } + } + return ByteBuffer.wrap(bytes, 0, length); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Same as readHeader(), but returns a VersionInfo rather than a compact int. + */ + public static VersionInfo readHeaderAndDataVersion(ByteBuffer bytes, + int dataFormat, + Authenticate authenticate) + throws IOException { + return getVersionInfoFromCompactInt(readHeader(bytes, dataFormat, authenticate)); + } + + private static final byte BIG_ENDIAN_ = 1; + public static final byte[] readHeader(InputStream inputStream, + byte dataFormatIDExpected[], + Authenticate authenticate) + throws IOException + { + DataInputStream input = new DataInputStream(inputStream); + char headersize = input.readChar(); + int readcount = 2; + //reading the header format + byte magic1 = input.readByte(); + readcount ++; + byte magic2 = input.readByte(); + readcount ++; + if (magic1 != MAGIC1 || magic2 != MAGIC2) { + throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); + } + + input.readChar(); // reading size + readcount += 2; + input.readChar(); // reading reserved word + readcount += 2; + byte bigendian = input.readByte(); + readcount ++; + byte charset = input.readByte(); + readcount ++; + byte charsize = input.readByte(); + readcount ++; + input.readByte(); // reading reserved byte + readcount ++; + + byte dataFormatID[] = new byte[4]; + input.readFully(dataFormatID); + readcount += 4; + byte dataVersion[] = new byte[4]; + input.readFully(dataVersion); + readcount += 4; + byte unicodeVersion[] = new byte[4]; + input.readFully(unicodeVersion); + readcount += 4; + if (headersize < readcount) { + throw new IOException("Internal Error: Header size error"); + } + input.skipBytes(headersize - readcount); + + if (bigendian != BIG_ENDIAN_ || charset != CHAR_SET_ + || charsize != CHAR_SIZE_ + || !Arrays.equals(dataFormatIDExpected, dataFormatID) + || (authenticate != null + && !authenticate.isDataVersionAcceptable(dataVersion))) { + throw new IOException(HEADER_AUTHENTICATION_FAILED_); + } + return unicodeVersion; + } + + /** + * Reads an ICU data header, checks the data format, and returns the data version. + * + *

Assumes that the ByteBuffer position is 0 on input. + * The buffer byte order is set according to the data. + * The buffer position is advanced past the header (including UDataInfo and comment). + * + *

See C++ ucmndata.h and unicode/udata.h. + * + * @return dataVersion + * @throws IOException if this is not a valid ICU data item of the expected dataFormat + */ + public static int readHeader(ByteBuffer bytes, int dataFormat, Authenticate authenticate) + throws IOException { + assert bytes.position() == 0; + byte magic1 = bytes.get(2); + byte magic2 = bytes.get(3); + if (magic1 != MAGIC1 || magic2 != MAGIC2) { + throw new IOException(MAGIC_NUMBER_AUTHENTICATION_FAILED_); + } + + byte isBigEndian = bytes.get(8); + byte charsetFamily = bytes.get(9); + byte sizeofUChar = bytes.get(10); + if (isBigEndian < 0 || 1 < isBigEndian || + charsetFamily != CHAR_SET_ || sizeofUChar != CHAR_SIZE_) { + throw new IOException(HEADER_AUTHENTICATION_FAILED_); + } + bytes.order(isBigEndian != 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN); + + int headerSize = bytes.getChar(0); + int sizeofUDataInfo = bytes.getChar(4); + if (sizeofUDataInfo < 20 || headerSize < (sizeofUDataInfo + 4)) { + throw new IOException("Internal Error: Header size error"); + } + // TODO: Change Authenticate to take int major, int minor, int milli, int micro + // to avoid array allocation. + byte[] formatVersion = new byte[] { + bytes.get(16), bytes.get(17), bytes.get(18), bytes.get(19) + }; + if (bytes.get(12) != (byte)(dataFormat >> 24) || + bytes.get(13) != (byte)(dataFormat >> 16) || + bytes.get(14) != (byte)(dataFormat >> 8) || + bytes.get(15) != (byte)dataFormat || + (authenticate != null && !authenticate.isDataVersionAcceptable(formatVersion))) { + throw new IOException(HEADER_AUTHENTICATION_FAILED_ + + String.format("; data format %02x%02x%02x%02x, format version %d.%d.%d.%d", + bytes.get(12), bytes.get(13), bytes.get(14), bytes.get(15), + formatVersion[0] & 0xff, formatVersion[1] & 0xff, + formatVersion[2] & 0xff, formatVersion[3] & 0xff)); + } + + bytes.position(headerSize); + return // dataVersion + ((int)bytes.get(20) << 24) | + ((bytes.get(21) & 0xff) << 16) | + ((bytes.get(22) & 0xff) << 8) | + (bytes.get(23) & 0xff); + } + + public static void skipBytes(ByteBuffer bytes, int skipLength) { + if (skipLength > 0) { + bytes.position(bytes.position() + skipLength); + } + } + + public static byte[] getBytes(ByteBuffer bytes, int length, int additionalSkipLength) { + byte[] dest = new byte[length]; + bytes.get(dest); + if (additionalSkipLength > 0) { + skipBytes(bytes, additionalSkipLength); + } + return dest; + } + + public static String getString(ByteBuffer bytes, int length, int additionalSkipLength) { + CharSequence cs = bytes.asCharBuffer(); + String s = cs.subSequence(0, length).toString(); + skipBytes(bytes, length * 2 + additionalSkipLength); + return s; + } + + public static char[] getChars(ByteBuffer bytes, int length, int additionalSkipLength) { + char[] dest = new char[length]; + bytes.asCharBuffer().get(dest); + skipBytes(bytes, length * 2 + additionalSkipLength); + return dest; + } + + public static int[] getInts(ByteBuffer bytes, int length, int additionalSkipLength) { + int[] dest = new int[length]; + bytes.asIntBuffer().get(dest); + skipBytes(bytes, length * 4 + additionalSkipLength); + return dest; + } + + /** + * Returns a VersionInfo for the bytes in the compact version integer. + */ + public static VersionInfo getVersionInfoFromCompactInt(int version) { + return VersionInfo.getInstance( + version >>> 24, (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); + } + + // private variables ------------------------------------------------- + + /** + * Magic numbers to authenticate the data file + */ + private static final byte MAGIC1 = (byte)0xda; + private static final byte MAGIC2 = (byte)0x27; + + /** + * File format authentication values + */ + private static final byte CHAR_SET_ = 0; + private static final byte CHAR_SIZE_ = 2; + + /** + * Error messages + */ + private static final String MAGIC_NUMBER_AUTHENTICATION_FAILED_ = + "ICUBinary data file error: Magic number authentication failed"; + private static final String HEADER_AUTHENTICATION_FAILED_ = + "ICUBinary data file error: Header authentication failed"; +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$ComposeNormalizer2.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$ComposeNormalizer2.class new file mode 100644 index 00000000..4b0b575a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$ComposeNormalizer2.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$DecomposeNormalizer2.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$DecomposeNormalizer2.class new file mode 100644 index 00000000..6ebad8b6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$DecomposeNormalizer2.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NFCSingleton.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NFCSingleton.class new file mode 100644 index 00000000..809ab7c8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NFCSingleton.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NFKCSingleton.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NFKCSingleton.class new file mode 100644 index 00000000..b9d4b920 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NFKCSingleton.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NoopNormalizer2.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NoopNormalizer2.class new file mode 100644 index 00000000..7ada3471 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$NoopNormalizer2.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$Norm2AllModesSingleton.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$Norm2AllModesSingleton.class new file mode 100644 index 00000000..623afa8a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$Norm2AllModesSingleton.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$Normalizer2WithImpl.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$Normalizer2WithImpl.class new file mode 100644 index 00000000..a736a551 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes$Normalizer2WithImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes.class b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes.class new file mode 100644 index 00000000..42ddb2d3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes.java b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes.java new file mode 100644 index 00000000..7922bb46 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/Norm2AllModes.java @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 2009-2014, International Business Machines + * Corporation and others. All Rights Reserved. + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import java.io.IOException; + +import jdk.internal.icu.text.Normalizer2; +import jdk.internal.icu.util.VersionInfo; + +public final class Norm2AllModes { + // Public API dispatch via Normalizer2 subclasses -------------------------- *** + + // Normalizer2 implementation for the old UNORM_NONE. + public static final class NoopNormalizer2 extends Normalizer2 { + @Override + public StringBuilder normalize(CharSequence src, StringBuilder dest) { + if(dest!=src) { + dest.setLength(0); + return dest.append(src); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public Appendable normalize(CharSequence src, Appendable dest) { + if(dest!=src) { + try { + return dest.append(src); + } catch(IOException e) { + throw new InternalError(e.toString(), e); + } + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public StringBuilder normalizeSecondAndAppend(StringBuilder first, CharSequence second) { + if(first!=second) { + return first.append(second); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public StringBuilder append(StringBuilder first, CharSequence second) { + if(first!=second) { + return first.append(second); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public String getDecomposition(int c) { + return null; + } + + // No need to override the default getRawDecomposition(). + @Override + public boolean isNormalized(CharSequence s) { return true; } + + @Override + public int spanQuickCheckYes(CharSequence s) { return s.length(); } + + @Override + public boolean hasBoundaryBefore(int c) { return true; } + } + + // Intermediate class: + // Has NormalizerImpl and does boilerplate argument checking and setup. + public abstract static class Normalizer2WithImpl extends Normalizer2 { + public Normalizer2WithImpl(NormalizerImpl ni) { + impl=ni; + } + + // normalize + @Override + public StringBuilder normalize(CharSequence src, StringBuilder dest) { + if(dest==src) { + throw new IllegalArgumentException(); + } + dest.setLength(0); + normalize(src, new NormalizerImpl.ReorderingBuffer(impl, dest, src.length())); + return dest; + } + + @Override + public Appendable normalize(CharSequence src, Appendable dest) { + if(dest==src) { + throw new IllegalArgumentException(); + } + NormalizerImpl.ReorderingBuffer buffer= + new NormalizerImpl.ReorderingBuffer(impl, dest, src.length()); + normalize(src, buffer); + buffer.flush(); + return dest; + } + + protected abstract void normalize(CharSequence src, NormalizerImpl.ReorderingBuffer buffer); + + // normalize and append + @Override + public StringBuilder normalizeSecondAndAppend(StringBuilder first, CharSequence second) { + return normalizeSecondAndAppend(first, second, true); + } + + @Override + public StringBuilder append(StringBuilder first, CharSequence second) { + return normalizeSecondAndAppend(first, second, false); + } + + public StringBuilder normalizeSecondAndAppend( + StringBuilder first, CharSequence second, boolean doNormalize) { + if(first==second) { + throw new IllegalArgumentException(); + } + normalizeAndAppend( + second, doNormalize, + new NormalizerImpl.ReorderingBuffer(impl, first, first.length()+second.length())); + return first; + } + + protected abstract void normalizeAndAppend( + CharSequence src, boolean doNormalize, NormalizerImpl.ReorderingBuffer buffer); + + @Override + public String getDecomposition(int c) { + return impl.getDecomposition(c); + } + + @Override + public int getCombiningClass(int c) { + return impl.getCC(impl.getNorm16(c)); + } + + // quick checks + @Override + public boolean isNormalized(CharSequence s) { + return s.length()==spanQuickCheckYes(s); + } + + public final NormalizerImpl impl; + } + + public static final class DecomposeNormalizer2 extends Normalizer2WithImpl { + public DecomposeNormalizer2(NormalizerImpl ni) { + super(ni); + } + + @Override + protected void normalize(CharSequence src, NormalizerImpl.ReorderingBuffer buffer) { + impl.decompose(src, 0, src.length(), buffer); + } + + @Override + protected void normalizeAndAppend( + CharSequence src, boolean doNormalize, NormalizerImpl.ReorderingBuffer buffer) { + impl.decomposeAndAppend(src, doNormalize, buffer); + } + + @Override + public int spanQuickCheckYes(CharSequence s) { + return impl.decompose(s, 0, s.length(), null); + } + + @Override + public boolean hasBoundaryBefore(int c) { return impl.hasDecompBoundaryBefore(c); } + } + + public static final class ComposeNormalizer2 extends Normalizer2WithImpl { + public ComposeNormalizer2(NormalizerImpl ni, boolean fcc) { + super(ni); + onlyContiguous=fcc; + } + + @Override + protected void normalize(CharSequence src, NormalizerImpl.ReorderingBuffer buffer) { + impl.compose(src, 0, src.length(), onlyContiguous, true, buffer); + } + + @Override + protected void normalizeAndAppend( + CharSequence src, boolean doNormalize, NormalizerImpl.ReorderingBuffer buffer) { + impl.composeAndAppend(src, doNormalize, onlyContiguous, buffer); + } + + @Override + public boolean isNormalized(CharSequence s) { + // 5: small destCapacity for substring normalization + return impl.compose(s, 0, s.length(), + onlyContiguous, false, + new NormalizerImpl.ReorderingBuffer(impl, new StringBuilder(), 5)); + } + + @Override + public int spanQuickCheckYes(CharSequence s) { + return impl.composeQuickCheck(s, 0, s.length(), onlyContiguous, true)>>>1; + } + + @Override + public boolean hasBoundaryBefore(int c) { return impl.hasCompBoundaryBefore(c); } + + private final boolean onlyContiguous; + } + + // instance cache ---------------------------------------------------------- *** + + private Norm2AllModes(NormalizerImpl ni) { + impl=ni; + comp=new ComposeNormalizer2(ni, false); + decomp=new DecomposeNormalizer2(ni); + } + + public final NormalizerImpl impl; + public final ComposeNormalizer2 comp; + public final DecomposeNormalizer2 decomp; + + private static Norm2AllModes getInstanceFromSingleton(Norm2AllModesSingleton singleton) { + if(singleton.exception!=null) { + throw singleton.exception; + } + return singleton.allModes; + } + + public static Norm2AllModes getNFCInstance() { + return getInstanceFromSingleton(NFCSingleton.INSTANCE); + } + + public static Norm2AllModes getNFKCInstance() { + return getInstanceFromSingleton(NFKCSingleton.INSTANCE); + } + + public static final NoopNormalizer2 NOOP_NORMALIZER2=new NoopNormalizer2(); + + private static final class Norm2AllModesSingleton { + private Norm2AllModesSingleton(String name) { + try { + @SuppressWarnings("deprecation") + String DATA_FILE_NAME = "/jdk/internal/icu/impl/data/icudt" + + VersionInfo.ICU_DATA_VERSION_PATH + "/" + name + ".nrm"; + NormalizerImpl impl=new NormalizerImpl().load(DATA_FILE_NAME); + allModes=new Norm2AllModes(impl); + } catch (RuntimeException e) { + exception=e; + } + } + + private Norm2AllModes allModes; + private RuntimeException exception; + } + + private static final class NFCSingleton { + private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfc"); + } + + private static final class NFKCSingleton { + private static final Norm2AllModesSingleton INSTANCE=new Norm2AllModesSingleton("nfkc"); + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$Hangul.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$Hangul.class new file mode 100644 index 00000000..aa47418a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$Hangul.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$IsAcceptable.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$IsAcceptable.class new file mode 100644 index 00000000..89292e2e Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$IsAcceptable.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$NextCCArgs.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$NextCCArgs.class new file mode 100644 index 00000000..8fc3c98c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$NextCCArgs.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$PrevArgs.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$PrevArgs.class new file mode 100644 index 00000000..0b863c8a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$PrevArgs.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$ReorderingBuffer.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$ReorderingBuffer.class new file mode 100644 index 00000000..8b221882 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$ReorderingBuffer.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$UTF16Plus.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$UTF16Plus.class new file mode 100644 index 00000000..91c98516 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl$UTF16Plus.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl.class b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl.class new file mode 100644 index 00000000..f85b30fb Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl.java b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl.java new file mode 100644 index 00000000..291653f9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/NormalizerImpl.java @@ -0,0 +1,2193 @@ +/* + * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 2009-2015, International Business Machines + * Corporation and others. All Rights Reserved. + ******************************************************************************* + */ +package jdk.internal.icu.impl; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import jdk.internal.icu.lang.UCharacter; +import jdk.internal.icu.text.Normalizer2; +import jdk.internal.icu.text.UTF16; +import jdk.internal.icu.util.CodePointTrie; +import jdk.internal.icu.util.VersionInfo; + +// Original filename in ICU4J: Normalizer2Impl.java +public final class NormalizerImpl { + public static final class Hangul { + /* Korean Hangul and Jamo constants */ + public static final int JAMO_L_BASE=0x1100; /* "lead" jamo */ + public static final int JAMO_V_BASE=0x1161; /* "vowel" jamo */ + public static final int JAMO_T_BASE=0x11a7; /* "trail" jamo */ + + public static final int HANGUL_BASE=0xac00; + public static final int HANGUL_END=0xd7a3; + + public static final int JAMO_L_COUNT=19; + public static final int JAMO_V_COUNT=21; + public static final int JAMO_T_COUNT=28; + + public static final int HANGUL_COUNT=JAMO_L_COUNT*JAMO_V_COUNT*JAMO_T_COUNT; + public static final int HANGUL_LIMIT=HANGUL_BASE+HANGUL_COUNT; + + public static boolean isHangul(int c) { + return HANGUL_BASE<=c && c + * If dest is a StringBuilder, then the buffer writes directly to it. + * Otherwise, the buffer maintains a StringBuilder for intermediate text segments + * until no further changes are necessary and whole segments are appended. + * append() methods that take combining-class values always write to the StringBuilder. + * Other append() methods flush and append to the Appendable. + */ + public static final class ReorderingBuffer implements Appendable { + public ReorderingBuffer(NormalizerImpl ni, Appendable dest, int destCapacity) { + impl=ni; + app=dest; + if (app instanceof StringBuilder) { + appIsStringBuilder=true; + str=(StringBuilder)dest; + // In Java, the constructor subsumes public void init(int destCapacity) + str.ensureCapacity(destCapacity); + reorderStart=0; + if(str.length()==0) { + lastCC=0; + } else { + setIterator(); + lastCC=previousCC(); + // Set reorderStart after the last code point with cc<=1 if there is one. + if(lastCC>1) { + while(previousCC()>1) {} + } + reorderStart=codePointLimit; + } + } else { + appIsStringBuilder=false; + str=new StringBuilder(); + reorderStart=0; + lastCC=0; + } + } + + public boolean isEmpty() { return str.length()==0; } + public int length() { return str.length(); } + public int getLastCC() { return lastCC; } + + public StringBuilder getStringBuilder() { return str; } + + public boolean equals(CharSequence s, int start, int limit) { + return UTF16Plus.equal(str, 0, str.length(), s, start, limit); + } + + public void append(int c, int cc) { + if(lastCC<=cc || cc==0) { + str.appendCodePoint(c); + lastCC=cc; + if(cc<=1) { + reorderStart=str.length(); + } + } else { + insert(c, cc); + } + } + public void append(CharSequence s, int start, int limit, boolean isNFD, + int leadCC, int trailCC) { + if(start==limit) { + return; + } + if(lastCC<=leadCC || leadCC==0) { + if(trailCC<=1) { + reorderStart=str.length()+(limit-start); + } else if(leadCC<=1) { + reorderStart=str.length()+1; // Ok if not a code point boundary. + } + str.append(s, start, limit); + lastCC=trailCC; + } else { + int c=Character.codePointAt(s, start); + start+=Character.charCount(c); + insert(c, leadCC); // insert first code point + while(startcc;) {} + // insert c at codePointLimit, after the character with prevCC<=cc + if(c<=0xffff) { + str.insert(codePointLimit, (char)c); + if(cc<=1) { + reorderStart=codePointLimit+1; + } + } else { + str.insert(codePointLimit, Character.toChars(c)); + if(cc<=1) { + reorderStart=codePointLimit+2; + } + } + } + + private final NormalizerImpl impl; + private final Appendable app; + private final StringBuilder str; + private final boolean appIsStringBuilder; + private int reorderStart; + private int lastCC; + + // private backward iterator + private void setIterator() { codePointStart=str.length(); } + private void skipPrevious() { // Requires 0=codePointStart) { + return 0; + } + int c=str.codePointBefore(codePointStart); + codePointStart-=Character.charCount(c); + return impl.getCCFromYesOrMaybeCP(c); + } + private int codePointStart, codePointLimit; + } + + // TODO: Propose as public API on the UTF16 class. + // TODO: Propose widening UTF16 methods that take char to take int. + // TODO: Propose widening UTF16 methods that take String to take CharSequence. + public static final class UTF16Plus { + /** + * Is this code point a lead surrogate (U+d800..U+dbff)? + * @param c code unit or code point + * @return true or false + */ + public static boolean isLeadSurrogate(int c) { return (c & 0xfffffc00) == 0xd800; } + /** + * Assuming c is a surrogate code point (UTF16.isSurrogate(c)), + * is it a lead surrogate? + * @param c code unit or code point + * @return true or false + */ + public static boolean isSurrogateLead(int c) { return (c&0x400)==0; } + + /** + * Compares two CharSequence subsequences for binary equality. + * @param s1 first sequence + * @param start1 start offset in first sequence + * @param limit1 limit offset in first sequence + * @param s2 second sequence + * @param start2 start offset in second sequence + * @param limit2 limit offset in second sequence + * @return true if s1.subSequence(start1, limit1) contains the same text + * as s2.subSequence(start2, limit2) + */ + public static boolean equal(CharSequence s1, int start1, int limit1, + CharSequence s2, int start2, int limit2) { + if((limit1-start1)!=(limit2-start2)) { + return false; + } + if(s1==s2 && start1==start2) { + return true; + } + while(start1>DELTA_SHIFT)-MAX_DELTA-1; + + // Read the normTrie. + int offset=inIndexes[IX_NORM_TRIE_OFFSET]; + int nextOffset=inIndexes[IX_EXTRA_DATA_OFFSET]; + int triePosition = bytes.position(); + normTrie = CodePointTrie.Fast16.fromBinary(bytes); + int trieLength = bytes.position() - triePosition; + if(trieLength>(nextOffset-offset)) { + throw new InternalError("Normalizer2 data: not enough bytes for normTrie"); + } + ICUBinary.skipBytes(bytes, (nextOffset-offset)-trieLength); // skip padding after trie bytes + + // Read the composition and mapping data. + offset=nextOffset; + nextOffset=inIndexes[IX_SMALL_FCD_OFFSET]; + int numChars=(nextOffset-offset)/2; + if(numChars!=0) { + maybeYesCompositions=ICUBinary.getString(bytes, numChars, 0); + extraData=maybeYesCompositions.substring((MIN_NORMAL_MAYBE_YES-minMaybeYes)>>OFFSET_SHIFT); + } + + // smallFCD: new in formatVersion 2 + offset=nextOffset; + smallFCD=new byte[0x100]; + bytes.get(smallFCD); + + return this; + } catch(IOException e) { + throw new InternalError(e); + } + } + public NormalizerImpl load(String name) { + return load(ICUBinary.getRequiredData(name)); + } + + // The trie stores values for lead surrogate code *units*. + // Surrogate code *points* are inert. + public int getNorm16(int c) { + return UTF16Plus.isLeadSurrogate(c) ? INERT : normTrie.get(c); + } + public int getRawNorm16(int c) { return normTrie.get(c); } + public boolean isAlgorithmicNoNo(int norm16) { return limitNoNo<=norm16 && norm16=MIN_NORMAL_MAYBE_YES) { + return getCCFromNormalYesOrMaybe(norm16); + } + if(norm16> OFFSET_SHIFT) & 0xff; + } + public static int getCCFromYesOrMaybe(int norm16) { + return norm16>=MIN_NORMAL_MAYBE_YES ? getCCFromNormalYesOrMaybe(norm16) : 0; + } + public int getCCFromYesOrMaybeCP(int c) { + if (c < minCompNoMaybeCP) { return 0; } + return getCCFromYesOrMaybe(getNorm16(c)); + } + + /** + * Returns the FCD data for code point c. + * @param c A Unicode code point. + * @return The lccc(c) in bits 15..8 and tccc(c) in bits 7..0. + */ + public int getFCD16(int c) { + if(c>8]; + if(bits==0) { return false; } + return ((bits>>((lead>>5)&7))&1)!=0; + } + + /** Gets the FCD value from the regular normalization data. */ + public int getFCD16FromNormData(int c) { + int norm16=getNorm16(c); + if (norm16 >= limitNoNo) { + if(norm16>=MIN_NORMAL_MAYBE_YES) { + // combining mark + norm16=getCCFromNormalYesOrMaybe(norm16); + return norm16|(norm16<<8); + } else if(norm16>=minMaybeYes) { + return 0; + } else { // isDecompNoAlgorithmic(norm16) + int deltaTrailCC = norm16 & DELTA_TCCC_MASK; + if (deltaTrailCC <= DELTA_TCCC_1) { + return deltaTrailCC >> OFFSET_SHIFT; + } + // Maps to an isCompYesAndZeroCC. + c=mapAlgorithmic(c, norm16); + norm16=getRawNorm16(c); + } + } + if(norm16<=minYesNo || isHangulLVT(norm16)) { + // no decomposition or Hangul syllable, all zeros + return 0; + } + // c decomposes, get everything from the variable-length extra data + int mapping=norm16>>OFFSET_SHIFT; + int firstUnit=extraData.charAt(mapping); + int fcd16=firstUnit>>8; // tccc + if((firstUnit&MAPPING_HAS_CCC_LCCC_WORD)!=0) { + fcd16|=extraData.charAt(mapping-1)&0xff00; // lccc + } + return fcd16; + } + + /** + * Gets the decomposition for one code point. + * @param c code point + * @return c's decomposition, if it has one; returns null if it does not have a decomposition + */ + public String getDecomposition(int c) { + int norm16; + if(c>OFFSET_SHIFT; + int length=extraData.charAt(mapping++)&MAPPING_LENGTH_MASK; + return extraData.substring(mapping, mapping+length); + } + + // Fixed norm16 values. + public static final int MIN_YES_YES_WITH_CC=0xfe02; + public static final int JAMO_VT=0xfe00; + public static final int MIN_NORMAL_MAYBE_YES=0xfc00; + public static final int JAMO_L=2; // offset=1 hasCompBoundaryAfter=false + public static final int INERT=1; // offset=0 hasCompBoundaryAfter=true + + // norm16 bit 0 is comp-boundary-after. + public static final int HAS_COMP_BOUNDARY_AFTER=1; + public static final int OFFSET_SHIFT=1; + + // For algorithmic one-way mappings, norm16 bits 2..1 indicate the + // tccc (0, 1, >1) for quick FCC boundary-after tests. + public static final int DELTA_TCCC_0=0; + public static final int DELTA_TCCC_1=2; + public static final int DELTA_TCCC_GT_1=4; + public static final int DELTA_TCCC_MASK=6; + public static final int DELTA_SHIFT=3; + + public static final int MAX_DELTA=0x40; + + // Byte offsets from the start of the data, after the generic header. + public static final int IX_NORM_TRIE_OFFSET=0; + public static final int IX_EXTRA_DATA_OFFSET=1; + public static final int IX_SMALL_FCD_OFFSET=2; + public static final int IX_RESERVED3_OFFSET=3; + public static final int IX_TOTAL_SIZE=7; + public static final int MIN_CCC_LCCC_CP=0x300; + // Code point thresholds for quick check codes. + public static final int IX_MIN_DECOMP_NO_CP=8; + public static final int IX_MIN_COMP_NO_MAYBE_CP=9; + + // Norm16 value thresholds for quick check combinations and types of extra data. + + /** Mappings & compositions in [minYesNo..minYesNoMappingsOnly[. */ + public static final int IX_MIN_YES_NO=10; + /** Mappings are comp-normalized. */ + public static final int IX_MIN_NO_NO=11; + public static final int IX_LIMIT_NO_NO=12; + public static final int IX_MIN_MAYBE_YES=13; + + /** Mappings only in [minYesNoMappingsOnly..minNoNo[. */ + public static final int IX_MIN_YES_NO_MAPPINGS_ONLY=14; + /** Mappings are not comp-normalized but have a comp boundary before. */ + public static final int IX_MIN_NO_NO_COMP_BOUNDARY_BEFORE=15; + /** Mappings do not have a comp boundary before. */ + public static final int IX_MIN_NO_NO_COMP_NO_MAYBE_CC=16; + /** Mappings to the empty string. */ + public static final int IX_MIN_NO_NO_EMPTY=17; + + public static final int IX_MIN_LCCC_CP=18; + public static final int IX_COUNT=20; + + public static final int MAPPING_HAS_CCC_LCCC_WORD=0x80; + public static final int MAPPING_HAS_RAW_MAPPING=0x40; + // unused bit 0x20; + public static final int MAPPING_LENGTH_MASK=0x1f; + + public static final int COMP_1_LAST_TUPLE=0x8000; + public static final int COMP_1_TRIPLE=1; + public static final int COMP_1_TRAIL_LIMIT=0x3400; + public static final int COMP_1_TRAIL_MASK=0x7ffe; + public static final int COMP_1_TRAIL_SHIFT=9; // 10-1 for the "triple" bit + public static final int COMP_2_TRAIL_SHIFT=6; + public static final int COMP_2_TRAIL_MASK=0xffc0; + + // higher-level functionality ------------------------------------------ *** + + /** + * Decomposes s[src, limit[ and writes the result to dest. + * limit can be NULL if src is NUL-terminated. + * destLengthEstimate is the initial dest buffer capacity and can be -1. + */ + public void decompose(CharSequence s, int src, int limit, StringBuilder dest, + int destLengthEstimate) { + if(destLengthEstimate<0) { + destLengthEstimate=limit-src; + } + dest.setLength(0); + ReorderingBuffer buffer=new ReorderingBuffer(this, dest, destLengthEstimate); + decompose(s, src, limit, buffer); + } + + // Dual functionality: + // buffer!=NULL: normalize + // buffer==NULL: isNormalized/quickCheck/spanQuickCheckYes + public int decompose(CharSequence s, int src, int limit, + ReorderingBuffer buffer) { + int minNoCP=minDecompNoCP; + + int prevSrc; + int c=0; + int norm16=0; + + // only for quick check + int prevBoundary=src; + int prevCC=0; + + for(;;) { + // count code units below the minimum or with irrelevant data for the quick check + for(prevSrc=src; src!=limit;) { + if( (c=s.charAt(src))=limit) { + break; + } + c=Character.codePointAt(s, src); + cc=getCC(getNorm16(c)); + }; + buffer.append(s, 0, src, false, firstCC, prevCC); + buffer.append(s, src, limit); + } + + // Very similar to composeQuickCheck(): Make the same changes in both places if relevant. + // doCompose: normalize + // !doCompose: isNormalized (buffer must be empty and initialized) + public boolean compose(CharSequence s, int src, int limit, + boolean onlyContiguous, + boolean doCompose, + ReorderingBuffer buffer) { + int prevBoundary=src; + int minNoMaybeCP=minCompNoMaybeCP; + + for (;;) { + // Fast path: Scan over a sequence of characters below the minimum "no or maybe" code point, + // or with (compYes && ccc==0) properties. + int prevSrc; + int c = 0; + int norm16 = 0; + for (;;) { + if (src == limit) { + if (prevBoundary != limit && doCompose) { + buffer.append(s, prevBoundary, limit); + } + return true; + } + if( (c=s.charAt(src))=minNoNo. + // The current character is either a "noNo" (has a mapping) + // or a "maybeYes" (combines backward) + // or a "yesYes" with ccc!=0. + // It is not a Hangul syllable or Jamo L because those have "yes" properties. + + // Medium-fast path: Handle cases that do not require full decomposition and recomposition. + if (!isMaybeOrNonZeroCC(norm16)) { // minNoNo <= norm16 < minMaybeYes + if (!doCompose) { + return false; + } + // Fast path for mapping a character that is immediately surrounded by boundaries. + // In this case, we need not decompose around the current character. + if (isDecompNoAlgorithmic(norm16)) { + // Maps to a single isCompYesAndZeroCC character + // which also implies hasCompBoundaryBefore. + if (norm16HasCompBoundaryAfter(norm16, onlyContiguous) || + hasCompBoundaryBefore(s, src, limit)) { + if (prevBoundary != prevSrc) { + buffer.append(s, prevBoundary, prevSrc); + } + buffer.append(mapAlgorithmic(c, norm16), 0); + prevBoundary = src; + continue; + } + } else if (norm16 < minNoNoCompBoundaryBefore) { + // The mapping is comp-normalized which also implies hasCompBoundaryBefore. + if (norm16HasCompBoundaryAfter(norm16, onlyContiguous) || + hasCompBoundaryBefore(s, src, limit)) { + if (prevBoundary != prevSrc) { + buffer.append(s, prevBoundary, prevSrc); + } + int mapping = norm16 >> OFFSET_SHIFT; + int length = extraData.charAt(mapping++) & MAPPING_LENGTH_MASK; + buffer.append(extraData, mapping, mapping + length); + prevBoundary = src; + continue; + } + } else if (norm16 >= minNoNoEmpty) { + // The current character maps to nothing. + // Simply omit it from the output if there is a boundary before _or_ after it. + // The character itself implies no boundaries. + if (hasCompBoundaryBefore(s, src, limit) || + hasCompBoundaryAfter(s, prevBoundary, prevSrc, onlyContiguous)) { + if (prevBoundary != prevSrc) { + buffer.append(s, prevBoundary, prevSrc); + } + prevBoundary = src; + continue; + } + } + // Other "noNo" type, or need to examine more text around this character: + // Fall through to the slow path. + } else if (isJamoVT(norm16) && prevBoundary != prevSrc) { + char prev=s.charAt(prevSrc-1); + if(c= 0) { + int syllable = Hangul.HANGUL_BASE + + (l*Hangul.JAMO_V_COUNT + (c-Hangul.JAMO_V_BASE)) * + Hangul.JAMO_T_COUNT + t; + --prevSrc; // Replace the Jamo L as well. + if (prevBoundary != prevSrc) { + buffer.append(s, prevBoundary, prevSrc); + } + buffer.append((char)syllable); + prevBoundary = src; + continue; + } + // If we see L+V+x where x!=T then we drop to the slow path, + // decompose and recompose. + // This is to deal with NFKC finding normal L and V but a + // compatibility variant of a T. + // We need to either fully compose that combination here + // (which would complicate the code and may not work with strange custom data) + // or use the slow path. + } + } else if (Hangul.isHangulLV(prev)) { + // The current character is a Jamo Trailing consonant, + // compose with previous Hangul LV that does not contain a Jamo T. + if (!doCompose) { + return false; + } + int syllable = prev + c - Hangul.JAMO_T_BASE; + --prevSrc; // Replace the Hangul LV as well. + if (prevBoundary != prevSrc) { + buffer.append(s, prevBoundary, prevSrc); + } + buffer.append((char)syllable); + prevBoundary = src; + continue; + } + // No matching context, or may need to decompose surrounding text first: + // Fall through to the slow path. + } else if (norm16 > JAMO_VT) { // norm16 >= MIN_YES_YES_WITH_CC + // One or more combining marks that do not combine-back: + // Check for canonical order, copy unchanged if ok and + // if followed by a character with a boundary-before. + int cc = getCCFromNormalYesOrMaybe(norm16); // cc!=0 + if (onlyContiguous /* FCC */ && getPreviousTrailCC(s, prevBoundary, prevSrc) > cc) { + // Fails FCD test, need to decompose and contiguously recompose. + if (!doCompose) { + return false; + } + } else { + // If !onlyContiguous (not FCC), then we ignore the tccc of + // the previous character which passed the quick check "yes && ccc==0" test. + int n16; + for (;;) { + if (src == limit) { + if (doCompose) { + buffer.append(s, prevBoundary, limit); + } + return true; + } + int prevCC = cc; + c = Character.codePointAt(s, src); + n16 = normTrie.get(c); + if (n16 >= MIN_YES_YES_WITH_CC) { + cc = getCCFromNormalYesOrMaybe(n16); + if (prevCC > cc) { + if (!doCompose) { + return false; + } + break; + } + } else { + break; + } + src += Character.charCount(c); + } + // p is after the last in-order combining mark. + // If there is a boundary here, then we continue with no change. + if (norm16HasCompBoundaryBefore(n16)) { + if (isCompYesAndZeroCC(n16)) { + src += Character.charCount(c); + } + continue; + } + // Use the slow path. There is no boundary in [prevSrc, src[. + } + } + + // Slow path: Find the nearest boundaries around the current character, + // decompose and recompose. + if (prevBoundary != prevSrc && !norm16HasCompBoundaryBefore(norm16)) { + c = Character.codePointBefore(s, prevSrc); + norm16 = normTrie.get(c); + if (!norm16HasCompBoundaryAfter(norm16, onlyContiguous)) { + prevSrc -= Character.charCount(c); + } + } + if (doCompose && prevBoundary != prevSrc) { + buffer.append(s, prevBoundary, prevSrc); + } + int recomposeStartIndex=buffer.length(); + // We know there is not a boundary here. + decomposeShort(s, prevSrc, src, false /* !stopAtCompBoundary */, onlyContiguous, + buffer); + // Decompose until the next boundary. + src = decomposeShort(s, src, limit, true /* stopAtCompBoundary */, onlyContiguous, + buffer); + recompose(buffer, recomposeStartIndex, onlyContiguous); + if(!doCompose) { + if(!buffer.equals(s, prevSrc, src)) { + return false; + } + buffer.remove(); + } + prevBoundary=src; + } + } + + /** + * Very similar to compose(): Make the same changes in both places if relevant. + * doSpan: spanQuickCheckYes (ignore bit 0 of the return value) + * !doSpan: quickCheck + * @return bits 31..1: spanQuickCheckYes (==s.length() if "yes") and + * bit 0: set if "maybe"; otherwise, if the span length<s.length() + * then the quick check result is "no" + */ + public int composeQuickCheck(CharSequence s, int src, int limit, + boolean onlyContiguous, boolean doSpan) { + int qcResult=0; + int prevBoundary=src; + int minNoMaybeCP=minCompNoMaybeCP; + + for(;;) { + // Fast path: Scan over a sequence of characters below the minimum "no or maybe" code point, + // or with (compYes && ccc==0) properties. + int prevSrc; + int c = 0; + int norm16 = 0; + for (;;) { + if(src==limit) { + return (src<<1)|qcResult; // "yes" or "maybe" + } + if( (c=s.charAt(src))=minNoNo. + // The current character is either a "noNo" (has a mapping) + // or a "maybeYes" (combines backward) + // or a "yesYes" with ccc!=0. + // It is not a Hangul syllable or Jamo L because those have "yes" properties. + + int prevNorm16 = INERT; + if (prevBoundary != prevSrc) { + prevBoundary = prevSrc; + if (!norm16HasCompBoundaryBefore(norm16)) { + c = Character.codePointBefore(s, prevSrc); + int n16 = getNorm16(c); + if (!norm16HasCompBoundaryAfter(n16, onlyContiguous)) { + prevBoundary -= Character.charCount(c); + prevNorm16 = n16; + } + } + } + + if(isMaybeOrNonZeroCC(norm16)) { + int cc=getCCFromYesOrMaybe(norm16); + if (onlyContiguous /* FCC */ && cc != 0 && + getTrailCCFromCompYesAndZeroCC(prevNorm16) > cc) { + // The [prevBoundary..prevSrc[ character + // passed the quick check "yes && ccc==0" test + // but is out of canonical order with the current combining mark. + } else { + // If !onlyContiguous (not FCC), then we ignore the tccc of + // the previous character which passed the quick check "yes && ccc==0" test. + for (;;) { + if (norm16 < MIN_YES_YES_WITH_CC) { + if (!doSpan) { + qcResult = 1; + } else { + return prevBoundary << 1; // spanYes does not care to know it's "maybe" + } + } + if (src == limit) { + return (src<<1) | qcResult; // "yes" or "maybe" + } + int prevCC = cc; + c = Character.codePointAt(s, src); + norm16 = getNorm16(c); + if (isMaybeOrNonZeroCC(norm16)) { + cc = getCCFromYesOrMaybe(norm16); + if (!(prevCC <= cc || cc == 0)) { + break; + } + } else { + break; + } + src += Character.charCount(c); + } + // src is after the last in-order combining mark. + if (isCompYesAndZeroCC(norm16)) { + prevBoundary = src; + src += Character.charCount(c); + continue; + } + } + } + return prevBoundary<<1; // "no" + } + } + public void composeAndAppend(CharSequence s, + boolean doCompose, + boolean onlyContiguous, + ReorderingBuffer buffer) { + int src=0, limit=s.length(); + if(!buffer.isEmpty()) { + int firstStarterInSrc=findNextCompBoundary(s, 0, limit, onlyContiguous); + if(0!=firstStarterInSrc) { + int lastStarterInDest=findPreviousCompBoundary(buffer.getStringBuilder(), + buffer.length(), onlyContiguous); + StringBuilder middle=new StringBuilder((buffer.length()-lastStarterInDest)+ + firstStarterInSrc+16); + middle.append(buffer.getStringBuilder(), lastStarterInDest, buffer.length()); + buffer.removeSuffix(buffer.length()-lastStarterInDest); + middle.append(s, 0, firstStarterInSrc); + compose(middle, 0, middle.length(), onlyContiguous, true, buffer); + src=firstStarterInSrc; + } + } + if(doCompose) { + compose(s, src, limit, onlyContiguous, true, buffer); + } else { + buffer.append(s, src, limit); + } + } + // Dual functionality: + // buffer!=NULL: normalize + // buffer==NULL: isNormalized/quickCheck/spanQuickCheckYes + public int makeFCD(CharSequence s, int src, int limit, ReorderingBuffer buffer) { + // Note: In this function we use buffer->appendZeroCC() because we track + // the lead and trail combining classes here, rather than leaving it to + // the ReorderingBuffer. + // The exception is the call to decomposeShort() which uses the buffer + // in the normal way. + + // Tracks the last FCD-safe boundary, before lccc=0 or after properly-ordered tccc<=1. + // Similar to the prevBoundary in the compose() implementation. + int prevBoundary=src; + int prevSrc; + int c=0; + int prevFCD16=0; + int fcd16=0; + + for(;;) { + // count code units with lccc==0 + for(prevSrc=src; src!=limit;) { + if((c=s.charAt(src))1) { + --prevBoundary; + } + } + } else { + int p=src-1; + if( Character.isLowSurrogate(s.charAt(p)) && prevSrc

1) { + prevBoundary=p; + } + } + if(buffer!=null) { + // The last lccc==0 character is excluded from the + // flush-and-append call in case it needs to be modified. + buffer.flushAndAppendZeroCC(s, prevSrc, prevBoundary); + buffer.append(s, prevBoundary, src); + } + // The start of the current character (c). + prevSrc=src; + } else if(src==limit) { + break; + } + + src+=Character.charCount(c); + // The current character (c) at [prevSrc..src[ has a non-zero lead combining class. + // Check for proper order, and decompose locally if necessary. + if((prevFCD16&0xff)<=(fcd16>>8)) { + // proper order: prev tccc <= current lccc + if((fcd16&0xff)<=1) { + prevBoundary=src; + } + if(buffer!=null) { + buffer.appendZeroCC(c); + } + prevFCD16=fcd16; + continue; + } else if(buffer==null) { + return prevBoundary; // quick check "no" + } else { + /* + * Back out the part of the source that we copied or appended + * already but is now going to be decomposed. + * prevSrc is set to after what was copied/appended. + */ + buffer.removeSuffix(prevSrc-prevBoundary); + /* + * Find the part of the source that needs to be decomposed, + * up to the next safe boundary. + */ + src=findNextFCDBoundary(s, src, limit); + /* + * The source text does not fulfill the conditions for FCD. + * Decompose and reorder a limited piece of the text. + */ + decomposeShort(s, prevBoundary, src, false, false, buffer); + prevBoundary=src; + prevFCD16=0; + } + } + return src; + } + + public boolean hasDecompBoundaryBefore(int c) { + return c < minLcccCP || (c <= 0xffff && !singleLeadMightHaveNonZeroFCD16(c)) || + norm16HasDecompBoundaryBefore(getNorm16(c)); + } + public boolean norm16HasDecompBoundaryBefore(int norm16) { + if (norm16 < minNoNoCompNoMaybeCC) { + return true; + } + if (norm16 >= limitNoNo) { + return norm16 <= MIN_NORMAL_MAYBE_YES || norm16 == JAMO_VT; + } + // c decomposes, get everything from the variable-length extra data + int mapping=norm16>>OFFSET_SHIFT; + int firstUnit=extraData.charAt(mapping); + // true if leadCC==0 (hasFCDBoundaryBefore()) + return (firstUnit&MAPPING_HAS_CCC_LCCC_WORD)==0 || (extraData.charAt(mapping-1)&0xff00)==0; + } + public boolean hasDecompBoundaryAfter(int c) { + if (c < minDecompNoCP) { + return true; + } + if (c <= 0xffff && !singleLeadMightHaveNonZeroFCD16(c)) { + return true; + } + return norm16HasDecompBoundaryAfter(getNorm16(c)); + } + public boolean norm16HasDecompBoundaryAfter(int norm16) { + if(norm16 <= minYesNo || isHangulLVT(norm16)) { + return true; + } + if (norm16 >= limitNoNo) { + if (isMaybeOrNonZeroCC(norm16)) { + return norm16 <= MIN_NORMAL_MAYBE_YES || norm16 == JAMO_VT; + } + // Maps to an isCompYesAndZeroCC. + return (norm16 & DELTA_TCCC_MASK) <= DELTA_TCCC_1; + } + // c decomposes, get everything from the variable-length extra data + int mapping=norm16>>OFFSET_SHIFT; + int firstUnit=extraData.charAt(mapping); + // decomp after-boundary: same as hasFCDBoundaryAfter(), + // fcd16<=1 || trailCC==0 + if(firstUnit>0x1ff) { + return false; // trailCC>1 + } + if(firstUnit<=0xff) { + return true; // trailCC==0 + } + // if(trailCC==1) test leadCC==0, same as checking for before-boundary + // true if leadCC==0 (hasFCDBoundaryBefore()) + return (firstUnit&MAPPING_HAS_CCC_LCCC_WORD)==0 || (extraData.charAt(mapping-1)&0xff00)==0; + } + public boolean isDecompInert(int c) { return isDecompYesAndZeroCC(getNorm16(c)); } + + public boolean hasCompBoundaryBefore(int c) { + return c=minMaybeYes; } + private static boolean isInert(int norm16) { return norm16==INERT; } + private static boolean isJamoVT(int norm16) { return norm16==JAMO_VT; } + private int hangulLVT() { return minYesNoMappingsOnly|HAS_COMP_BOUNDARY_AFTER; } + private boolean isHangulLV(int norm16) { return norm16==minYesNo; } + private boolean isHangulLVT(int norm16) { + return norm16==hangulLVT(); + } + private boolean isCompYesAndZeroCC(int norm16) { return norm16=MIN_YES_YES_WITH_CC || norm16=limitNoNo; } + + // For use with isCompYes(). + // Perhaps the compiler can combine the two tests for MIN_YES_YES_WITH_CC. + // static uint8_t getCCFromYes(uint16_t norm16) { + // return norm16>=MIN_YES_YES_WITH_CC ? getCCFromNormalYesOrMaybe(norm16) : 0; + // } + private int getCCFromNoNo(int norm16) { + int mapping=norm16>>OFFSET_SHIFT; + if((extraData.charAt(mapping)&MAPPING_HAS_CCC_LCCC_WORD)!=0) { + return extraData.charAt(mapping-1)&0xff; + } else { + return 0; + } + } + int getTrailCCFromCompYesAndZeroCC(int norm16) { + if(norm16<=minYesNo) { + return 0; // yesYes and Hangul LV have ccc=tccc=0 + } else { + // For Hangul LVT we harmlessly fetch a firstUnit with tccc=0 here. + return extraData.charAt(norm16>>OFFSET_SHIFT)>>8; // tccc from yesNo + } + } + + // Requires algorithmic-NoNo. + private int mapAlgorithmic(int c, int norm16) { + return c+(norm16>>DELTA_SHIFT)-centerNoNoDelta; + } + + // Requires minYesNo>OFFSET_SHIFT); } + + /** + * @return index into maybeYesCompositions, or -1 + */ + private int getCompositionsListForDecompYes(int norm16) { + if(norm16>OFFSET_SHIFT; + } + } + /** + * @return index into maybeYesCompositions + */ + private int getCompositionsListForComposite(int norm16) { + // A composite has both mapping & compositions list. + int list=((MIN_NORMAL_MAYBE_YES-minMaybeYes)+norm16)>>OFFSET_SHIFT; + int firstUnit=maybeYesCompositions.charAt(list); + return list+ // mapping in maybeYesCompositions + 1+ // +1 to skip the first unit with the mapping length + (firstUnit&MAPPING_LENGTH_MASK); // + mapping length + } + + // Decompose a short piece of text which is likely to contain characters that + // fail the quick check loop and/or where the quick check loop's overhead + // is unlikely to be amortized. + // Called by the compose() and makeFCD() implementations. + // Public in Java for collation implementation code. + private int decomposeShort( + CharSequence s, int src, int limit, + boolean stopAtCompBoundary, boolean onlyContiguous, + ReorderingBuffer buffer) { + while(src= limitNoNo) { + if (isMaybeOrNonZeroCC(norm16)) { + buffer.append(c, getCCFromYesOrMaybe(norm16)); + return; + } + // Maps to an isCompYesAndZeroCC. + c=mapAlgorithmic(c, norm16); + norm16=getRawNorm16(c); + } + if (norm16 < minYesNo) { + // c does not decompose + buffer.append(c, 0); + } else if(isHangulLV(norm16) || isHangulLVT(norm16)) { + // Hangul syllable: decompose algorithmically + Hangul.decompose(c, buffer); + } else { + // c decomposes, get everything from the variable-length extra data + int mapping=norm16>>OFFSET_SHIFT; + int firstUnit=extraData.charAt(mapping); + int length=firstUnit&MAPPING_LENGTH_MASK; + int leadCC, trailCC; + trailCC=firstUnit>>8; + if((firstUnit&MAPPING_HAS_CCC_LCCC_WORD)!=0) { + leadCC=extraData.charAt(mapping-1)>>8; + } else { + leadCC=0; + } + ++mapping; // skip over the firstUnit + buffer.append(extraData, mapping, mapping+length, true, leadCC, trailCC); + } + } + + /** + * Finds the recomposition result for + * a forward-combining "lead" character, + * specified with a pointer to its compositions list, + * and a backward-combining "trail" character. + * + *

If the lead and trail characters combine, then this function returns + * the following "compositeAndFwd" value: + *

+     * Bits 21..1  composite character
+     * Bit      0  set if the composite is a forward-combining starter
+     * 
+ * otherwise it returns -1. + * + *

The compositions list has (trail, compositeAndFwd) pair entries, + * encoded as either pairs or triples of 16-bit units. + * The last entry has the high bit of its first unit set. + * + *

The list is sorted by ascending trail characters (there are no duplicates). + * A linear search is used. + * + *

See normalizer2impl.h for a more detailed description + * of the compositions list format. + */ + private static int combine(String compositions, int list, int trail) { + int key1, firstUnit; + if(trail(firstUnit=compositions.charAt(list))) { + list+=2+(firstUnit&COMP_1_TRIPLE); + } + if(key1==(firstUnit&COMP_1_TRAIL_MASK)) { + if((firstUnit&COMP_1_TRIPLE)!=0) { + return (compositions.charAt(list+1)<<16)|compositions.charAt(list+2); + } else { + return compositions.charAt(list+1); + } + } + } else { + // trail character is 3400..10FFFF + // result entry has 3 units + key1=COMP_1_TRAIL_LIMIT+(((trail>>COMP_1_TRAIL_SHIFT))&~COMP_1_TRIPLE); + int key2=(trail<(firstUnit=compositions.charAt(list))) { + list+=2+(firstUnit&COMP_1_TRIPLE); + } else if(key1==(firstUnit&COMP_1_TRAIL_MASK)) { + if(key2>(secondUnit=compositions.charAt(list+1))) { + if((firstUnit&COMP_1_LAST_TUPLE)!=0) { + break; + } else { + list+=3; + } + } else if(key2==(secondUnit&COMP_2_TRAIL_MASK)) { + return ((secondUnit&~COMP_2_TRAIL_MASK)<<16)|compositions.charAt(list+2); + } else { + break; + } + } else { + break; + } + } + } + return -1; + } + + /* + * Recomposes the buffer text starting at recomposeStartIndex + * (which is in NFD - decomposed and canonically ordered), + * and truncates the buffer contents. + * + * Note that recomposition never lengthens the text: + * Any character consists of either one or two code units; + * a composition may contain at most one more code unit than the original starter, + * while the combining mark that is removed has at least one code unit. + */ + private void recompose(ReorderingBuffer buffer, int recomposeStartIndex, + boolean onlyContiguous) { + StringBuilder sb=buffer.getStringBuilder(); + int p=recomposeStartIndex; + if(p==sb.length()) { + return; + } + + int starter, pRemove; + int compositionsList; + int c, compositeAndFwd; + int norm16; + int cc, prevCC; + boolean starterIsSupplementary; + + // Some of the following variables are not used until we have a forward-combining starter + // and are only initialized now to avoid compiler warnings. + compositionsList=-1; // used as indicator for whether we have a forward-combining starter + starter=-1; + starterIsSupplementary=false; + prevCC=0; + + for(;;) { + c=sb.codePointAt(p); + p+=Character.charCount(c); + norm16=getNorm16(c); + cc=getCCFromYesOrMaybe(norm16); + if( // this character combines backward and + isMaybe(norm16) && + // we have seen a starter that combines forward and + compositionsList>=0 && + // the backward-combining character is not blocked + (prevCC=0) { + // The starter and the combining mark (c) do combine. + int composite=compositeAndFwd>>1; + + // Remove the combining mark. + pRemove=p-Character.charCount(c); // pRemove & p: start & limit of the combining mark + sb.delete(pRemove, p); + p=pRemove; + // Replace the starter with the composite. + if(starterIsSupplementary) { + if(composite>0xffff) { + // both are supplementary + sb.setCharAt(starter, UTF16.getLeadSurrogate(composite)); + sb.setCharAt(starter+1, UTF16.getTrailSurrogate(composite)); + } else { + sb.setCharAt(starter, (char)c); + sb.deleteCharAt(starter+1); + // The composite is shorter than the starter, + // move the intermediate characters forward one. + starterIsSupplementary=false; + --p; + } + } else if(composite>0xffff) { + // The composite is longer than the starter, + // move the intermediate characters back one. + starterIsSupplementary=true; + sb.setCharAt(starter, UTF16.getLeadSurrogate(composite)); + sb.insert(starter+1, UTF16.getTrailSurrogate(composite)); + ++p; + } else { + // both are on the BMP + sb.setCharAt(starter, (char)composite); + } + + // Keep prevCC because we removed the combining mark. + + if(p==sb.length()) { + break; + } + // Is the composite a starter that combines forward? + if((compositeAndFwd&1)!=0) { + compositionsList= + getCompositionsListForComposite(getRawNorm16(composite)); + } else { + compositionsList=-1; + } + + // We combined; continue with looking for compositions. + continue; + } + } + + // no combination this time + prevCC=cc; + if(p==sb.length()) { + break; + } + + // If c did not combine, then check if it is a starter. + if(cc==0) { + // Found a new starter. + if((compositionsList=getCompositionsListForDecompYes(norm16))>=0) { + // It may combine with something, prepare for it. + if(c<=0xffff) { + starterIsSupplementary=false; + starter=p-1; + } else { + starterIsSupplementary=true; + starter=p-2; + } + } + } else if(onlyContiguous) { + // FCC: no discontiguous compositions; any intervening character blocks. + compositionsList=-1; + } + } + buffer.flush(); + } + + /** + * Does c have a composition boundary before it? + * True if its decomposition begins with a character that has + * ccc=0 && NFC_QC=Yes (isCompYesAndZeroCC()). + * As a shortcut, this is true if c itself has ccc=0 && NFC_QC=Yes + * (isCompYesAndZeroCC()) so we need not decompose. + */ + private boolean hasCompBoundaryBefore(int c, int norm16) { + return c> OFFSET_SHIFT) <= 0x1ff); + } + + private int findPreviousCompBoundary(CharSequence s, int p, boolean onlyContiguous) { + while(p>0) { + int c=Character.codePointBefore(s, p); + int norm16 = getNorm16(c); + if (norm16HasCompBoundaryAfter(norm16, onlyContiguous)) { + break; + } + p-=Character.charCount(c); + if(hasCompBoundaryBefore(c, norm16)) { + break; + } + } + return p; + } + private int findNextCompBoundary(CharSequence s, int p, int limit, boolean onlyContiguous) { + while(p= 0x0009 && c <= 0x000D) || + (c >= 0x0020 && c <= 0x002F) || + (c >= 0x003A && c <= 0x0040) || + (c >= 0x005B && c <= 0x0060) || + (c >= 0x007B && c <= 0x007E); + } + + public static String canonicalDecomposeWithSingleQuotation(String string) { + Normalizer2 impl = Normalizer2.getNFDInstance(); + char[] src = string.toCharArray(); + int srcIndex = 0; + int srcLimit = src.length; + char[] dest = new char[src.length * 3]; //MAX_BUF_SIZE_DECOMPOSE = 3 + int destIndex = 0; + int destLimit = dest.length; + + int prevSrc; + String norm; + int reorderStartIndex, length; + char c1, c2; + int cp; + int minNoMaybe = 0x00c0; + int cc, prevCC, trailCC; + char[] p; + int pStart; + + // initialize + reorderStartIndex = 0; + prevCC = 0; + norm = null; + cp = 0; + pStart = 0; + + cc = trailCC = -1; // initialize to bogus value + c1 = 0; + for (;;) { + prevSrc=srcIndex; + //quick check (1)less than minNoMaybe (2)no decomp (3)hangual + while (srcIndex != srcLimit && + ((c1 = src[srcIndex]) < minNoMaybe || + (norm = impl.getDecomposition(cp = string.codePointAt(srcIndex))) == null || + (c1 >= '\uac00' && c1 <= '\ud7a3'))) { // Hangul Syllables + prevCC = 0; + srcIndex += (cp < 0x10000) ? 1 : 2; + } + + // copy these code units all at once + if (srcIndex != prevSrc) { + length = srcIndex - prevSrc; + if ((destIndex + length) <= destLimit) { + System.arraycopy(src,prevSrc,dest,destIndex,length); + } + + destIndex += length; + reorderStartIndex = destIndex; + } + + // end of source reached? + if (srcIndex == srcLimit) { + break; + } + + // cp already contains *src and norm32 is set for it, increment src + srcIndex += (cp < 0x10000) ? 1 : 2; + + if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + c2 = 0; + length = 1; + + if (Character.isHighSurrogate(c1) + || Character.isLowSurrogate(c1)) { + norm = null; + } + } else { + length = 2; + c2 = src[srcIndex-1]; + } + + // get the decomposition and the lead and trail cc's + if (norm == null) { + // cp does not decompose + cc = trailCC = UCharacter.getCombiningClass(cp); + p = null; + pStart = -1; + } else { + + pStart = 0; + p = norm.toCharArray(); + length = p.length; + int cpNum = norm.codePointCount(0, length); + cc= UCharacter.getCombiningClass(norm.codePointAt(0)); + trailCC= UCharacter.getCombiningClass(norm.codePointAt(cpNum-1)); + if (length == 1) { + // fastpath a single code unit from decomposition + c1 = p[pStart]; + c2 = 0; + p = null; + pStart = -1; + } + } + + if((destIndex + length * 3) >= destLimit) { // 2 SingleQuotations + // buffer overflow + char[] tmpBuf = new char[destLimit * 2]; + System.arraycopy(dest, 0, tmpBuf, 0, destIndex); + dest = tmpBuf; + destLimit = dest.length; + } + + // append the decomposition to the destination buffer, assume length>0 + { + int reorderSplit = destIndex; + if (p == null) { + // fastpath: single code point + if (needSingleQuotation(c1)) { + //if we need single quotation, no need to consider "prevCC" + //and it must NOT be a supplementary pair + dest[destIndex++] = '\''; + dest[destIndex++] = c1; + dest[destIndex++] = '\''; + trailCC = 0; + } else if(cc != 0 && cc < prevCC) { + // (c1, c2) is out of order with respect to the preceding + // text + destIndex += length; + trailCC = insertOrdered(dest, reorderStartIndex, + reorderSplit, destIndex, c1, c2, cc); + } else { + // just append (c1, c2) + dest[destIndex++] = c1; + if(c2 != 0) { + dest[destIndex++] = c2; + } + } + } else { + // general: multiple code points (ordered by themselves) + // from decomposition + if (needSingleQuotation(p[pStart])) { + dest[destIndex++] = '\''; + dest[destIndex++] = p[pStart++]; + dest[destIndex++] = '\''; + length--; + do { + dest[destIndex++] = p[pStart++]; + } while(--length > 0); + } else if (cc != 0 && cc < prevCC) { + destIndex += length; + trailCC = mergeOrdered(dest, reorderStartIndex, + reorderSplit, p, pStart, + pStart+length); + } else { + // just append the decomposition + do { + dest[destIndex++] = p[pStart++]; + } while (--length > 0); + } + } + } + prevCC = trailCC; + if(prevCC == 0) { + reorderStartIndex = destIndex; + } + } + + return new String(dest, 0, destIndex); + } + + /** + * simpler, single-character version of mergeOrdered() - + * bubble-insert one single code point into the preceding string + * which is already canonically ordered + * (c, c2) may or may not yet have been inserted at src[current]..src[p] + * + * it must be p=current+lengthof(c, c2) i.e. p=current+(c2==0 ? 1 : 2) + * + * before: src[start]..src[current] is already ordered, and + * src[current]..src[p] may or may not hold (c, c2) but + * must be exactly the same length as (c, c2) + * after: src[start]..src[p] is ordered + * + * @return the trailing combining class + */ + private static int/*unsigned byte*/ insertOrdered(char[] source, + int start, + int current, int p, + char c1, char c2, + int/*unsigned byte*/ cc) { + int back, preBack; + int r; + int prevCC, trailCC=cc; + + if (start=prevCC + preBack=back=current; + + PrevArgs prevArgs = new PrevArgs(); + prevArgs.current = current; + prevArgs.start = start; + prevArgs.src = source; + prevArgs.c1 = c1; + prevArgs.c2 = c2; + + // get the prevCC + prevCC=getPrevCC(prevArgs); + preBack = prevArgs.current; + + if(cc=prevCC) { + break; + } + back=preBack; + } + + // this is where we are right now with all these indicies: + // [start]..[pPreBack] 0..? code points that we can ignore + // [pPreBack]..[pBack] 0..1 code points with prevCC<=cc + // [pBack]..[current] 0..n code points with >cc, move up to insert (c, c2) + // [current]..[p] 1 code point (c, c2) with cc + + // move the code units in between up + r=p; + do { + source[--r]=source[--current]; + } while (back!=current); + } + } + + // insert (c1, c2) + source[current] = c1; + if (c2!=0) { + source[(current+1)] = c2; + } + + // we know the cc of the last code point + return trailCC; + } + /** + * merge two UTF-16 string parts together + * to canonically order (order by combining classes) their concatenation + * + * the two strings may already be adjacent, so that the merging is done + * in-place if the two strings are not adjacent, then the buffer holding the + * first one must be large enough + * the second string may or may not be ordered in itself + * + * before: [start]..[current] is already ordered, and + * [next]..[limit] may be ordered in itself, but + * is not in relation to [start..current[ + * after: [start..current+(limit-next)[ is ordered + * + * the algorithm is a simple bubble-sort that takes the characters from + * src[next++] and inserts them in correct combining class order into the + * preceding part of the string + * + * since this function is called much less often than the single-code point + * insertOrdered(), it just uses that for easier maintenance + * + * @return the trailing combining class + */ + private static int /*unsigned byte*/ mergeOrdered(char[] source, + int start, + int current, + char[] data, + int next, + int limit) { + int r; + int /*unsigned byte*/ cc, trailCC=0; + boolean adjacent; + + adjacent= current==next; + NextCCArgs ncArgs = new NextCCArgs(); + ncArgs.source = data; + ncArgs.next = next; + ncArgs.limit = limit; + + if(start!=current) { + + while(ncArgs.next((BASE-TMIN)*TMAX)/2; count+=BASE) { + delta/=(BASE-TMIN); + } + + return count+(((BASE-TMIN+1)*delta)/(delta+SKEW)); + } + + /** + * @return the numeric value of a basic code point (for use in representing integers) + * in the range 0 to BASE-1, or a negative value if cp is invalid. + */ + private static final int decodeDigit(int cp) { + if(cp<='Z') { + if(cp<='9') { + if(cp<'0') { + return -1; + } else { + return cp-'0'+26; // 0..9 -> 26..35 + } + } else { + return cp-'A'; // A-Z -> 0..25 + } + } else if(cp<='z') { + return cp-'a'; // a..z -> 0..25 + } else { + return -1; + } + }; + + private static char asciiCaseMap(char b, boolean uppercase) { + if(uppercase) { + if(SMALL_A<=b && b<=SMALL_Z) { + b-=(SMALL_A-CAPITAL_A); + } + } else { + if(CAPITAL_A<=b && b<=CAPITAL_Z) { + b+=(SMALL_A-CAPITAL_A); + } + } + return b; + } + + /** + * digitToBasic() returns the basic code point whose value + * (when used for representing integers) is d, which must be in the + * range 0 to BASE-1. The lowercase form is used unless the uppercase flag is + * nonzero, in which case the uppercase form is used. + */ + private static char digitToBasic(int digit, boolean uppercase) { + /* 0..25 map to ASCII a..z or A..Z */ + /* 26..35 map to ASCII 0..9 */ + if(digit<26) { + if(uppercase) { + return (char)(CAPITAL_A+digit); + } else { + return (char)(SMALL_A+digit); + } + } else { + return (char)((ZERO-26)+digit); + } + } + + // ICU-13727: Limit input length for n^2 algorithm + // where well-formed strings are at most 59 characters long. + private static final int ENCODE_MAX_CODE_UNITS = 1000; + private static final int DECODE_MAX_CHARS = 2000; + + /** + * Converts Unicode to Punycode. + * The input string must not contain single, unpaired surrogates. + * The output will be represented as an array of ASCII code points. + * + * @param src + * @param caseFlags + * @return + * @throws ParseException + */ + public static StringBuffer encode(StringBuffer src, boolean[] caseFlags) throws ParseException{ + + int[] cpBuffer = new int[MAX_CP_COUNT]; + int n, delta, handledCPCount, basicLength, destLength, bias, j, m, q, k, t, srcCPCount; + char c, c2; + int srcLength = src.length(); + if (srcLength > ENCODE_MAX_CODE_UNITS) { + throw new RuntimeException( + "input too long: " + srcLength + " UTF-16 code units"); + } + int destCapacity = MAX_CP_COUNT; + char[] dest = new char[destCapacity]; + StringBuffer result = new StringBuffer(); + /* + * Handle the basic code points and + * convert extended ones to UTF-32 in cpBuffer (caseFlag in sign bit): + */ + srcCPCount=destLength=0; + + for(j=0; j0) { + if(destLength state to , but guard against overflow: + */ + if(m-n>(0x7fffffff-handledCPCount-delta)/(handledCPCount+1)) { + throw new RuntimeException("Internal program error"); + } + delta+=(m-n)*(handledCPCount+1); + n=m; + + /* Encode a sequence of same code points n */ + for(j=0; jTMAX) { + t=TMAX; + } + */ + + t=k-bias; + if(t=(bias+TMAX)) { + t=TMAX; + } + + if(q DECODE_MAX_CHARS) { + throw new RuntimeException("input too long: " + srcLength + " characters"); + } + StringBuffer result = new StringBuffer(); + int n, destLength, i, bias, basicLength, j, in, oldi, w, k, digit, t, + destCPCount, firstSupplementaryIndex, cpLength; + char b; + int destCapacity = MAX_CP_COUNT; + char[] dest = new char[destCapacity]; + + /* + * Handle the basic code points: + * Let basicLength be the number of input code points + * before the last delimiter, or 0 if there is none, + * then copy the first basicLength code points to the output. + * + * The two following loops iterate backward. + */ + for(j=srcLength; j>0;) { + if(src.charAt(--j)==DELIMITER) { + break; + } + } + destLength=basicLength=destCPCount=j; + + while(j>0) { + b=src.charAt(--j); + if(!isBasic(b)) { + throw new ParseException("Illegal char found", -1); + } + + if(j0 ? basicLength+1 : 0; in=srcLength) { + throw new ParseException("Illegal char found", -1); + } + + digit=decodeDigit(src.charAt(in++)); + if(digit<0) { + throw new ParseException("Invalid char found", -1); + } + if(digit>(0x7fffffff-i)/w) { + /* integer overflow */ + throw new ParseException("Illegal char found", -1); + } + + i+=digit*w; + t=k-bias; + if(t=(bias+TMAX)) { + t=TMAX; + } + if(digit0x7fffffff/(BASE-t)) { + /* integer overflow */ + throw new ParseException("Illegal char found", -1); + } + w*=BASE-t; + } + + /* + * Modification from sample code: + * Increments destCPCount here, + * where needed instead of in for() loop tail. + */ + ++destCPCount; + bias=adaptBias(i-oldi, destCPCount, (oldi==0)); + + /* + * i was supposed to wrap around from (incremented) destCPCount to 0, + * incrementing n each time, so we'll fix that now: + */ + if(i/destCPCount>(0x7fffffff-n)) { + /* integer overflow */ + throw new ParseException("Illegal char found", -1); + } + + n+=i/destCPCount; + i%=destCPCount; + /* not needed for Punycode: */ + /* if (decode_digit(n) <= BASE) return punycode_invalid_input; */ + + if(n>0x10ffff || isSurrogate(n)) { + /* Unicode code point overflow */ + throw new ParseException("Illegal char found", -1); + } + + /* Insert n at position i of the output: */ + cpLength=UTF16.getCharCount(n); + if((destLength+cpLength)1) { + firstSupplementaryIndex=codeUnitIndex; + } else { + ++firstSupplementaryIndex; + } + } else { + codeUnitIndex=firstSupplementaryIndex; + codeUnitIndex=UTF16.moveCodePointOffset(dest, 0, destLength, codeUnitIndex, i-codeUnitIndex); + } + + /* use the UChar index codeUnitIndex instead of the code point index i */ + if(codeUnitIndexReplaceableobject + * @return copy of this iterator + */ + public Object clone(){ + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; // never invoked + } + } + + /** + * Returns the current UTF16 character. + * @return current UTF16 character + */ + public int current(){ + if (currentIndex < replaceable.length()) { + return replaceable.charAt(currentIndex); + } + return DONE; + } + + /** + * Returns the length of the text + * @return length of the text + */ + public int getLength(){ + return replaceable.length(); + } + + /** + * Gets the current currentIndex in text. + * @return current currentIndex in text. + */ + public int getIndex(){ + return currentIndex; + } + + /** + * Returns next UTF16 character and increments the iterator's currentIndex by 1. + * If the resulting currentIndex is greater or equal to the text length, the + * currentIndex is reset to the text length and a value of DONECODEPOINT is + * returned. + * @return next UTF16 character in text or DONE if the new currentIndex is off the + * end of the text range. + */ + public int next(){ + if (currentIndex < replaceable.length()) { + return replaceable.charAt(currentIndex++); + } + return DONE; + } + + + /** + * Returns previous UTF16 character and decrements the iterator's currentIndex by + * 1. + * If the resulting currentIndex is less than 0, the currentIndex is reset to 0 and a + * value of DONECODEPOINT is returned. + * @return next UTF16 character in text or DONE if the new currentIndex is off the + * start of the text range. + */ + public int previous(){ + if (currentIndex > 0) { + return replaceable.charAt(--currentIndex); + } + return DONE; + } + + /** + * Sets the currentIndex to the specified currentIndex in the text and returns that + * single UTF16 character at currentIndex. + * This assumes the text is stored as 16-bit code units. + * @param currentIndex the currentIndex within the text. + * @exception IllegalArgumentException is thrown if an invalid currentIndex is + * supplied. i.e. currentIndex is out of bounds. + */ + public void setIndex(int currentIndex) { + if (currentIndex < 0 || currentIndex > replaceable.length()) { + throw new IllegalArgumentException(); + } + this.currentIndex = currentIndex; + } + + public int getText(char[] fillIn, int offset){ + int length = replaceable.length(); + if(offset < 0 || offset + length > fillIn.length){ + throw new IndexOutOfBoundsException(Integer.toString(length)); + } + replaceable.getChars(0,length,fillIn,offset); + return length; + } + + // private data members ---------------------------------------------------- + + /** + * Replaceable object + */ + private Replaceable replaceable; + /** + * Current currentIndex + */ + private int currentIndex; + +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/StringPrepDataReader.class b/tests/test_data/std/jdk/internal/icu/impl/StringPrepDataReader.class new file mode 100644 index 00000000..b99ed459 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/StringPrepDataReader.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/StringPrepDataReader.java b/tests/test_data/std/jdk/internal/icu/impl/StringPrepDataReader.java new file mode 100644 index 00000000..e21799c8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/StringPrepDataReader.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* +/* + ****************************************************************************** + * Copyright (C) 2003, International Business Machines Corporation and * + * others. All Rights Reserved. * + ****************************************************************************** + * + * Created on May 2, 2003 + * + * To change the template for this generated file go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +// CHANGELOG +// 2005-05-19 Edward Wang +// - copy this file from icu4jsrc_3_2/src/com/ibm/icu/impl/StringPrepDataReader.java +// - move from package com.ibm.icu.impl to package sun.net.idn +// +package jdk.internal.icu.impl; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import jdk.internal.icu.impl.ICUBinary; + + +/** + * @author ram + * + * To change the template for this generated type comment go to + * Window>Preferences>Java>Code Generation>Code and Comments + */ +public final class StringPrepDataReader implements ICUBinary.Authenticate { + + /** + *

private constructor.

+ * @param inputStream ICU uprop.dat file input stream + * @exception IOException throw if data file fails authentication + * @draft 2.1 + */ + public StringPrepDataReader(InputStream inputStream) + throws IOException{ + + unicodeVersion = ICUBinary.readHeader(inputStream, DATA_FORMAT_ID, this); + + + dataInputStream = new DataInputStream(inputStream); + + } + + public void read(byte[] idnaBytes, + char[] mappingTable) + throws IOException{ + + // Read the bytes that make up the idnaTrie + dataInputStream.read(idnaBytes); + + // Read the extra data + for(int i=0;iA trie is a kind of compressed, serializable table of values + * associated with Unicode code points (0..0x10ffff).

+ *

This class defines the basic structure of a trie and provides methods + * to retrieve the offsets to the actual data.

+ *

Data will be the form of an array of basic types, char or int.

+ *

The actual data format will have to be specified by the user in the + * inner static interface com.ibm.icu.impl.Trie.DataManipulate.

+ *

This trie implementation is optimized for getting offset while walking + * forward through a UTF-16 string. + * Therefore, the simplest and fastest access macros are the + * fromLead() and fromOffsetTrail() methods. + * The fromBMP() method are a little more complicated; they get offsets even + * for lead surrogate codepoints, while the fromLead() method get special + * "folded" offsets for lead surrogate code units if there is relevant data + * associated with them. + * From such a folded offsets, an offset needs to be extracted to supply + * to the fromOffsetTrail() methods. + * To handle such supplementary codepoints, some offset information are kept + * in the data.

+ *

Methods in com.ibm.icu.impl.Trie.DataManipulate are called to retrieve + * that offset from the folded value for the lead surrogate unit.

+ *

For examples of use, see com.ibm.icu.impl.CharTrie or + * com.ibm.icu.impl.IntTrie.

+ * @author synwee + * @see com.ibm.icu.impl.CharTrie + * @see com.ibm.icu.impl.IntTrie + * @since release 2.1, Jan 01 2002 + */ +public abstract class Trie +{ + // public class declaration ---------------------------------------- + + /** + * Character data in com.ibm.impl.Trie have different user-specified format + * for different purposes. + * This interface specifies methods to be implemented in order for + * com.ibm.impl.Trie, to surrogate offset information encapsulated within + * the data. + */ + public static interface DataManipulate + { + /** + * Called by com.ibm.icu.impl.Trie to extract from a lead surrogate's + * data + * the index array offset of the indexes for that lead surrogate. + * @param value data value for a surrogate from the trie, including the + * folding offset + * @return data offset or 0 if there is no data for the lead surrogate + */ + public int getFoldingOffset(int value); + } + + // default implementation + private static class DefaultGetFoldingOffset implements DataManipulate { + public int getFoldingOffset(int value) { + return value; + } + } + + // protected constructor ------------------------------------------- + + /** + * Trie constructor for CharTrie use. + * @param inputStream ICU data file input stream which contains the + * trie + * @param dataManipulate object containing the information to parse the + * trie data + * @throws IOException thrown when input stream does not have the + * right header. + */ + protected Trie(InputStream inputStream, + DataManipulate dataManipulate) throws IOException + { + DataInputStream input = new DataInputStream(inputStream); + // Magic number to authenticate the data. + int signature = input.readInt(); + m_options_ = input.readInt(); + + if (!checkHeader(signature)) { + throw new IllegalArgumentException("ICU data file error: Trie header authentication failed, please check if you have the most updated ICU data file"); + } + + if(dataManipulate != null) { + m_dataManipulate_ = dataManipulate; + } else { + m_dataManipulate_ = new DefaultGetFoldingOffset(); + } + m_isLatin1Linear_ = (m_options_ & + HEADER_OPTIONS_LATIN1_IS_LINEAR_MASK_) != 0; + m_dataOffset_ = input.readInt(); + m_dataLength_ = input.readInt(); + unserialize(inputStream); + } + + // protected data members ------------------------------------------ + + /** + * Lead surrogate code points' index displacement in the index array. + *
{@code
+     * 0x10000-0xd800=0x2800
+     * 0x2800 >> INDEX_STAGE_1_SHIFT_
+     * }
+ */ + protected static final int LEAD_INDEX_OFFSET_ = 0x2800 >> 5; + /** + * Shift size for shifting right the input index. 1..9 + */ + protected static final int INDEX_STAGE_1_SHIFT_ = 5; + /** + * Shift size for shifting left the index array values. + * Increases possible data size with 16-bit index values at the cost + * of compactability. + * This requires blocks of stage 2 data to be aligned by + * DATA_GRANULARITY. + * 0..INDEX_STAGE_1_SHIFT + */ + protected static final int INDEX_STAGE_2_SHIFT_ = 2; + /** + * Number of data values in a stage 2 (data array) block. + */ + protected static final int DATA_BLOCK_LENGTH=1< + * getRawOffset(0, ch); + *

+ * will do. Otherwise if it is a supplementary character formed by + * surrogates lead and trail. Then we would have to call getRawOffset() + * with getFoldingIndexOffset(). See getSurrogateOffset(). + * @param offset index offset which ch is to start from + * @param ch index to be used after offset + * @return offset to the data + */ + protected final int getRawOffset(int offset, char ch) + { + return (m_index_[offset + (ch >> INDEX_STAGE_1_SHIFT_)] + << INDEX_STAGE_2_SHIFT_) + + (ch & INDEX_STAGE_3_MASK_); + } + + /** + * Gets the offset to data which the BMP character points to + * Treats a lead surrogate as a normal code point. + * @param ch BMP character + * @return offset to data + */ + protected final int getBMPOffset(char ch) + { + return (ch >= UTF16.LEAD_SURROGATE_MIN_VALUE + && ch <= UTF16.LEAD_SURROGATE_MAX_VALUE) + ? getRawOffset(LEAD_INDEX_OFFSET_, ch) + : getRawOffset(0, ch); + // using a getRawOffset(ch) makes no diff + } + + /** + * Gets the offset to the data which this lead surrogate character points + * to. + * Data at the returned offset may contain folding offset information for + * the next trailing surrogate character. + * @param ch lead surrogate character + * @return offset to data + */ + protected final int getLeadOffset(char ch) + { + return getRawOffset(0, ch); + } + + /** + * Internal trie getter from a code point. + * Could be faster(?) but longer with + * {@code if((c32)<=0xd7ff) { (result)=_TRIE_GET_RAW(trie, data, 0, c32); }} + * Gets the offset to data which the codepoint points to + * @param ch codepoint + * @return offset to data + */ + protected final int getCodePointOffset(int ch) + { + // if ((ch >> 16) == 0) slower + if (ch < 0) { + return -1; + } else if (ch < UTF16.LEAD_SURROGATE_MIN_VALUE) { + // fastpath for the part of the BMP below surrogates (D800) where getRawOffset() works + return getRawOffset(0, (char)ch); + } else if (ch < UTF16.SUPPLEMENTARY_MIN_VALUE) { + // BMP codepoint + return getBMPOffset((char)ch); + } else if (ch <= UCharacter.MAX_VALUE) { + // look at the construction of supplementary characters + // trail forms the ends of it. + return getSurrogateOffset(UTF16.getLeadSurrogate(ch), + (char)(ch & SURROGATE_MASK_)); + } else { + // return -1 if there is an error, in this case we return + return -1; + } + } + + /** + *

Parses the inputstream and creates the trie index with it.

+ *

This is overwritten by the child classes. + * @param inputStream input stream containing the trie information + * @exception IOException thrown when data reading fails. + */ + protected void unserialize(InputStream inputStream) throws IOException + { + //indexLength is a multiple of 1024 >> INDEX_STAGE_2_SHIFT_ + m_index_ = new char[m_dataOffset_]; + DataInputStream input = new DataInputStream(inputStream); + for (int i = 0; i < m_dataOffset_; i ++) { + m_index_[i] = input.readChar(); + } + } + + /** + * Determines if this is a 16 bit trie + * @return true if this is a 16 bit trie + */ + protected final boolean isCharTrie() + { + return (m_options_ & HEADER_OPTIONS_DATA_IS_32_BIT_) == 0; + } + + // private data members -------------------------------------------- + + /** + * Latin 1 option mask + */ + protected static final int HEADER_OPTIONS_LATIN1_IS_LINEAR_MASK_ = 0x200; + /** + * Constant number to authenticate the byte block + */ + protected static final int HEADER_SIGNATURE_ = 0x54726965; + /** + * Header option formatting + */ + private static final int HEADER_OPTIONS_SHIFT_MASK_ = 0xF; + protected static final int HEADER_OPTIONS_INDEX_SHIFT_ = 4; + protected static final int HEADER_OPTIONS_DATA_IS_32_BIT_ = 0x100; + + /** + * Flag indicator for Latin quick access data block + */ + private boolean m_isLatin1Linear_; + + /** + *

Trie options field.

+ *

options bit field:
+ * 9 1 = Latin-1 data is stored linearly at data + DATA_BLOCK_LENGTH
+ * 8 0 = 16-bit data, 1=32-bit data
+ * 7..4 INDEX_STAGE_1_SHIFT // 0..INDEX_STAGE_2_SHIFT
+ * 3..0 INDEX_STAGE_2_SHIFT // 1..9
+ */ + private int m_options_; + + // private methods --------------------------------------------------- + + /** + * Authenticates raw data header. + * Checking the header information, signature and options. + * @param signature This contains the options and type of a Trie + * @return true if the header is authenticated valid + */ + private final boolean checkHeader(int signature) + { + // check the signature + // Trie in big-endian US-ASCII (0x54726965). + // Magic number to authenticate the data. + if (signature != HEADER_SIGNATURE_) { + return false; + } + + if ((m_options_ & HEADER_OPTIONS_SHIFT_MASK_) != + INDEX_STAGE_1_SHIFT_ || + ((m_options_ >> HEADER_OPTIONS_INDEX_SHIFT_) & + HEADER_OPTIONS_SHIFT_MASK_) + != INDEX_STAGE_2_SHIFT_) { + return false; + } + return true; + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2$1.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2$1.class new file mode 100644 index 00000000..4009d3c3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2$1.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2$Range.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2$Range.class new file mode 100644 index 00000000..3ca56ef3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2$Range.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2$Trie2Iterator.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2$Trie2Iterator.class new file mode 100644 index 00000000..d9b5cd33 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2$Trie2Iterator.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2$UTrie2Header.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2$UTrie2Header.class new file mode 100644 index 00000000..74457b19 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2$UTrie2Header.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2$ValueMapper.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2$ValueMapper.class new file mode 100644 index 00000000..e175a6be Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2$ValueMapper.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2.class new file mode 100644 index 00000000..9a26149d Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2.java b/tests/test_data/std/jdk/internal/icu/impl/Trie2.java new file mode 100644 index 00000000..dc066fe2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/Trie2.java @@ -0,0 +1,655 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 2009-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * This is the interface and common implementation of a Unicode Trie2. + * It is a kind of compressed table that maps from Unicode code points (0..0x10ffff) + * to 16- or 32-bit integer values. It works best when there are ranges of + * characters with the same value, which is generally the case with Unicode + * character properties. + * + * This is the second common version of a Unicode trie (hence the name Trie2). + * + */ +abstract class Trie2 implements Iterable { + + /** + * Create a Trie2 from its serialized form. Inverse of utrie2_serialize(). + * + * Reads from the current position and leaves the buffer after the end of the trie. + * + * The serialized format is identical between ICU4C and ICU4J, so this function + * will work with serialized Trie2s from either. + * + * The actual type of the returned Trie2 will be either Trie2_16 or Trie2_32, depending + * on the width of the data. + * + * To obtain the width of the Trie2, check the actual class type of the returned Trie2. + * Or use the createFromSerialized() function of Trie2_16 or Trie2_32, which will + * return only Tries of their specific type/size. + * + * The serialized Trie2 on the stream may be in either little or big endian byte order. + * This allows using serialized Tries from ICU4C without needing to consider the + * byte order of the system that created them. + * + * @param bytes a byte buffer to the serialized form of a UTrie2. + * @return An unserialized Trie2, ready for use. + * @throws IllegalArgumentException if the stream does not contain a serialized Trie2. + * @throws IOException if a read error occurs in the buffer. + * + */ + public static Trie2 createFromSerialized(ByteBuffer bytes) throws IOException { + // From ICU4C utrie2_impl.h + // * Trie2 data structure in serialized form: + // * + // * UTrie2Header header; + // * uint16_t index[header.index2Length]; + // * uint16_t data[header.shiftedDataLength<<2]; -- or uint32_t data[...] + // * @internal + // */ + // typedef struct UTrie2Header { + // /** "Tri2" in big-endian US-ASCII (0x54726932) */ + // uint32_t signature; + + // /** + // * options bit field: + // * 15.. 4 reserved (0) + // * 3.. 0 UTrie2ValueBits valueBits + // */ + // uint16_t options; + // + // /** UTRIE2_INDEX_1_OFFSET..UTRIE2_MAX_INDEX_LENGTH */ + // uint16_t indexLength; + // + // /** (UTRIE2_DATA_START_OFFSET..UTRIE2_MAX_DATA_LENGTH)>>UTRIE2_INDEX_SHIFT */ + // uint16_t shiftedDataLength; + // + // /** Null index and data blocks, not shifted. */ + // uint16_t index2NullOffset, dataNullOffset; + // + // /** + // * First code point of the single-value range ending with U+10ffff, + // * rounded up and then shifted right by UTRIE2_SHIFT_1. + // */ + // uint16_t shiftedHighStart; + // } UTrie2Header; + + ByteOrder outerByteOrder = bytes.order(); + try { + UTrie2Header header = new UTrie2Header(); + + /* check the signature */ + header.signature = bytes.getInt(); + switch (header.signature) { + case 0x54726932: + // The buffer is already set to the trie data byte order. + break; + case 0x32697254: + // Temporarily reverse the byte order. + boolean isBigEndian = outerByteOrder == ByteOrder.BIG_ENDIAN; + bytes.order(isBigEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + header.signature = 0x54726932; + break; + default: + throw new IllegalArgumentException("Buffer does not contain a serialized UTrie2"); + } + + header.options = bytes.getChar(); + header.indexLength = bytes.getChar(); + header.shiftedDataLength = bytes.getChar(); + header.index2NullOffset = bytes.getChar(); + header.dataNullOffset = bytes.getChar(); + header.shiftedHighStart = bytes.getChar(); + + if ((header.options & UTRIE2_OPTIONS_VALUE_BITS_MASK) != 0) { + throw new IllegalArgumentException("UTrie2 serialized format error."); + } + + Trie2 This; + This = new Trie2_16(); + This.header = header; + + /* get the length values and offsets */ + This.indexLength = header.indexLength; + This.dataLength = header.shiftedDataLength << UTRIE2_INDEX_SHIFT; + This.index2NullOffset = header.index2NullOffset; + This.dataNullOffset = header.dataNullOffset; + This.highStart = header.shiftedHighStart << UTRIE2_SHIFT_1; + This.highValueIndex = This.dataLength - UTRIE2_DATA_GRANULARITY; + This.highValueIndex += This.indexLength; + + // Allocate the Trie2 index array. If the data width is 16 bits, the array also + // includes the space for the data. + + int indexArraySize = This.indexLength; + indexArraySize += This.dataLength; + This.index = new char[indexArraySize]; + + /* Read in the index */ + int i; + for (i=0; i iterator() { + return iterator(defaultValueMapper); + } + + private static ValueMapper defaultValueMapper = new ValueMapper() { + public int map(int in) { + return in; + } + }; + + /** + * Create an iterator over the value ranges from this Trie2. + * Values from the Trie2 are passed through a caller-supplied remapping function, + * and it is the remapped values that determine the ranges that + * will be produced by the iterator. + * + * + * @param mapper provides a function to remap values obtained from the Trie2. + * @return an Iterator + */ + public Iterator iterator(ValueMapper mapper) { + return new Trie2Iterator(mapper); + } + + /** + * When iterating over the contents of a Trie2, an instance of TrieValueMapper may + * be used to remap the values from the Trie2. The remapped values will be used + * both in determining the ranges of codepoints and as the value to be returned + * for each range. + * + * Example of use, with an anonymous subclass of TrieValueMapper: + * + * + * ValueMapper m = new ValueMapper() { + * int map(int in) {return in & 0x1f;}; + * } + * for (Iterator iter = trie.iterator(m); i.hasNext(); ) { + * Trie2EnumRange r = i.next(); + * ... // Do something with the range r. + * } + * + */ + public interface ValueMapper { + public int map(int originalVal); + } + + //-------------------------------------------------------------------------------- + // + // Below this point are internal implementation items. No further public API. + // + //-------------------------------------------------------------------------------- + + /** + * Trie2 data structure in serialized form: + * + * UTrie2Header header; + * uint16_t index[header.index2Length]; + * uint16_t data[header.shiftedDataLength<<2]; -- or uint32_t data[...] + * + * For Java, this is read from the stream into an instance of UTrie2Header. + * (The C version just places a struct over the raw serialized data.) + * + * @internal + */ + static class UTrie2Header { + /** "Tri2" in big-endian US-ASCII (0x54726932) */ + int signature; + + /** + * options bit field (uint16_t): + * 15.. 4 reserved (0) + * 3.. 0 UTrie2ValueBits valueBits + */ + int options; + + /** UTRIE2_INDEX_1_OFFSET..UTRIE2_MAX_INDEX_LENGTH (uint16_t) */ + int indexLength; + + /** (UTRIE2_DATA_START_OFFSET..UTRIE2_MAX_DATA_LENGTH)>>UTRIE2_INDEX_SHIFT (uint16_t) */ + int shiftedDataLength; + + /** Null index and data blocks, not shifted. (uint16_t) */ + int index2NullOffset, dataNullOffset; + + /** + * First code point of the single-value range ending with U+10ffff, + * rounded up and then shifted right by UTRIE2_SHIFT_1. (uint16_t) + */ + int shiftedHighStart; + } + + // + // Data members of UTrie2. + // + UTrie2Header header; + char index[]; // Index array. Includes data for 16 bit Tries. + int data16; // Offset to data portion of the index array, if 16 bit data. + // zero if 32 bit data. + int data32[]; // NULL if 16b data is used via index + + int indexLength; + int dataLength; + int index2NullOffset; // 0xffff if there is no dedicated index-2 null block + int initialValue; + + /** Value returned for out-of-range code points and illegal UTF-8. */ + int errorValue; + + /* Start of the last range which ends at U+10ffff, and its value. */ + int highStart; + int highValueIndex; + + int dataNullOffset; + + /** + * Trie2 constants, defining shift widths, index array lengths, etc. + * + * These are needed for the runtime macros but users can treat these as + * implementation details and skip to the actual public API further below. + */ + + static final int UTRIE2_OPTIONS_VALUE_BITS_MASK=0x000f; + + + /** Shift size for getting the index-1 table offset. */ + static final int UTRIE2_SHIFT_1=6+5; + + /** Shift size for getting the index-2 table offset. */ + static final int UTRIE2_SHIFT_2=5; + + /** + * Difference between the two shift sizes, + * for getting an index-1 offset from an index-2 offset. 6=11-5 + */ + static final int UTRIE2_SHIFT_1_2=UTRIE2_SHIFT_1-UTRIE2_SHIFT_2; + + /** + * Number of index-1 entries for the BMP. 32=0x20 + * This part of the index-1 table is omitted from the serialized form. + */ + static final int UTRIE2_OMITTED_BMP_INDEX_1_LENGTH=0x10000>>UTRIE2_SHIFT_1; + + /** Number of entries in an index-2 block. 64=0x40 */ + static final int UTRIE2_INDEX_2_BLOCK_LENGTH=1<>UTRIE2_SHIFT_2. (There are 1024=0x400 lead surrogates.) + */ + static final int UTRIE2_LSCP_INDEX_2_OFFSET=0x10000>>UTRIE2_SHIFT_2; + static final int UTRIE2_LSCP_INDEX_2_LENGTH=0x400>>UTRIE2_SHIFT_2; + + /** Count the lengths of both BMP pieces. 2080=0x820 */ + static final int UTRIE2_INDEX_2_BMP_LENGTH=UTRIE2_LSCP_INDEX_2_OFFSET+UTRIE2_LSCP_INDEX_2_LENGTH; + + /** + * The 2-byte UTF-8 version of the index-2 table follows at offset 2080=0x820. + * Length 32=0x20 for lead bytes C0..DF, regardless of UTRIE2_SHIFT_2. + */ + static final int UTRIE2_UTF8_2B_INDEX_2_OFFSET=UTRIE2_INDEX_2_BMP_LENGTH; + static final int UTRIE2_UTF8_2B_INDEX_2_LENGTH=0x800>>6; /* U+0800 is the first code point after 2-byte UTF-8 */ + + /** + * The index-1 table, only used for supplementary code points, at offset 2112=0x840. + * Variable length, for code points up to highStart, where the last single-value range starts. + * Maximum length 512=0x200=0x100000>>UTRIE2_SHIFT_1. + * (For 0x100000 supplementary code points U+10000..U+10ffff.) + * + * The part of the index-2 table for supplementary code points starts + * after this index-1 table. + * + * Both the index-1 table and the following part of the index-2 table + * are omitted completely if there is only BMP data. + */ + static final int UTRIE2_INDEX_1_OFFSET=UTRIE2_UTF8_2B_INDEX_2_OFFSET+UTRIE2_UTF8_2B_INDEX_2_LENGTH; + + /** + * The illegal-UTF-8 data block follows the ASCII block, at offset 128=0x80. + * Used with linear access for single bytes 0..0xbf for simple error handling. + * Length 64=0x40, not UTRIE2_DATA_BLOCK_LENGTH. + */ + static final int UTRIE2_BAD_UTF8_DATA_OFFSET=0x80; + + /** + * Implementation class for an iterator over a Trie2. + * + * Iteration over a Trie2 first returns all of the ranges that are indexed by code points, + * then returns the special alternate values for the lead surrogates + * + * @internal + */ + class Trie2Iterator implements Iterator { + + // The normal constructor that configures the iterator to cover the complete + // contents of the Trie2 + Trie2Iterator(ValueMapper vm) { + mapper = vm; + nextStart = 0; + limitCP = 0x110000; + doLeadSurrogates = true; + } + + /** + * The main next() function for Trie2 iterators + * + */ + public Range next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (nextStart >= limitCP) { + // Switch over from iterating normal code point values to + // doing the alternate lead-surrogate values. + doingCodePoints = false; + nextStart = 0xd800; + } + int endOfRange = 0; + int val = 0; + int mappedVal = 0; + + if (doingCodePoints) { + // Iteration over code point values. + val = get(nextStart); + mappedVal = mapper.map(val); + endOfRange = rangeEnd(nextStart, limitCP, val); + // Loop once for each range in the Trie2 with the same raw (unmapped) value. + // Loop continues so long as the mapped values are the same. + for (;;) { + if (endOfRange >= limitCP-1) { + break; + } + val = get(endOfRange+1); + if (mapper.map(val) != mappedVal) { + break; + } + endOfRange = rangeEnd(endOfRange+1, limitCP, val); + } + } else { + // Iteration over the alternate lead surrogate values. + val = getFromU16SingleLead((char)nextStart); + mappedVal = mapper.map(val); + endOfRange = rangeEndLS((char)nextStart); + // Loop once for each range in the Trie2 with the same raw (unmapped) value. + // Loop continues so long as the mapped values are the same. + for (;;) { + if (endOfRange >= 0xdbff) { + break; + } + val = getFromU16SingleLead((char)(endOfRange+1)); + if (mapper.map(val) != mappedVal) { + break; + } + endOfRange = rangeEndLS((char)(endOfRange+1)); + } + } + returnValue.startCodePoint = nextStart; + returnValue.endCodePoint = endOfRange; + returnValue.value = mappedVal; + returnValue.leadSurrogate = !doingCodePoints; + nextStart = endOfRange+1; + return returnValue; + } + + /** + * + */ + public boolean hasNext() { + return doingCodePoints && (doLeadSurrogates || nextStart < limitCP) || nextStart < 0xdc00; + } + + private int rangeEndLS(char startingLS) { + if (startingLS >= 0xdbff) { + return 0xdbff; + } + + int c; + int val = getFromU16SingleLead(startingLS); + for (c = startingLS+1; c <= 0x0dbff; c++) { + if (getFromU16SingleLead((char)c) != val) { + break; + } + } + return c-1; + } + + // + // Iteration State Variables + // + private ValueMapper mapper; + private Range returnValue = new Range(); + // The starting code point for the next range to be returned. + private int nextStart; + // The upper limit for the last normal range to be returned. Normally 0x110000, but + // may be lower when iterating over the code points for a single lead surrogate. + private int limitCP; + + // True while iterating over the Trie2 values for code points. + // False while iterating over the alternate values for lead surrogates. + private boolean doingCodePoints = true; + + // True if the iterator should iterate the special values for lead surrogates in + // addition to the normal values for code points. + private boolean doLeadSurrogates = true; + } + + /** + * Find the last character in a contiguous range of characters with the + * same Trie2 value as the input character. + * + * @param c The character to begin with. + * @return The last contiguous character with the same value. + */ + int rangeEnd(int start, int limitp, int val) { + int c; + int limit = Math.min(highStart, limitp); + + for (c = start+1; c < limit; c++) { + if (get(c) != val) { + break; + } + } + if (c >= highStart) { + c = limitp; + } + return c - 1; + } + + + // + // Hashing implementation functions. FNV hash. Respected public domain algorithm. + // + private static int initHash() { + return 0x811c9DC5; // unsigned 2166136261 + } + + private static int hashByte(int h, int b) { + h = h * 16777619; + h = h ^ b; + return h; + } + + private static int hashUChar32(int h, int c) { + h = Trie2.hashByte(h, c & 255); + h = Trie2.hashByte(h, (c>>8) & 255); + h = Trie2.hashByte(h, c>>16); + return h; + } + + private static int hashInt(int h, int i) { + h = Trie2.hashByte(h, i & 255); + h = Trie2.hashByte(h, (i>>8) & 255); + h = Trie2.hashByte(h, (i>>16) & 255); + h = Trie2.hashByte(h, (i>>24) & 255); + return h; + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2_16.class b/tests/test_data/std/jdk/internal/icu/impl/Trie2_16.class new file mode 100644 index 00000000..f1b86de7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Trie2_16.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Trie2_16.java b/tests/test_data/std/jdk/internal/icu/impl/Trie2_16.java new file mode 100644 index 00000000..06bc79ae --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/Trie2_16.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 2009-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import java.io.IOException; +import java.nio.ByteBuffer; + + +/** + * @author aheninger + * + * A read-only Trie2, holding 16 bit data values. + * + * A Trie2 is a highly optimized data structure for mapping from Unicode + * code points (values ranging from 0 to 0x10ffff) to a 16 or 32 bit value. + * + * See class Trie2 for descriptions of the API for accessing the contents of a trie. + * + * The fundamental data access methods are declared final in this class, with + * the intent that applications might gain a little extra performance, when compared + * with calling the same methods via the abstract UTrie2 base class. + */ +public final class Trie2_16 extends Trie2 { + + /** + * Internal constructor, not for general use. + */ + Trie2_16() { + } + + + /** + * Create a Trie2 from its serialized form. Inverse of utrie2_serialize(). + * The serialized format is identical between ICU4C and ICU4J, so this function + * will work with serialized Trie2s from either. + * + * The serialized Trie2 in the bytes may be in either little or big endian byte order. + * This allows using serialized Tries from ICU4C without needing to consider the + * byte order of the system that created them. + * + * @param bytes a byte buffer to the serialized form of a UTrie2. + * @return An unserialized Trie2_16, ready for use. + * @throws IllegalArgumentException if the buffer does not contain a serialized Trie2. + * @throws IOException if a read error occurs in the buffer. + * @throws ClassCastException if the bytes contain a serialized Trie2_32 + */ + public static Trie2_16 createFromSerialized(ByteBuffer bytes) throws IOException { + return (Trie2_16) Trie2.createFromSerialized(bytes); + } + + /** + * Get the value for a code point as stored in the Trie2. + * + * @param codePoint the code point + * @return the value + */ + @Override + public final int get(int codePoint) { + int value; + int ix; + + if (codePoint >= 0) { + if (codePoint < 0x0d800 || (codePoint > 0x0dbff && codePoint <= 0x0ffff)) { + // Ordinary BMP code point, excluding leading surrogates. + // BMP uses a single level lookup. BMP index starts at offset 0 in the Trie2 index. + // 16 bit data is stored in the index array itself. + ix = index[codePoint >> UTRIE2_SHIFT_2]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); + value = index[ix]; + return value; + } + if (codePoint <= 0xffff) { + // Lead Surrogate Code Point. A Separate index section is stored for + // lead surrogate code units and code points. + // The main index has the code unit data. + // For this function, we need the code point data. + // Note: this expression could be refactored for slightly improved efficiency, but + // surrogate code points will be so rare in practice that it's not worth it. + ix = index[UTRIE2_LSCP_INDEX_2_OFFSET + ((codePoint - 0xd800) >> UTRIE2_SHIFT_2)]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); + value = index[ix]; + return value; + } + if (codePoint < highStart) { + // Supplemental code point, use two-level lookup. + ix = (UTRIE2_INDEX_1_OFFSET - UTRIE2_OMITTED_BMP_INDEX_1_LENGTH) + (codePoint >> UTRIE2_SHIFT_1); + ix = index[ix]; + ix += (codePoint >> UTRIE2_SHIFT_2) & UTRIE2_INDEX_2_MASK; + ix = index[ix]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codePoint & UTRIE2_DATA_MASK); + value = index[ix]; + return value; + } + if (codePoint <= 0x10ffff) { + value = index[highValueIndex]; + return value; + } + } + + // Fall through. The code point is outside of the legal range of 0..0x10ffff. + return errorValue; + } + + + /** + * Get a Trie2 value for a UTF-16 code unit. + * + * This function returns the same value as get() if the input + * character is outside of the lead surrogate range + * + * There are two values stored in a Trie2 for inputs in the lead + * surrogate range. This function returns the alternate value, + * while Trie2.get() returns the main value. + * + * @param codeUnit a 16 bit code unit or lead surrogate value. + * @return the value + */ + @Override + public int getFromU16SingleLead(char codeUnit) { + int value; + int ix; + + // Because the input is a 16 bit char, we can skip the tests for it being in + // the BMP range. It is. + ix = index[codeUnit >> UTRIE2_SHIFT_2]; + ix = (ix << UTRIE2_INDEX_SHIFT) + (codeUnit & UTRIE2_DATA_MASK); + value = index[ix]; + return value; + } + + /** + * @return the number of bytes of the serialized trie + */ + public int getSerializedLength() { + return 16+(header.indexLength+dataLength)*2; + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps$IsAcceptable.class b/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps$IsAcceptable.class new file mode 100644 index 00000000..c3b185f8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps$IsAcceptable.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps.class b/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps.class new file mode 100644 index 00000000..f61d3d36 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps.java b/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps.java new file mode 100644 index 00000000..287ac38c --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/UBiDiProps.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + ******************************************************************************* + * + * Copyright (C) 2004-2014, International Business Machines + * Corporation and others. All Rights Reserved. + * + ******************************************************************************* + * file name: UBiDiProps.java + * encoding: US-ASCII + * tab size: 8 (not used) + * indentation:4 + * + * created on: 2005jan16 + * created by: Markus W. Scherer + * + * Low-level Unicode bidi/shaping properties access. + * Java port of ubidi_props.h/.c. + */ + +package jdk.internal.icu.impl; + +import jdk.internal.icu.lang.UCharacter; +import jdk.internal.icu.util.VersionInfo; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.MissingResourceException; + +public final class UBiDiProps { + // constructors etc. --------------------------------------------------- *** + + // port of ubidi_openProps() + private UBiDiProps() throws IOException{ + ByteBuffer bytes=ICUBinary.getRequiredData(DATA_FILE_NAME); + readData(bytes); + } + + private void readData(ByteBuffer bytes) throws IOException { + // read the header + ICUBinary.readHeader(bytes, FMT, new IsAcceptable()); + + // read indexes[] + int i, count; + count=bytes.getInt(); + if(countexpectedTrieLength) { + throw new IOException(DATA_FILE_NAME+": not enough bytes for the trie"); + } + // skip padding after trie bytes + ICUBinary.skipBytes(bytes, expectedTrieLength-trieLength); + + // read mirrors[] + count=indexes[IX_MIRROR_LENGTH]; + if(count>0) { + mirrors=new int[count]; + for(i=0; i>JT_SHIFT; + } + + public final int getJoiningGroup(int c) { + int start, limit; + + start=indexes[IX_JG_START]; + limit=indexes[IX_JG_LIMIT]; + if(start<=c && c>BPT_SHIFT; + } + + public final int getPairedBracket(int c) { + int props=trie.get(c); + if((props&BPT_MASK)==0) { + return c; + } else { + return getMirror(c, props); + } + } + + // data members -------------------------------------------------------- *** + private int indexes[]; + private int mirrors[]; + private byte jgArray[]; + private byte jgArray2[]; + + private Trie2_16 trie; + + // data format constants ----------------------------------------------- *** + @SuppressWarnings("deprecation") + private static final String DATA_FILE_NAME = + "/jdk/internal/icu/impl/data/icudt" + + VersionInfo.ICU_DATA_VERSION_PATH + + "/ubidi.icu"; + + /* format "BiDi" */ + private static final int FMT=0x42694469; + + /* indexes into indexes[] */ + private static final int IX_TRIE_SIZE=2; + private static final int IX_MIRROR_LENGTH=3; + + private static final int IX_JG_START=4; + private static final int IX_JG_LIMIT=5; + private static final int IX_JG_START2=6; /* new in format version 2.2, ICU 54 */ + private static final int IX_JG_LIMIT2=7; + + private static final int IX_TOP=16; + + // definitions for 16-bit bidi/shaping properties word ----------------- *** + + /* CLASS_SHIFT=0, */ /* bidi class: 5 bits (4..0) */ + private static final int JT_SHIFT=5; /* joining type: 3 bits (7..5) */ + + private static final int BPT_SHIFT=8; /* Bidi_Paired_Bracket_Type(bpt): 2 bits (9..8) */ + + private static final int MIRROR_DELTA_SHIFT=13; /* bidi mirroring delta: 3 bits (15..13) */ + + private static final int CLASS_MASK= 0x0000001f; + private static final int JT_MASK= 0x000000e0; + private static final int BPT_MASK= 0x00000300; + + private static final int getClassFromProps(int props) { + return props&CLASS_MASK; + } + private static final boolean getFlagFromProps(int props, int shift) { + return ((props>>shift)&1)!=0; + } + private static final int getMirrorDeltaFromProps(int props) { + return (short)props>>MIRROR_DELTA_SHIFT; + } + + private static final int ESC_MIRROR_DELTA=-4; + + // definitions for 32-bit mirror table entry --------------------------- *** + + /* the source Unicode code point takes 21 bits (20..0) */ + private static final int MIRROR_INDEX_SHIFT=21; + + private static final int getMirrorCodePoint(int m) { + return m&0x1fffff; + } + private static final int getMirrorIndex(int m) { + return m>>>MIRROR_INDEX_SHIFT; + } + + + /* + * public singleton instance + */ + public static final UBiDiProps INSTANCE; + + // This static initializer block must be placed after + // other static member initialization + static { + try { + INSTANCE = new UBiDiProps(); + } catch (IOException e) { + throw new MissingResourceException(e.getMessage(),DATA_FILE_NAME,""); + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$1.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$1.class new file mode 100644 index 00000000..88f457b6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$1.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$BiDiIntProperty.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$BiDiIntProperty.class new file mode 100644 index 00000000..470e2664 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$BiDiIntProperty.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$CombiningClassIntProperty.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$CombiningClassIntProperty.class new file mode 100644 index 00000000..27a4961e Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$CombiningClassIntProperty.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$IntProperty.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$IntProperty.class new file mode 100644 index 00000000..4fc4b7ef Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$IntProperty.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$IsAcceptable.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$IsAcceptable.class new file mode 100644 index 00000000..44084567 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$IsAcceptable.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$NormQuickCheckIntProperty.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$NormQuickCheckIntProperty.class new file mode 100644 index 00000000..6d174a90 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty$NormQuickCheckIntProperty.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty.class b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty.class new file mode 100644 index 00000000..f31a52a9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty.java b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty.java new file mode 100644 index 00000000..474b169e --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/UCharacterProperty.java @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + ******************************************************************************* + * Copyright (C) 1996-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.MissingResourceException; + +import jdk.internal.icu.lang.UCharacter.HangulSyllableType; +import jdk.internal.icu.lang.UCharacter.NumericType; +import jdk.internal.icu.text.UTF16; +import jdk.internal.icu.text.UnicodeSet; +import jdk.internal.icu.util.VersionInfo; + +/** +*

Internal class used for Unicode character property database.

+*

This classes store binary data read from uprops.icu. +* It does not have the capability to parse the data into more high-level +* information. It only returns bytes of information when required.

+*

Due to the form most commonly used for retrieval, array of char is used +* to store the binary data.

+*

UCharacterPropertyDB also contains information on accessing indexes to +* significant points in the binary data.

+*

Responsibility for molding the binary data into more meaning form lies on +* UCharacter.

+* @author Syn Wee Quek +* @since release 2.1, february 1st 2002 +*/ + +public final class UCharacterProperty +{ + // public data members ----------------------------------------------- + + /* + * public singleton instance + */ + public static final UCharacterProperty INSTANCE; + + /** + * Trie data + */ + public Trie2_16 m_trie_; + + /** + * Unicode version + */ + public VersionInfo m_unicodeVersion_; + + /** + * Character type mask + */ + public static final int TYPE_MASK = 0x1F; + + // uprops.h enum UPropertySource --------------------------------------- *** + + /** From uchar.c/uprops.icu main trie */ + public static final int SRC_CHAR=1; + /** From uchar.c/uprops.icu properties vectors trie */ + public static final int SRC_PROPSVEC=2; + /** From ubidi_props.c/ubidi.icu */ + public static final int SRC_BIDI=5; + /** From normalizer2impl.cpp/nfc.nrm */ + public static final int SRC_NFC=8; + /** From normalizer2impl.cpp/nfkc.nrm */ + public static final int SRC_NFKC=9; + + // public methods ---------------------------------------------------- + + /** + * Gets the main property value for code point ch. + * @param ch code point whose property value is to be retrieved + * @return property value of code point + */ + public final int getProperty(int ch) + { + return m_trie_.get(ch); + } + + /** + * Gets the unicode additional properties. + * Java version of C u_getUnicodeProperties(). + * @param codepoint codepoint whose additional properties is to be + * retrieved + * @param column The column index. + * @return unicode properties + */ + public int getAdditional(int codepoint, int column) { + assert column >= 0; + if (column >= m_additionalColumnsCount_) { + return 0; + } + return m_additionalVectors_[m_additionalTrie_.get(codepoint) + column]; + } + + /** + *

Get the "age" of the code point.

+ *

The "age" is the Unicode version when the code point was first + * designated (as a non-character or for Private Use) or assigned a + * character.

+ *

This can be useful to avoid emitting code points to receiving + * processes that do not accept newer characters.

+ *

The data is from the UCD file DerivedAge.txt.

+ *

This API does not check the validity of the codepoint.

+ * @param codepoint The code point. + * @return the Unicode version number + */ + public VersionInfo getAge(int codepoint) + { + int version = getAdditional(codepoint, 0) >> AGE_SHIFT_; + return VersionInfo.getInstance( + (version >> FIRST_NIBBLE_SHIFT_) & LAST_NIBBLE_MASK_, + version & LAST_NIBBLE_MASK_, 0, 0); + } + + // int-value and enumerated properties --------------------------------- *** + + public int getType(int c) { + return getProperty(c)&TYPE_MASK; + } + + /* + * Map some of the Grapheme Cluster Break values to Hangul Syllable Types. + * Hangul_Syllable_Type is fully redundant with a subset of Grapheme_Cluster_Break. + */ + private static final int /* UHangulSyllableType */ gcbToHst[]={ + HangulSyllableType.NOT_APPLICABLE, /* U_GCB_OTHER */ + HangulSyllableType.NOT_APPLICABLE, /* U_GCB_CONTROL */ + HangulSyllableType.NOT_APPLICABLE, /* U_GCB_CR */ + HangulSyllableType.NOT_APPLICABLE, /* U_GCB_EXTEND */ + HangulSyllableType.LEADING_JAMO, /* U_GCB_L */ + HangulSyllableType.NOT_APPLICABLE, /* U_GCB_LF */ + HangulSyllableType.LV_SYLLABLE, /* U_GCB_LV */ + HangulSyllableType.LVT_SYLLABLE, /* U_GCB_LVT */ + HangulSyllableType.TRAILING_JAMO, /* U_GCB_T */ + HangulSyllableType.VOWEL_JAMO /* U_GCB_V */ + /* + * Omit GCB values beyond what we need for hst. + * The code below checks for the array length. + */ + }; + + private class IntProperty { + int column; // SRC_PROPSVEC column, or "source" if mask==0 + int mask; + int shift; + + IntProperty(int column, int mask, int shift) { + this.column=column; + this.mask=mask; + this.shift=shift; + } + + IntProperty(int source) { + this.column=source; + this.mask=0; + } + + int getValue(int c) { + // systematic, directly stored properties + return (getAdditional(c, column)&mask)>>>shift; + } + } + + private class BiDiIntProperty extends IntProperty { + BiDiIntProperty() { + super(SRC_BIDI); + } + } + + private class CombiningClassIntProperty extends IntProperty { + CombiningClassIntProperty(int source) { + super(source); + } + } + + private class NormQuickCheckIntProperty extends IntProperty { // UCHAR_NF*_QUICK_CHECK properties + int which; + int max; + + NormQuickCheckIntProperty(int source, int which, int max) { + super(source); + this.which=which; + this.max=max; + } + } + + private IntProperty intProp = new BiDiIntProperty() { // BIDI_PAIRED_BRACKET_TYPE + int getValue(int c) { + return UBiDiProps.INSTANCE.getPairedBracketType(c); + } + }; + + public int getIntPropertyValue(int c, int which) { + if (which == BIDI_PAIRED_BRACKET_TYPE) { + return intProp.getValue(c); + } + return 0; // undefined + } + + /** + * Forms a supplementary code point from the argument character
+ * Note this is for internal use hence no checks for the validity of the + * surrogate characters are done + * @param lead lead surrogate character + * @param trail trailing surrogate character + * @return code point of the supplementary character + */ + public static int getRawSupplementary(char lead, char trail) + { + return (lead << LEAD_SURROGATE_SHIFT_) + trail + SURROGATE_OFFSET_; + } + + /** + * Gets the type mask + * @param type character type + * @return mask + */ + public static final int getMask(int type) + { + return 1 << type; + } + + /** + * Returns the digit values of characters like 'A' - 'Z', normal, + * half-width and full-width. This method assumes that the other digit + * characters are checked by the calling method. + * @param ch character to test + * @return -1 if ch is not a character of the form 'A' - 'Z', otherwise + * its corresponding digit will be returned. + */ + public static int getEuropeanDigit(int ch) { + if ((ch > 0x7a && ch < 0xff21) + || ch < 0x41 || (ch > 0x5a && ch < 0x61) + || ch > 0xff5a || (ch > 0xff3a && ch < 0xff41)) { + return -1; + } + if (ch <= 0x7a) { + // ch >= 0x41 or ch < 0x61 + return ch + 10 - ((ch <= 0x5a) ? 0x41 : 0x61); + } + // ch >= 0xff21 + if (ch <= 0xff3a) { + return ch + 10 - 0xff21; + } + // ch >= 0xff41 && ch <= 0xff5a + return ch + 10 - 0xff41; + } + + public int digit(int c) { + int value = getNumericTypeValue(getProperty(c)) - NTV_DECIMAL_START_; + if(value<=9) { + return value; + } else { + return -1; + } + } + + // protected variables ----------------------------------------------- + + /** + * Extra property trie + */ + Trie2_16 m_additionalTrie_; + /** + * Extra property vectors, 1st column for age and second for binary + * properties. + */ + int m_additionalVectors_[]; + /** + * Number of additional columns + */ + int m_additionalColumnsCount_; + /** + * Maximum values for block, bits used as in vector word + * 0 + */ + int m_maxBlockScriptValue_; + /** + * Maximum values for script, bits used as in vector word + * 0 + */ + int m_maxJTGValue_; + /** + * Script_Extensions data + */ + public char[] m_scriptExtensions_; + + // private variables ------------------------------------------------- + + /** + * Default name of the datafile + */ + @SuppressWarnings("deprecation") + private static final String DATA_FILE_NAME_ = + "/jdk/internal/icu/impl/data/icudt" + + VersionInfo.ICU_DATA_VERSION_PATH + + "/uprops.icu"; + + /** + * Shift value for lead surrogate to form a supplementary character. + */ + private static final int LEAD_SURROGATE_SHIFT_ = 10; + /** + * Offset to add to combined surrogate pair to avoid masking. + */ + private static final int SURROGATE_OFFSET_ = + UTF16.SUPPLEMENTARY_MIN_VALUE - + (UTF16.SURROGATE_MIN_VALUE << + LEAD_SURROGATE_SHIFT_) - + UTF16.TRAIL_SURROGATE_MIN_VALUE; + + + // property data constants ------------------------------------------------- + + /** + * Numeric types and values in the main properties words. + */ + private static final int NUMERIC_TYPE_VALUE_SHIFT_ = 6; + private static final int getNumericTypeValue(int props) { + return props >> NUMERIC_TYPE_VALUE_SHIFT_; + } + + /* constants for the storage form of numeric types and values */ + /** No numeric value. */ + private static final int NTV_NONE_ = 0; + /** Decimal digits: nv=0..9 */ + private static final int NTV_DECIMAL_START_ = 1; + /** Other digits: nv=0..9 */ + private static final int NTV_DIGIT_START_ = 11; + /** Small integers: nv=0..154 */ + private static final int NTV_NUMERIC_START_ = 21; + + private static final int ntvGetType(int ntv) { + return + (ntv==NTV_NONE_) ? NumericType.NONE : + (ntv> SCRIPT_HIGH_SHIFT) | + (scriptX & SCRIPT_LOW_MASK); + } + + /** + * Additional properties used in internal trie data + */ + /* + * Properties in vector word 1 + * Each bit encodes one binary property. + * The following constants represent the bit number, use 1< expectedTrieLength) { + throw new IOException("uprops.icu: not enough bytes for main trie"); + } + // skip padding after trie bytes + ICUBinary.skipBytes(bytes, expectedTrieLength - trieLength); + + // skip unused intervening data structures + ICUBinary.skipBytes(bytes, (additionalOffset - propertyOffset) * 4); + + if(m_additionalColumnsCount_ > 0) { + // reads the additional property block + m_additionalTrie_ = Trie2_16.createFromSerialized(bytes); + expectedTrieLength = (additionalVectorsOffset-additionalOffset)*4; + trieLength = m_additionalTrie_.getSerializedLength(); + if(trieLength > expectedTrieLength) { + throw new IOException("uprops.icu: not enough bytes for additional-properties trie"); + } + // skip padding after trie bytes + ICUBinary.skipBytes(bytes, expectedTrieLength - trieLength); + + // additional properties + int size = scriptExtensionsOffset - additionalVectorsOffset; + m_additionalVectors_ = new int[size]; + for (int i = 0; i < size; i ++) { + m_additionalVectors_[i] = bytes.getInt(); + } + } + + // Script_Extensions + int numChars = (reservedOffset7 - scriptExtensionsOffset) * 2; + if(numChars > 0) { + m_scriptExtensions_ = new char[numChars]; + for(int i = 0; i < numChars; ++i) { + m_scriptExtensions_[i] = bytes.getChar(); + } + } + } + + private static final class IsAcceptable implements ICUBinary.Authenticate { + // @Override when we switch to Java 6 + public boolean isDataVersionAcceptable(byte version[]) { + return version[0] == 7; + } + } + + private static final int DATA_FORMAT = 0x5550726F; // "UPro" + + public void upropsvec_addPropertyStarts(UnicodeSet set) { + /* add the start code point of each same-value range of the properties vectors trie */ + if(m_additionalColumnsCount_>0) { + /* if m_additionalColumnsCount_==0 then the properties vectors trie may not be there at all */ + Iterator trieIterator = m_additionalTrie_.iterator(); + Trie2.Range range; + while(trieIterator.hasNext() && !(range=trieIterator.next()).leadSurrogate) { + set.add(range.startCodePoint); + } + } + } + + // This static initializer block must be placed after + // other static member initialization + static { + try { + INSTANCE = new UCharacterProperty(); + } + catch (IOException e) { + throw new MissingResourceException(e.getMessage(),DATA_FILE_NAME_,""); + } + } + + + // Moved from UProperty.java + /** + * Enumerated property Bidi_Paired_Bracket_Type (new in Unicode 6.3). + * Used in UAX #9: Unicode Bidirectional Algorithm + * (http://www.unicode.org/reports/tr9/) + * Returns UCharacter.BidiPairedBracketType values. + * @stable ICU 52 + */ + public static final int BIDI_PAIRED_BRACKET_TYPE = 0x1015; + +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan$OffsetList.class b/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan$OffsetList.class new file mode 100644 index 00000000..34a5d596 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan$OffsetList.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan.class b/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan.class new file mode 100644 index 00000000..356707ec Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan.java b/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan.java new file mode 100644 index 00000000..331ab234 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/UnicodeSetStringSpan.java @@ -0,0 +1,1175 @@ +/* + * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ****************************************************************************** + * + * Copyright (C) 2009-2014, International Business Machines + * Corporation and others. All Rights Reserved. + * + ****************************************************************************** + */ + +package jdk.internal.icu.impl; + +import java.util.ArrayList; + +import jdk.internal.icu.text.UTF16; +import jdk.internal.icu.text.UnicodeSet; +import jdk.internal.icu.text.UnicodeSet.SpanCondition; +import jdk.internal.icu.util.OutputInt; + +/* + * Implement span() etc. for a set with strings. + * Avoid recursion because of its exponential complexity. + * Instead, try multiple paths at once and track them with an IndexList. + */ +public class UnicodeSetStringSpan { + + /* + * Which span() variant will be used? The object is either built for one variant and used once, + * or built for all and may be used many times. + */ + public static final int WITH_COUNT = 0x40; // spanAndCount() may be called + public static final int FWD = 0x20; + public static final int BACK = 0x10; + // public static final int UTF16 = 8; + public static final int CONTAINED = 2; + public static final int NOT_CONTAINED = 1; + + public static final int ALL = 0x7f; + + public static final int FWD_UTF16_CONTAINED = FWD | /* UTF16 | */ CONTAINED; + public static final int FWD_UTF16_NOT_CONTAINED = FWD | /* UTF16 | */NOT_CONTAINED; + public static final int BACK_UTF16_CONTAINED = BACK | /* UTF16 | */ CONTAINED; + public static final int BACK_UTF16_NOT_CONTAINED = BACK | /* UTF16 | */NOT_CONTAINED; + + /** + * Special spanLength short values. (since Java has not unsigned byte type) + * All code points in the string are contained in the parent set. + */ + static final short ALL_CP_CONTAINED = 0xff; + + /** The spanLength is >=0xfe. */ + static final short LONG_SPAN = ALL_CP_CONTAINED - 1; + + /** Set for span(). Same as parent but without strings. */ + private UnicodeSet spanSet; + + /** + * Set for span(not contained). + * Same as spanSet, plus characters that start or end strings. + */ + private UnicodeSet spanNotSet; + + /** The strings of the parent set. */ + private ArrayList strings; + + /** The lengths of span(), spanBack() etc. for each string. */ + private short[] spanLengths; + + /** Maximum lengths of relevant strings. */ + private int maxLength16; + + /** Are there strings that are not fully contained in the code point set? */ + private boolean someRelevant; + + /** Set up for all variants of span()? */ + private boolean all; + + /** Span helper */ + private OffsetList offsets; + + /** + * Constructs for all variants of span(), or only for any one variant. + * Initializes as little as possible, for single use. + */ + public UnicodeSetStringSpan(final UnicodeSet set, final ArrayList setStrings, int which) { + spanSet = new UnicodeSet(0, 0x10ffff); + // TODO: With Java 6, just take the parent set's strings as is, + // as a NavigableSet, rather than as an ArrayList copy of the set of strings. + // Then iterate via the first() and higher() methods. + // (We do not want to create multiple Iterator objects in each span().) + // See ICU ticket #7454. + strings = setStrings; + all = (which == ALL); + spanSet.retainAll(set); + if (0 != (which & NOT_CONTAINED)) { + // Default to the same sets. + // addToSpanNotSet() will create a separate set if necessary. + spanNotSet = spanSet; + } + offsets = new OffsetList(); + + // Determine if the strings even need to be taken into account at all for span() etc. + // If any string is relevant, then all strings need to be used for + // span(longest match) but only the relevant ones for span(while contained). + // TODO: Possible optimization: Distinguish CONTAINED vs. LONGEST_MATCH + // and do not store UTF-8 strings if !thisRelevant and CONTAINED. + // (Only store irrelevant UTF-8 strings for LONGEST_MATCH where they are relevant after all.) + // Also count the lengths of the UTF-8 versions of the strings for memory allocation. + int stringsLength = strings.size(); + + int i, spanLength; + someRelevant = false; + for (i = 0; i < stringsLength;) { + String string = strings.get(i); + int length16 = string.length(); + if (length16 == 0) { + // Remove the empty string. + strings.remove(i); + --stringsLength; + continue; + } + spanLength = spanSet.span(string, SpanCondition.CONTAINED); + if (spanLength < length16) { // Relevant string. + someRelevant = true; + } + if (/* (0 != (which & UTF16)) && */ length16 > maxLength16) { + maxLength16 = length16; + } + ++i; + } + if (!someRelevant && (which & WITH_COUNT) == 0) { + return; + } + + // Freeze after checking for the need to use strings at all because freezing + // a set takes some time and memory which are wasted if there are no relevant strings. + if (all) { + spanSet.freeze(); + } + + int spanBackLengthsOffset; + + // Allocate a block of meta data. + int allocSize; + if (all) { + // 2 sets of span lengths + allocSize = stringsLength * (2); + } else { + allocSize = stringsLength; // One set of span lengths. + } + spanLengths = new short[allocSize]; + + if (all) { + // Store span lengths for all span() variants. + spanBackLengthsOffset = stringsLength; + } else { + // Store span lengths for only one span() variant. + spanBackLengthsOffset = 0; + } + + // Set the meta data and spanNotSet and write the UTF-8 strings. + + for (i = 0; i < stringsLength; ++i) { + String string = strings.get(i); + int length16 = string.length(); + spanLength = spanSet.span(string, SpanCondition.CONTAINED); + if (spanLength < length16) { // Relevant string. + if (true /* 0 != (which & UTF16) */) { + if (0 != (which & CONTAINED)) { + if (0 != (which & FWD)) { + spanLengths[i] = makeSpanLengthByte(spanLength); + } + if (0 != (which & BACK)) { + spanLength = length16 + - spanSet.spanBack(string, length16, SpanCondition.CONTAINED); + spanLengths[spanBackLengthsOffset + i] = makeSpanLengthByte(spanLength); + } + } else /* not CONTAINED, not all, but NOT_CONTAINED */{ + spanLengths[i] = spanLengths[spanBackLengthsOffset + i] = 0; // Only store a relevant/irrelevant + // flag. + } + } + if (0 != (which & NOT_CONTAINED)) { + // Add string start and end code points to the spanNotSet so that + // a span(while not contained) stops before any string. + int c; + if (0 != (which & FWD)) { + c = string.codePointAt(0); + addToSpanNotSet(c); + } + if (0 != (which & BACK)) { + c = string.codePointBefore(length16); + addToSpanNotSet(c); + } + } + } else { // Irrelevant string. + if (all) { + spanLengths[i] = spanLengths[spanBackLengthsOffset + i] = ALL_CP_CONTAINED; + } else { + // All spanXYZLengths pointers contain the same address. + spanLengths[i] = ALL_CP_CONTAINED; + } + } + } + + // Finish. + if (all) { + spanNotSet.freeze(); + } + } + + /** + * Do the strings need to be checked in span() etc.? + * + * @return true if strings need to be checked (call span() here), + * false if not (use a BMPSet for best performance). + */ + public boolean needsStringSpanUTF16() { + return someRelevant; + } + + /** For fast UnicodeSet::contains(c). */ + public boolean contains(int c) { + return spanSet.contains(c); + } + + /** + * Adds a starting or ending string character to the spanNotSet + * so that a character span ends before any string. + */ + private void addToSpanNotSet(int c) { + if (spanNotSet == null || spanNotSet == spanSet) { + if (spanSet.contains(c)) { + return; // Nothing to do. + } + spanNotSet = spanSet.cloneAsThawed(); + } + spanNotSet.add(c); + } + + /* + * Note: In span() when spanLength==0 + * (after a string match, or at the beginning after an empty code point span) + * and in spanNot() and spanNotUTF8(), + * string matching could use a binary search because all string matches are done + * from the same start index. + * + * For UTF-8, this would require a comparison function that returns UTF-16 order. + * + * This optimization should not be necessary for normal UnicodeSets because most sets have no strings, and most sets + * with strings have very few very short strings. For cases with many strings, it might be better to use a different + * API and implementation with a DFA (state machine). + */ + + /* + * Algorithm for span(SpanCondition.CONTAINED) + * + * Theoretical algorithm: + * - Iterate through the string, and at each code point boundary: + * + If the code point there is in the set, then remember to continue after it. + * + If a set string matches at the current position, then remember to continue after it. + * + Either recursively span for each code point or string match, or recursively span + * for all but the shortest one and iteratively continue the span with the shortest local match. + * + Remember the longest recursive span (the farthest end point). + * + If there is no match at the current position, + * neither for the code point there nor for any set string, + * then stop and return the longest recursive span length. + * + * Optimized implementation: + * + * (We assume that most sets will have very few very short strings. + * A span using a string-less set is extremely fast.) + * + * Create and cache a spanSet which contains all of the single code points of the original set + * but none of its strings. + * + * - Start with spanLength=spanSet.span(SpanCondition.CONTAINED). + * - Loop: + * + Try to match each set string at the end of the spanLength. + * ~ Set strings that start with set-contained code points + * must be matched with a partial overlap + * because the recursive algorithm would have tried to match them at every position. + * ~ Set strings that entirely consist of set-contained code points + * are irrelevant for span(SpanCondition.CONTAINED) + * because the recursive algorithm would continue after them anyway and + * find the longest recursive match from their end. + * ~ Rather than recursing, note each end point of a set string match. + * + If no set string matched after spanSet.span(), + * then return with where the spanSet.span() ended. + * + If at least one set string matched after spanSet.span(), + * then pop the shortest string match end point and continue the loop, + * trying to match all set strings from there. + * + If at least one more set string matched after a previous string match, then test if the + * code point after the previous string match is also contained in the set. + * Continue the loop with the shortest end point of + * either this code point or a matching set string. + * + If no more set string matched after a previous string match, + * then try another spanLength=spanSet.span(SpanCondition.CONTAINED). + * Stop if spanLength==0, otherwise continue the loop. + * + * By noting each end point of a set string match, the function visits each string position at most once and + * finishes in linear time. + * + * The recursive algorithm may visit the same string position many times + * if multiple paths lead to it and finishes in exponential time. + */ + + /* + * Algorithm for span(SIMPLE) + * + * Theoretical algorithm: + * - Iterate through the string, and at each code point boundary: + * + If the code point there is in the set, then remember to continue after it. + * + If a set string matches at the current position, then remember to continue after it. + * + Continue from the farthest match position and ignore all others. + * + If there is no match at the current position, then stop and return the current position. + * + * Optimized implementation: + * + * (Same assumption and spanSet as above.) + * + * - Start with spanLength=spanSet.span(SpanCondition.CONTAINED). + * - Loop: + * + Try to match each set string at the end of the spanLength. + * ~ Set strings that start with set-contained code points + * must be matched with a partial overlap + * because the standard algorithm would have tried to match them earlier. + * ~ Set strings that entirely consist of set-contained code points + * must be matched with a full overlap because the longest-match algorithm + * would hide set string matches that end earlier. + * Such set strings need not be matched earlier inside the code point span + * because the standard algorithm would then have + * continued after the set string match anyway. + * ~ Remember the longest set string match (farthest end point) + * from the earliest starting point. + * + If no set string matched after spanSet.span(), + * then return with where the spanSet.span() ended. + * + If at least one set string matched, + * then continue the loop after the longest match from the earliest position. + * + If no more set string matched after a previous string match, + * then try another spanLength=spanSet.span(SpanCondition.CONTAINED). + * Stop if spanLength==0, otherwise continue the loop. + */ + /** + * Spans a string. + * + * @param s The string to be spanned + * @param start The start index that the span begins + * @param spanCondition The span condition + * @return the limit (exclusive end) of the span + */ + public int span(CharSequence s, int start, SpanCondition spanCondition) { + if (spanCondition == SpanCondition.NOT_CONTAINED) { + return spanNot(s, start, null); + } + int spanLimit = spanSet.span(s, start, SpanCondition.CONTAINED); + if (spanLimit == s.length()) { + return spanLimit; + } + return spanWithStrings(s, start, spanLimit, spanCondition); + } + + /** + * Synchronized method for complicated spans using the offsets. + * Avoids synchronization for simple cases. + * + * @param spanLimit = spanSet.span(s, start, CONTAINED) + */ + private synchronized int spanWithStrings(CharSequence s, int start, int spanLimit, + SpanCondition spanCondition) { + // Consider strings; they may overlap with the span. + int initSize = 0; + if (spanCondition == SpanCondition.CONTAINED) { + // Use offset list to try all possibilities. + initSize = maxLength16; + } + offsets.setMaxLength(initSize); + int length = s.length(); + int pos = spanLimit, rest = length - spanLimit; + int spanLength = spanLimit - start; + int i, stringsLength = strings.size(); + for (;;) { + if (spanCondition == SpanCondition.CONTAINED) { + for (i = 0; i < stringsLength; ++i) { + int overlap = spanLengths[i]; + if (overlap == ALL_CP_CONTAINED) { + continue; // Irrelevant string. + } + String string = strings.get(i); + + int length16 = string.length(); + + // Try to match this string at pos-overlap..pos. + if (overlap >= LONG_SPAN) { + overlap = length16; + // While contained: No point matching fully inside the code point span. + overlap = string.offsetByCodePoints(overlap, -1); // Length of the string minus the last code + // point. + } + if (overlap > spanLength) { + overlap = spanLength; + } + int inc = length16 - overlap; // Keep overlap+inc==length16. + for (;;) { + if (inc > rest) { + break; + } + // Try to match if the increment is not listed already. + if (!offsets.containsOffset(inc) && matches16CPB(s, pos - overlap, length, string, length16)) { + if (inc == rest) { + return length; // Reached the end of the string. + } + offsets.addOffset(inc); + } + if (overlap == 0) { + break; + } + --overlap; + ++inc; + } + } + } else /* SIMPLE */{ + int maxInc = 0, maxOverlap = 0; + for (i = 0; i < stringsLength; ++i) { + int overlap = spanLengths[i]; + // For longest match, we do need to try to match even an all-contained string + // to find the match from the earliest start. + + String string = strings.get(i); + + int length16 = string.length(); + + // Try to match this string at pos-overlap..pos. + if (overlap >= LONG_SPAN) { + overlap = length16; + // Longest match: Need to match fully inside the code point span + // to find the match from the earliest start. + } + if (overlap > spanLength) { + overlap = spanLength; + } + int inc = length16 - overlap; // Keep overlap+inc==length16. + for (;;) { + if (inc > rest || overlap < maxOverlap) { + break; + } + // Try to match if the string is longer or starts earlier. + if ((overlap > maxOverlap || /* redundant overlap==maxOverlap && */inc > maxInc) + && matches16CPB(s, pos - overlap, length, string, length16)) { + maxInc = inc; // Longest match from earliest start. + maxOverlap = overlap; + break; + } + --overlap; + ++inc; + } + } + + if (maxInc != 0 || maxOverlap != 0) { + // Longest-match algorithm, and there was a string match. + // Simply continue after it. + pos += maxInc; + rest -= maxInc; + if (rest == 0) { + return length; // Reached the end of the string. + } + spanLength = 0; // Match strings from after a string match. + continue; + } + } + // Finished trying to match all strings at pos. + + if (spanLength != 0 || pos == 0) { + // The position is after an unlimited code point span (spanLength!=0), + // not after a string match. + // The only position where spanLength==0 after a span is pos==0. + // Otherwise, an unlimited code point span is only tried again when no + // strings match, and if such a non-initial span fails we stop. + if (offsets.isEmpty()) { + return pos; // No strings matched after a span. + } + // Match strings from after the next string match. + } else { + // The position is after a string match (or a single code point). + if (offsets.isEmpty()) { + // No more strings matched after a previous string match. + // Try another code point span from after the last string match. + spanLimit = spanSet.span(s, pos, SpanCondition.CONTAINED); + spanLength = spanLimit - pos; + if (spanLength == rest || // Reached the end of the string, or + spanLength == 0 // neither strings nor span progressed. + ) { + return spanLimit; + } + pos += spanLength; + rest -= spanLength; + continue; // spanLength>0: Match strings from after a span. + } else { + // Try to match only one code point from after a string match if some + // string matched beyond it, so that we try all possible positions + // and don't overshoot. + spanLength = spanOne(spanSet, s, pos, rest); + if (spanLength > 0) { + if (spanLength == rest) { + return length; // Reached the end of the string. + } + // Match strings after this code point. + // There cannot be any increments below it because UnicodeSet strings + // contain multiple code points. + pos += spanLength; + rest -= spanLength; + offsets.shift(spanLength); + spanLength = 0; + continue; // Match strings from after a single code point. + } + // Match strings from after the next string match. + } + } + int minOffset = offsets.popMinimum(null); + pos += minOffset; + rest -= minOffset; + spanLength = 0; // Match strings from after a string match. + } + } + + /** + * Spans a string and counts the smallest number of set elements on any path across the span. + * + *

For proper counting, we cannot ignore strings that are fully contained in code point spans. + * + *

If the set does not have any fully-contained strings, then we could optimize this + * like span(), but such sets are likely rare, and this is at least still linear. + * + * @param s The string to be spanned + * @param start The start index that the span begins + * @param spanCondition The span condition + * @param outCount The count + * @return the limit (exclusive end) of the span + */ + public int spanAndCount(CharSequence s, int start, SpanCondition spanCondition, + OutputInt outCount) { + if (spanCondition == SpanCondition.NOT_CONTAINED) { + return spanNot(s, start, outCount); + } + // Consider strings; they may overlap with the span, + // and they may result in a smaller count that with just code points. + if (spanCondition == SpanCondition.CONTAINED) { + return spanContainedAndCount(s, start, outCount); + } + // SIMPLE (not synchronized, does not use offsets) + int stringsLength = strings.size(); + int length = s.length(); + int pos = start; + int rest = length - start; + int count = 0; + while (rest != 0) { + // Try to match the next code point. + int cpLength = spanOne(spanSet, s, pos, rest); + int maxInc = (cpLength > 0) ? cpLength : 0; + // Try to match all of the strings. + for (int i = 0; i < stringsLength; ++i) { + String string = strings.get(i); + int length16 = string.length(); + if (maxInc < length16 && length16 <= rest && + matches16CPB(s, pos, length, string, length16)) { + maxInc = length16; + } + } + // We are done if there is no match beyond pos. + if (maxInc == 0) { + outCount.value = count; + return pos; + } + // Continue from the longest match. + ++count; + pos += maxInc; + rest -= maxInc; + } + outCount.value = count; + return pos; + } + + private synchronized int spanContainedAndCount(CharSequence s, int start, OutputInt outCount) { + // Use offset list to try all possibilities. + offsets.setMaxLength(maxLength16); + int stringsLength = strings.size(); + int length = s.length(); + int pos = start; + int rest = length - start; + int count = 0; + while (rest != 0) { + // Try to match the next code point. + int cpLength = spanOne(spanSet, s, pos, rest); + if (cpLength > 0) { + offsets.addOffsetAndCount(cpLength, count + 1); + } + // Try to match all of the strings. + for (int i = 0; i < stringsLength; ++i) { + String string = strings.get(i); + int length16 = string.length(); + // Note: If the strings were sorted by length, then we could also + // avoid trying to match if there is already a match of the same length. + if (length16 <= rest && !offsets.hasCountAtOffset(length16, count + 1) && + matches16CPB(s, pos, length, string, length16)) { + offsets.addOffsetAndCount(length16, count + 1); + } + } + // We are done if there is no match beyond pos. + if (offsets.isEmpty()) { + outCount.value = count; + return pos; + } + // Continue from the nearest match. + int minOffset = offsets.popMinimum(outCount); + count = outCount.value; + pos += minOffset; + rest -= minOffset; + } + outCount.value = count; + return pos; + } + + /** + * Span a string backwards. + * + * @param s The string to be spanned + * @param spanCondition The span condition + * @return The string index which starts the span (i.e. inclusive). + */ + public synchronized int spanBack(CharSequence s, int length, SpanCondition spanCondition) { + if (spanCondition == SpanCondition.NOT_CONTAINED) { + return spanNotBack(s, length); + } + int pos = spanSet.spanBack(s, length, SpanCondition.CONTAINED); + if (pos == 0) { + return 0; + } + int spanLength = length - pos; + + // Consider strings; they may overlap with the span. + int initSize = 0; + if (spanCondition == SpanCondition.CONTAINED) { + // Use offset list to try all possibilities. + initSize = maxLength16; + } + offsets.setMaxLength(initSize); + int i, stringsLength = strings.size(); + int spanBackLengthsOffset = 0; + if (all) { + spanBackLengthsOffset = stringsLength; + } + for (;;) { + if (spanCondition == SpanCondition.CONTAINED) { + for (i = 0; i < stringsLength; ++i) { + int overlap = spanLengths[spanBackLengthsOffset + i]; + if (overlap == ALL_CP_CONTAINED) { + continue; // Irrelevant string. + } + String string = strings.get(i); + + int length16 = string.length(); + + // Try to match this string at pos-(length16-overlap)..pos-length16. + if (overlap >= LONG_SPAN) { + overlap = length16; + // While contained: No point matching fully inside the code point span. + int len1 = 0; + len1 = string.offsetByCodePoints(0, 1); + overlap -= len1; // Length of the string minus the first code point. + } + if (overlap > spanLength) { + overlap = spanLength; + } + int dec = length16 - overlap; // Keep dec+overlap==length16. + for (;;) { + if (dec > pos) { + break; + } + // Try to match if the decrement is not listed already. + if (!offsets.containsOffset(dec) && matches16CPB(s, pos - dec, length, string, length16)) { + if (dec == pos) { + return 0; // Reached the start of the string. + } + offsets.addOffset(dec); + } + if (overlap == 0) { + break; + } + --overlap; + ++dec; + } + } + } else /* SIMPLE */{ + int maxDec = 0, maxOverlap = 0; + for (i = 0; i < stringsLength; ++i) { + int overlap = spanLengths[spanBackLengthsOffset + i]; + // For longest match, we do need to try to match even an all-contained string + // to find the match from the latest end. + + String string = strings.get(i); + + int length16 = string.length(); + + // Try to match this string at pos-(length16-overlap)..pos-length16. + if (overlap >= LONG_SPAN) { + overlap = length16; + // Longest match: Need to match fully inside the code point span + // to find the match from the latest end. + } + if (overlap > spanLength) { + overlap = spanLength; + } + int dec = length16 - overlap; // Keep dec+overlap==length16. + for (;;) { + if (dec > pos || overlap < maxOverlap) { + break; + } + // Try to match if the string is longer or ends later. + if ((overlap > maxOverlap || /* redundant overlap==maxOverlap && */dec > maxDec) + && matches16CPB(s, pos - dec, length, string, length16)) { + maxDec = dec; // Longest match from latest end. + maxOverlap = overlap; + break; + } + --overlap; + ++dec; + } + } + + if (maxDec != 0 || maxOverlap != 0) { + // Longest-match algorithm, and there was a string match. + // Simply continue before it. + pos -= maxDec; + if (pos == 0) { + return 0; // Reached the start of the string. + } + spanLength = 0; // Match strings from before a string match. + continue; + } + } + // Finished trying to match all strings at pos. + + if (spanLength != 0 || pos == length) { + // The position is before an unlimited code point span (spanLength!=0), + // not before a string match. + // The only position where spanLength==0 before a span is pos==length. + // Otherwise, an unlimited code point span is only tried again when no + // strings match, and if such a non-initial span fails we stop. + if (offsets.isEmpty()) { + return pos; // No strings matched before a span. + } + // Match strings from before the next string match. + } else { + // The position is before a string match (or a single code point). + if (offsets.isEmpty()) { + // No more strings matched before a previous string match. + // Try another code point span from before the last string match. + int oldPos = pos; + pos = spanSet.spanBack(s, oldPos, SpanCondition.CONTAINED); + spanLength = oldPos - pos; + if (pos == 0 || // Reached the start of the string, or + spanLength == 0 // neither strings nor span progressed. + ) { + return pos; + } + continue; // spanLength>0: Match strings from before a span. + } else { + // Try to match only one code point from before a string match if some + // string matched beyond it, so that we try all possible positions + // and don't overshoot. + spanLength = spanOneBack(spanSet, s, pos); + if (spanLength > 0) { + if (spanLength == pos) { + return 0; // Reached the start of the string. + } + // Match strings before this code point. + // There cannot be any decrements below it because UnicodeSet strings + // contain multiple code points. + pos -= spanLength; + offsets.shift(spanLength); + spanLength = 0; + continue; // Match strings from before a single code point. + } + // Match strings from before the next string match. + } + } + pos -= offsets.popMinimum(null); + spanLength = 0; // Match strings from before a string match. + } + } + + /** + * Algorithm for spanNot()==span(SpanCondition.NOT_CONTAINED) + * + * Theoretical algorithm: + * - Iterate through the string, and at each code point boundary: + * + If the code point there is in the set, then return with the current position. + * + If a set string matches at the current position, then return with the current position. + * + * Optimized implementation: + * + * (Same assumption as for span() above.) + * + * Create and cache a spanNotSet which contains + * all of the single code points of the original set but none of its strings. + * For each set string add its initial code point to the spanNotSet. + * (Also add its final code point for spanNotBack().) + * + * - Loop: + * + Do spanLength=spanNotSet.span(SpanCondition.NOT_CONTAINED). + * + If the current code point is in the original set, then return the current position. + * + If any set string matches at the current position, then return the current position. + * + If there is no match at the current position, neither for the code point + * there nor for any set string, then skip this code point and continue the loop. + * This happens for set-string-initial code points that were added to spanNotSet + * when there is not actually a match for such a set string. + * + * @param s The string to be spanned + * @param start The start index that the span begins + * @param outCount If not null: Receives the number of code points across the span. + * @return the limit (exclusive end) of the span + */ + private int spanNot(CharSequence s, int start, OutputInt outCount) { + int length = s.length(); + int pos = start, rest = length - start; + int stringsLength = strings.size(); + int count = 0; + do { + // Span until we find a code point from the set, + // or a code point that starts or ends some string. + int spanLimit; + if (outCount == null) { + spanLimit = spanNotSet.span(s, pos, SpanCondition.NOT_CONTAINED); + } else { + spanLimit = spanNotSet.spanAndCount(s, pos, SpanCondition.NOT_CONTAINED, outCount); + outCount.value = count = count + outCount.value; + } + if (spanLimit == length) { + return length; // Reached the end of the string. + } + pos = spanLimit; + rest = length - spanLimit; + + // Check whether the current code point is in the original set, + // without the string starts and ends. + int cpLength = spanOne(spanSet, s, pos, rest); + if (cpLength > 0) { + return pos; // There is a set element at pos. + } + + // Try to match the strings at pos. + for (int i = 0; i < stringsLength; ++i) { + if (spanLengths[i] == ALL_CP_CONTAINED) { + continue; // Irrelevant string. + } + String string = strings.get(i); + + int length16 = string.length(); + if (length16 <= rest && matches16CPB(s, pos, length, string, length16)) { + return pos; // There is a set element at pos. + } + } + + // The span(while not contained) ended on a string start/end which is + // not in the original set. Skip this code point and continue. + // cpLength<0 + pos -= cpLength; + rest += cpLength; + ++count; + } while (rest != 0); + if (outCount != null) { + outCount.value = count; + } + return length; // Reached the end of the string. + } + + private int spanNotBack(CharSequence s, int length) { + int pos = length; + int i, stringsLength = strings.size(); + do { + // Span until we find a code point from the set, + // or a code point that starts or ends some string. + pos = spanNotSet.spanBack(s, pos, SpanCondition.NOT_CONTAINED); + if (pos == 0) { + return 0; // Reached the start of the string. + } + + // Check whether the current code point is in the original set, + // without the string starts and ends. + int cpLength = spanOneBack(spanSet, s, pos); + if (cpLength > 0) { + return pos; // There is a set element at pos. + } + + // Try to match the strings at pos. + for (i = 0; i < stringsLength; ++i) { + // Use spanLengths rather than a spanLengths pointer because + // it is easier and we only need to know whether the string is irrelevant + // which is the same in either array. + if (spanLengths[i] == ALL_CP_CONTAINED) { + continue; // Irrelevant string. + } + String string = strings.get(i); + + int length16 = string.length(); + if (length16 <= pos && matches16CPB(s, pos - length16, length, string, length16)) { + return pos; // There is a set element at pos. + } + } + + // The span(while not contained) ended on a string start/end which is + // not in the original set. Skip this code point and continue. + // cpLength<0 + pos += cpLength; + } while (pos != 0); + return 0; // Reached the start of the string. + } + + static short makeSpanLengthByte(int spanLength) { + // 0xfe==UnicodeSetStringSpan::LONG_SPAN + return spanLength < LONG_SPAN ? (short) spanLength : LONG_SPAN; + } + + // Compare strings without any argument checks. Requires length>0. + private static boolean matches16(CharSequence s, int start, final String t, int length) { + int end = start + length; + while (length-- > 0) { + if (s.charAt(--end) != t.charAt(length)) { + return false; + } + } + return true; + } + + /** + * Compare 16-bit Unicode strings (which may be malformed UTF-16) + * at code point boundaries. + * That is, each edge of a match must not be in the middle of a surrogate pair. + * @param s The string to match in. + * @param start The start index of s. + * @param limit The limit of the subsequence of s being spanned. + * @param t The substring to be matched in s. + * @param tlength The length of t. + */ + static boolean matches16CPB(CharSequence s, int start, int limit, final String t, int tlength) { + return matches16(s, start, t, tlength) + && !(0 < start && Character.isHighSurrogate(s.charAt(start - 1)) && + Character.isLowSurrogate(s.charAt(start))) + && !((start + tlength) < limit && Character.isHighSurrogate(s.charAt(start + tlength - 1)) && + Character.isLowSurrogate(s.charAt(start + tlength))); + } + + /** + * Does the set contain the next code point? + * If so, return its length; otherwise return its negative length. + */ + static int spanOne(final UnicodeSet set, CharSequence s, int start, int length) { + char c = s.charAt(start); + if (c >= 0xd800 && c <= 0xdbff && length >= 2) { + char c2 = s.charAt(start + 1); + if (UTF16.isTrailSurrogate(c2)) { + int supplementary = UCharacterProperty.getRawSupplementary(c, c2); + return set.contains(supplementary) ? 2 : -2; + } + } + return set.contains(c) ? 1 : -1; + } + + static int spanOneBack(final UnicodeSet set, CharSequence s, int length) { + char c = s.charAt(length - 1); + if (c >= 0xdc00 && c <= 0xdfff && length >= 2) { + char c2 = s.charAt(length - 2); + if (UTF16.isLeadSurrogate(c2)) { + int supplementary = UCharacterProperty.getRawSupplementary(c2, c); + return set.contains(supplementary) ? 2 : -2; + } + } + return set.contains(c) ? 1 : -1; + } + + /** + * Helper class for UnicodeSetStringSpan. + * + *

List of offsets from the current position from where to try matching + * a code point or a string. + * Stores offsets rather than indexes to simplify the code and use the same list + * for both increments (in span()) and decrements (in spanBack()). + * + *

Assumption: The maximum offset is limited, and the offsets that are stored at any one time + * are relatively dense, that is, + * there are normally no gaps of hundreds or thousands of offset values. + * + *

This class optionally also tracks the minimum non-negative count for each position, + * intended to count the smallest number of elements of any path leading to that position. + * + *

The implementation uses a circular buffer of count integers, + * each indicating whether the corresponding offset is in the list, + * and its path element count. + * This avoids inserting into a sorted list of offsets (or absolute indexes) + * and physically moving part of the list. + * + *

Note: In principle, the caller should setMaxLength() to + * the maximum of the max string length and U16_LENGTH/U8_LENGTH + * to account for "long" single code points. + * + *

Note: An earlier version did not track counts and stored only byte flags. + * With boolean flags, if maxLength were guaranteed to be no more than 32 or 64, + * the list could be stored as bit flags in a single integer. + * Rather than handling a circular buffer with a start list index, + * the integer would simply be shifted when lower offsets are removed. + * UnicodeSet does not have a limit on the lengths of strings. + */ + private static final class OffsetList { + private int[] list; + private int length; + private int start; + + public OffsetList() { + list = new int[16]; // default size + } + + public void setMaxLength(int maxLength) { + if (maxLength > list.length) { + list = new int[maxLength]; + } + clear(); + } + + public void clear() { + for (int i = list.length; i-- > 0;) { + list[i] = 0; + } + start = length = 0; + } + + public boolean isEmpty() { + return (length == 0); + } + + /** + * Reduces all stored offsets by delta, used when the current position moves by delta. + * There must not be any offsets lower than delta. + * If there is an offset equal to delta, it is removed. + * + * @param delta [1..maxLength] + */ + public void shift(int delta) { + int i = start + delta; + if (i >= list.length) { + i -= list.length; + } + if (list[i] != 0) { + list[i] = 0; + --length; + } + start = i; + } + + /** + * Adds an offset. The list must not contain it yet. + * @param offset [1..maxLength] + */ + public void addOffset(int offset) { + int i = start + offset; + if (i >= list.length) { + i -= list.length; + } + assert list[i] == 0; + list[i] = 1; + ++length; + } + + /** + * Adds an offset and updates its count. + * The list may already contain the offset. + * @param offset [1..maxLength] + */ + public void addOffsetAndCount(int offset, int count) { + assert count > 0; + int i = start + offset; + if (i >= list.length) { + i -= list.length; + } + if (list[i] == 0) { + list[i] = count; + ++length; + } else if (count < list[i]) { + list[i] = count; + } + } + + /** + * @param offset [1..maxLength] + */ + public boolean containsOffset(int offset) { + int i = start + offset; + if (i >= list.length) { + i -= list.length; + } + return list[i] != 0; + } + + /** + * @param offset [1..maxLength] + */ + public boolean hasCountAtOffset(int offset, int count) { + int i = start + offset; + if (i >= list.length) { + i -= list.length; + } + int oldCount = list[i]; + return oldCount != 0 && oldCount <= count; + } + + /** + * Finds the lowest stored offset from a non-empty list, removes it, + * and reduces all other offsets by this minimum. + * @return min=[1..maxLength] + */ + public int popMinimum(OutputInt outCount) { + // Look for the next offset in list[start+1..list.length-1]. + int i = start, result; + while (++i < list.length) { + int count = list[i]; + if (count != 0) { + list[i] = 0; + --length; + result = i - start; + start = i; + if (outCount != null) { outCount.value = count; } + return result; + } + } + // i==list.length + + // Wrap around and look for the next offset in list[0..start]. + // Since the list is not empty, there will be one. + result = list.length - start; + i = 0; + int count; + while ((count = list[i]) == 0) { + ++i; + } + list[i] = 0; + --length; + start = i; + if (outCount != null) { outCount.value = count; } + return result + i; + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/Utility.class b/tests/test_data/std/jdk/internal/icu/impl/Utility.class new file mode 100644 index 00000000..6fedabcc Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/Utility.class differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/Utility.java b/tests/test_data/std/jdk/internal/icu/impl/Utility.java new file mode 100644 index 00000000..62c6c5a7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/impl/Utility.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + ******************************************************************************* + * Copyright (C) 1996-2015, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ + +package jdk.internal.icu.impl; + +import jdk.internal.icu.lang.UCharacter; +import jdk.internal.icu.text.UTF16; + +import java.io.IOException; +import java.util.Locale; + +public final class Utility { + + /** + * Convert characters outside the range U+0020 to U+007F to + * Unicode escapes, and convert backslash to a double backslash. + */ + public static final String escape(String s) { + StringBuilder buf = new StringBuilder(); + for (int i=0; i= ' ' && c <= 0x007F) { + if (c == '\\') { + buf.append("\\\\"); // That is, "\\" + } else { + buf.append((char)c); + } + } else { + boolean four = c <= 0xFFFF; + buf.append(four ? "\\u" : "\\U"); + buf.append(hex(c, four ? 4 : 8)); + } + } + return buf.toString(); + } + + /* This map must be in ASCENDING ORDER OF THE ESCAPE CODE */ + private static final char[] UNESCAPE_MAP = { + /*" 0x22, 0x22 */ + /*' 0x27, 0x27 */ + /*? 0x3F, 0x3F */ + /*\ 0x5C, 0x5C */ + /*a*/ 0x61, 0x07, + /*b*/ 0x62, 0x08, + /*e*/ 0x65, 0x1b, + /*f*/ 0x66, 0x0c, + /*n*/ 0x6E, 0x0a, + /*r*/ 0x72, 0x0d, + /*t*/ 0x74, 0x09, + /*v*/ 0x76, 0x0b + }; + + /** + * Convert an escape to a 32-bit code point value. We attempt + * to parallel the icu4c unescapeAt() function. + * @param offset16 an array containing offset to the character + * after the backslash. Upon return offset16[0] will + * be updated to point after the escape sequence. + * @return character value from 0 to 10FFFF, or -1 on error. + */ + public static int unescapeAt(String s, int[] offset16) { + int c; + int result = 0; + int n = 0; + int minDig = 0; + int maxDig = 0; + int bitsPerDigit = 4; + int dig; + int i; + boolean braces = false; + + /* Check that offset is in range */ + int offset = offset16[0]; + int length = s.length(); + if (offset < 0 || offset >= length) { + return -1; + } + + /* Fetch first UChar after '\\' */ + c = Character.codePointAt(s, offset); + offset += UTF16.getCharCount(c); + + /* Convert hexadecimal and octal escapes */ + switch (c) { + case 'u': + minDig = maxDig = 4; + break; + case 'U': + minDig = maxDig = 8; + break; + case 'x': + minDig = 1; + if (offset < length && UTF16.charAt(s, offset) == 0x7B /*{*/) { + ++offset; + braces = true; + maxDig = 8; + } else { + maxDig = 2; + } + break; + default: + dig = UCharacter.digit(c, 8); + if (dig >= 0) { + minDig = 1; + maxDig = 3; + n = 1; /* Already have first octal digit */ + bitsPerDigit = 3; + result = dig; + } + break; + } + if (minDig != 0) { + while (offset < length && n < maxDig) { + c = UTF16.charAt(s, offset); + dig = UCharacter.digit(c, (bitsPerDigit == 3) ? 8 : 16); + if (dig < 0) { + break; + } + result = (result << bitsPerDigit) | dig; + offset += UTF16.getCharCount(c); + ++n; + } + if (n < minDig) { + return -1; + } + if (braces) { + if (c != 0x7D /*}*/) { + return -1; + } + ++offset; + } + if (result < 0 || result >= 0x110000) { + return -1; + } + // If an escape sequence specifies a lead surrogate, see + // if there is a trail surrogate after it, either as an + // escape or as a literal. If so, join them up into a + // supplementary. + if (offset < length && + UTF16.isLeadSurrogate((char) result)) { + int ahead = offset+1; + c = s.charAt(offset); // [sic] get 16-bit code unit + if (c == '\\' && ahead < length) { + int o[] = new int[] { ahead }; + c = unescapeAt(s, o); + ahead = o[0]; + } + if (UTF16.isTrailSurrogate((char) c)) { + offset = ahead; + result = UCharacterProperty.getRawSupplementary( + (char) result, (char) c); + } + } + offset16[0] = offset; + return result; + } + + /* Convert C-style escapes in table */ + for (i=0; i= 0x20 && c <= 0x7E); + } + + /** + * Escape unprintable characters using uxxxx notation + * for U+0000 to U+FFFF and Uxxxxxxxx for U+10000 and + * above. If the character is printable ASCII, then do nothing + * and return false. Otherwise, append the escaped notation and + * return true. + */ + public static boolean escapeUnprintable(T result, int c) { + try { + if (isUnprintable(c)) { + result.append('\\'); + if ((c & ~0xFFFF) != 0) { + result.append('U'); + result.append(DIGITS[0xF&(c>>28)]); + result.append(DIGITS[0xF&(c>>24)]); + result.append(DIGITS[0xF&(c>>20)]); + result.append(DIGITS[0xF&(c>>16)]); + } else { + result.append('u'); + } + result.append(DIGITS[0xF&(c>>12)]); + result.append(DIGITS[0xF&(c>>8)]); + result.append(DIGITS[0xF&(c>>4)]); + result.append(DIGITS[0xF&c]); + return true; + } + return false; + } catch (IOException e) { + throw new IllegalArgumentException(e); + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/nfc.nrm b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/nfc.nrm new file mode 100644 index 00000000..9228eebb Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/nfc.nrm differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/nfkc.nrm b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/nfkc.nrm new file mode 100644 index 00000000..001a2c5c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/nfkc.nrm differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/ubidi.icu b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/ubidi.icu new file mode 100644 index 00000000..d5c81de1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/ubidi.icu differ diff --git a/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/uprops.icu b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/uprops.icu new file mode 100644 index 00000000..2cfd5a78 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/impl/data/icudt74b/uprops.icu differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacter$HangulSyllableType.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacter$HangulSyllableType.class new file mode 100644 index 00000000..682f3d4d Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacter$HangulSyllableType.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacter$JoiningGroup.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacter$JoiningGroup.class new file mode 100644 index 00000000..7c1789d8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacter$JoiningGroup.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacter$NumericType.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacter$NumericType.class new file mode 100644 index 00000000..65cb340d Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacter$NumericType.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacter.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacter.class new file mode 100644 index 00000000..56bd9814 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacter.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacter.java b/tests/test_data/std/jdk/internal/icu/lang/UCharacter.java new file mode 100644 index 00000000..cd87db30 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/lang/UCharacter.java @@ -0,0 +1,545 @@ +/* + * Copyright (c) 2009, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +******************************************************************************* +* Copyright (C) 1996-2014, International Business Machines Corporation and +* others. All Rights Reserved. +******************************************************************************* +*/ + +package jdk.internal.icu.lang; + +import jdk.internal.icu.impl.UBiDiProps; +import jdk.internal.icu.impl.UCharacterProperty; +import jdk.internal.icu.text.Normalizer2; +import jdk.internal.icu.text.UTF16; +import jdk.internal.icu.util.VersionInfo; + +/** + *

The UCharacter class provides extensions to the + * + * java.lang.Character class. These extensions provide support for + * more Unicode properties and together with the UTF16 + * class, provide support for supplementary characters (those with code + * points above U+FFFF). + * Each ICU release supports the latest version of Unicode available at that time. + * + *

Code points are represented in these API using ints. While it would be + * more convenient in Java to have a separate primitive datatype for them, + * ints suffice in the meantime. + * + *

To use this class please add the jar file name icu4j.jar to the + * class path, since it contains data files which supply the information used + * by this file.
+ * E.g. In Windows
+ * set CLASSPATH=%CLASSPATH%;$JAR_FILE_PATH/ucharacter.jar.
+ * Otherwise, another method would be to copy the files uprops.dat and + * unames.icu from the icu4j source subdirectory + * $ICU4J_SRC/src/com.ibm.icu.impl.data to your class directory + * $ICU4J_CLASS/com.ibm.icu.impl.data. + * + *

Aside from the additions for UTF-16 support, and the updated Unicode + * properties, the main differences between UCharacter and Character are: + *

    + *
  • UCharacter is not designed to be a char wrapper and does not have + * APIs to which involves management of that single char.
    + * These include: + *
      + *
    • char charValue(), + *
    • int compareTo(java.lang.Character, java.lang.Character), etc. + *
    + *
  • UCharacter does not include Character APIs that are deprecated, nor + * does it include the Java-specific character information, such as + * boolean isJavaIdentifierPart(char ch). + *
  • Character maps characters 'A' - 'Z' and 'a' - 'z' to the numeric + * values '10' - '35'. UCharacter also does this in digit and + * getNumericValue, to adhere to the java semantics of these + * methods. New methods unicodeDigit, and + * getUnicodeNumericValue do not treat the above code points + * as having numeric values. This is a semantic change from ICU4J 1.3.1. + *
+ *

+ * Further detail on differences can be determined using the program + * + * com.ibm.icu.dev.test.lang.UCharacterCompare + *

+ *

+ * In addition to Java compatibility functions, which calculate derived properties, + * this API provides low-level access to the Unicode Character Database. + *

+ *

+ * Unicode assigns each code point (not just assigned character) values for + * many properties. + * Most of them are simple boolean flags, or constants from a small enumerated list. + * For some properties, values are strings or other relatively more complex types. + *

+ *

+ * For more information see + * "About the Unicode Character Database" + * (http://www.unicode.org/ucd/) + * and the ICU + * User Guide chapter on Properties + * (https://unicode-org.github.io/icu/userguide/strings/properties). + *

+ *

+ * There are also functions that provide easy migration from C/POSIX functions + * like isblank(). Their use is generally discouraged because the C/POSIX + * standards do not define their semantics beyond the ASCII range, which means + * that different implementations exhibit very different behavior. + * Instead, Unicode properties should be used directly. + *

+ *

+ * There are also only a few, broad C/POSIX character classes, and they tend + * to be used for conflicting purposes. For example, the "isalpha()" class + * is sometimes used to determine word boundaries, while a more sophisticated + * approach would at least distinguish initial letters from continuation + * characters (the latter including combining marks). + * (In ICU, BreakIterator is the most sophisticated API for word boundaries.) + * Another example: There is no "istitle()" class for titlecase characters. + *

+ *

+ * ICU 3.4 and later provides API access for all twelve C/POSIX character classes. + * ICU implements them according to the Standard Recommendations in + * Annex C: Compatibility Properties of UTS #18 Unicode Regular Expressions + * (http://www.unicode.org/reports/tr18/#Compatibility_Properties). + *

+ *

+ * API access for C/POSIX character classes is as follows: + *

{@code
+ * - alpha:     isUAlphabetic(c) or hasBinaryProperty(c, UProperty.ALPHABETIC)
+ * - lower:     isULowercase(c) or hasBinaryProperty(c, UProperty.LOWERCASE)
+ * - upper:     isUUppercase(c) or hasBinaryProperty(c, UProperty.UPPERCASE)
+ * - punct:     ((1<
+ * 

+ *

+ * The C/POSIX character classes are also available in UnicodeSet patterns, + * using patterns like [:graph:] or \p{graph}. + *

+ * + * There are several ICU (and Java) whitespace functions. + * Comparison:
    + *
  • isUWhiteSpace=UCHAR_WHITE_SPACE: Unicode White_Space property; + * most of general categories "Z" (separators) + most whitespace ISO controls + * (including no-break spaces, but excluding IS1..IS4 and ZWSP) + *
  • isWhitespace: Java isWhitespace; Z + whitespace ISO controls but excluding no-break spaces + *
  • isSpaceChar: just Z (including no-break spaces)
+ *

+ *

+ * This class is not subclassable. + *

+ * @author Syn Wee Quek + * @stable ICU 2.1 + * @see com.ibm.icu.lang.UCharacterEnums + */ + +public final class UCharacter +{ + + /** + * Joining Group constants. + * @see UProperty#JOINING_GROUP + * @stable ICU 2.4 + */ + public static interface JoiningGroup + { + /** + * @stable ICU 2.4 + */ + public static final int NO_JOINING_GROUP = 0; + } + + /** + * Numeric Type constants. + * @see UProperty#NUMERIC_TYPE + * @stable ICU 2.4 + */ + public static interface NumericType + { + /** + * @stable ICU 2.4 + */ + public static final int NONE = 0; + /** + * @stable ICU 2.4 + */ + public static final int DECIMAL = 1; + /** + * @stable ICU 2.4 + */ + public static final int DIGIT = 2; + /** + * @stable ICU 2.4 + */ + public static final int NUMERIC = 3; + /** + * @stable ICU 2.4 + */ + public static final int COUNT = 4; + } + + /** + * Hangul Syllable Type constants. + * + * @see UProperty#HANGUL_SYLLABLE_TYPE + * @stable ICU 2.6 + */ + public static interface HangulSyllableType + { + /** + * @stable ICU 2.6 + */ + public static final int NOT_APPLICABLE = 0; /*[NA]*/ /*See note !!*/ + /** + * @stable ICU 2.6 + */ + public static final int LEADING_JAMO = 1; /*[L]*/ + /** + * @stable ICU 2.6 + */ + public static final int VOWEL_JAMO = 2; /*[V]*/ + /** + * @stable ICU 2.6 + */ + public static final int TRAILING_JAMO = 3; /*[T]*/ + /** + * @stable ICU 2.6 + */ + public static final int LV_SYLLABLE = 4; /*[LV]*/ + /** + * @stable ICU 2.6 + */ + public static final int LVT_SYLLABLE = 5; /*[LVT]*/ + /** + * @stable ICU 2.6 + */ + public static final int COUNT = 6; + } + + // public data members ----------------------------------------------- + + /** + * The lowest Unicode code point value. + * @stable ICU 2.1 + */ + public static final int MIN_VALUE = UTF16.CODEPOINT_MIN_VALUE; + + /** + * The highest Unicode code point value (scalar value) according to the + * Unicode Standard. + * This is a 21-bit value (21 bits, rounded up).
+ * Up-to-date Unicode implementation of java.lang.Character.MAX_VALUE + * @stable ICU 2.1 + */ + public static final int MAX_VALUE = UTF16.CODEPOINT_MAX_VALUE; + + // public methods ---------------------------------------------------- + + /** + * Returns the numeric value of a decimal digit code point. + *
This method observes the semantics of + * java.lang.Character.digit(). Note that this + * will return positive values for code points for which isDigit + * returns false, just like java.lang.Character. + *
Semantic Change: In release 1.3.1 and + * prior, this did not treat the European letters as having a + * digit value, and also treated numeric letters and other numbers as + * digits. + * This has been changed to conform to the java semantics. + *
A code point is a valid digit if and only if: + *
    + *
  • ch is a decimal digit or one of the european letters, and + *
  • the value of ch is less than the specified radix. + *
+ * @param ch the code point to query + * @param radix the radix + * @return the numeric value represented by the code point in the + * specified radix, or -1 if the code point is not a decimal digit + * or if its value is too large for the radix + * @stable ICU 2.1 + */ + public static int digit(int ch, int radix) + { + if (2 <= radix && radix <= 36) { + int value = digit(ch); + if (value < 0) { + // ch is not a decimal digit, try latin letters + value = UCharacterProperty.getEuropeanDigit(ch); + } + return (value < radix) ? value : -1; + } else { + return -1; // invalid radix + } + } + + /** + * Returns the numeric value of a decimal digit code point. + *
This is a convenience overload of digit(int, int) + * that provides a decimal radix. + *
Semantic Change: In release 1.3.1 and prior, this + * treated numeric letters and other numbers as digits. This has + * been changed to conform to the java semantics. + * @param ch the code point to query + * @return the numeric value represented by the code point, + * or -1 if the code point is not a decimal digit or if its + * value is too large for a decimal radix + * @stable ICU 2.1 + */ + public static int digit(int ch) + { + return UCharacterProperty.INSTANCE.digit(ch); + } + + /** + * Returns a value indicating a code point's Unicode category. + * Up-to-date Unicode implementation of java.lang.Character.getType() + * except for the above mentioned code points that had their category + * changed.
+ * Return results are constants from the interface + * UCharacterCategory
+ * NOTE: the UCharacterCategory values are not compatible with + * those returned by java.lang.Character.getType. UCharacterCategory values + * match the ones used in ICU4C, while java.lang.Character type + * values, though similar, skip the value 17.

+ * @param ch code point whose type is to be determined + * @return category which is a value of UCharacterCategory + * @stable ICU 2.1 + */ + public static int getType(int ch) + { + return UCharacterProperty.INSTANCE.getType(ch); + } + + /** + * Returns the Bidirection property of a code point. + * For example, 0x0041 (letter A) has the LEFT_TO_RIGHT directional + * property.
+ * Result returned belongs to the interface + * UCharacterDirection + * @param ch the code point to be determined its direction + * @return direction constant from UCharacterDirection. + * @stable ICU 2.1 + */ + public static int getDirection(int ch) + { + return UBiDiProps.INSTANCE.getClass(ch); + } + + /** + * Maps the specified code point to a "mirror-image" code point. + * For code points with the "mirrored" property, implementations sometimes + * need a "poor man's" mapping to another code point such that the default + * glyph may serve as the mirror-image of the default glyph of the + * specified code point.
+ * This is useful for text conversion to and from codepages with visual + * order, and for displays without glyph selection capabilities. + * @param ch code point whose mirror is to be retrieved + * @return another code point that may serve as a mirror-image substitute, + * or ch itself if there is no such mapping or ch does not have the + * "mirrored" property + * @stable ICU 2.1 + */ + public static int getMirror(int ch) + { + return UBiDiProps.INSTANCE.getMirror(ch); + } + + /** + * Maps the specified character to its paired bracket character. + * For Bidi_Paired_Bracket_Type!=None, this is the same as getMirror(int). + * Otherwise c itself is returned. + * See http://www.unicode.org/reports/tr9/ + * + * @param c the code point to be mapped + * @return the paired bracket code point, + * or c itself if there is no such mapping + * (Bidi_Paired_Bracket_Type=None) + * + * @see UProperty#BIDI_PAIRED_BRACKET + * @see UProperty#BIDI_PAIRED_BRACKET_TYPE + * @see #getMirror(int) + * @stable ICU 52 + */ + public static int getBidiPairedBracket(int c) { + return UBiDiProps.INSTANCE.getPairedBracket(c); + } + + /** + * Returns the combining class of the argument codepoint + * @param ch code point whose combining is to be retrieved + * @return the combining class of the codepoint + * @stable ICU 2.1 + */ + public static int getCombiningClass(int ch) + { + return Normalizer2.getNFDInstance().getCombiningClass(ch); + } + + /** + * Returns the version of Unicode data used. + * @return the unicode version number used + * @stable ICU 2.1 + */ + public static VersionInfo getUnicodeVersion() + { + return UCharacterProperty.INSTANCE.m_unicodeVersion_; + } + + /** + * Returns a code point corresponding to the two UTF16 characters. + * @param lead the lead char + * @param trail the trail char + * @return code point if surrogate characters are valid. + * @exception IllegalArgumentException thrown when argument characters do + * not form a valid codepoint + * @stable ICU 2.1 + */ + public static int getCodePoint(char lead, char trail) + { + if (UTF16.isLeadSurrogate(lead) && UTF16.isTrailSurrogate(trail)) { + return UCharacterProperty.getRawSupplementary(lead, trail); + } + throw new IllegalArgumentException("Illegal surrogate characters"); + } + + /** + * Returns the "age" of the code point.

+ *

The "age" is the Unicode version when the code point was first + * designated (as a non-character or for Private Use) or assigned a + * character. + *

This can be useful to avoid emitting code points to receiving + * processes that do not accept newer characters.

+ *

The data is from the UCD file DerivedAge.txt.

+ * @param ch The code point. + * @return the Unicode version number + * @stable ICU 2.6 + */ + public static VersionInfo getAge(int ch) + { + if (ch < MIN_VALUE || ch > MAX_VALUE) { + throw new IllegalArgumentException("Codepoint out of bounds"); + } + return UCharacterProperty.INSTANCE.getAge(ch); + } + + /** + * Returns the property value for an Unicode property type of a code point. + * Also returns binary and mask property values.

+ *

Unicode, especially in version 3.2, defines many more properties than + * the original set in UnicodeData.txt.

+ *

The properties APIs are intended to reflect Unicode properties as + * defined in the Unicode Character Database (UCD) and Unicode Technical + * Reports (UTR). For details about the properties see + * http://www.unicode.org/.

+ *

For names of Unicode properties see the UCD file PropertyAliases.txt. + *

+ *
+     * Sample usage:
+     * int ea = UCharacter.getIntPropertyValue(c, UProperty.EAST_ASIAN_WIDTH);
+     * int ideo = UCharacter.getIntPropertyValue(c, UProperty.IDEOGRAPHIC);
+     * boolean b = (ideo == 1) ? true : false;
+     * 
+ * @param ch code point to test. + * @param type UProperty selector constant, identifies which binary + * property to check. Must be + * UProperty.BINARY_START <= type < UProperty.BINARY_LIMIT or + * UProperty.INT_START <= type < UProperty.INT_LIMIT or + * UProperty.MASK_START <= type < UProperty.MASK_LIMIT. + * @return numeric value that is directly the property value or, + * for enumerated properties, corresponds to the numeric value of + * the enumerated constant of the respective property value + * enumeration type (cast to enum type if necessary). + * Returns 0 or 1 (for false / true) for binary Unicode properties. + * Returns a bit-mask for mask properties. + * Returns 0 if 'type' is out of bounds or if the Unicode version + * does not have data for the property at all, or not for this code + * point. + * @see UProperty + * @see #hasBinaryProperty + * @see #getIntPropertyMinValue + * @see #getIntPropertyMaxValue + * @see #getUnicodeVersion + * @stable ICU 2.4 + */ + // for BiDiBase.java + public static int getIntPropertyValue(int ch, int type) { + return UCharacterProperty.INSTANCE.getIntPropertyValue(ch, type); + } + + // private constructor ----------------------------------------------- + + /** + * Private constructor to prevent instantiation + */ + private UCharacter() { } + + /* + * Copied from UCharacterEnums.java + */ + + /** + * Character type Mn + * @stable ICU 2.1 + */ + public static final byte NON_SPACING_MARK = 6; + /** + * Character type Me + * @stable ICU 2.1 + */ + public static final byte ENCLOSING_MARK = 7; + /** + * Character type Mc + * @stable ICU 2.1 + */ + public static final byte COMBINING_SPACING_MARK = 8; + /** + * Character type count + * @stable ICU 2.1 + */ + public static final byte CHAR_CATEGORY_COUNT = 30; + + /** + * Directional type R + * @stable ICU 2.1 + */ + public static final int RIGHT_TO_LEFT = 1; + /** + * Directional type AL + * @stable ICU 2.1 + */ + public static final int RIGHT_TO_LEFT_ARABIC = 13; +} diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacterDirection.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacterDirection.class new file mode 100644 index 00000000..38101b41 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacterDirection.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacterDirection.java b/tests/test_data/std/jdk/internal/icu/lang/UCharacterDirection.java new file mode 100644 index 00000000..ef995893 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/lang/UCharacterDirection.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* +/** +******************************************************************************* +* Copyright (C) 1996-2004, International Business Machines Corporation and * +* others. All Rights Reserved. * +******************************************************************************* +*/ +// CHANGELOG +// 2005-05-19 Edward Wang +// - copy this file from icu4jsrc_3_2/src/com/ibm/icu/lang/UCharacterDirection.java +// - move from package com.ibm.icu.lang to package sun.net.idn +// + +package jdk.internal.icu.lang; + +/** + * Enumerated Unicode character linguistic direction constants. + * Used as return results from UCharacter + *

+ * This class is not subclassable + *

+ * @author Syn Wee Quek + * @stable ICU 2.1 + */ + +@SuppressWarnings("deprecation") +public final class UCharacterDirection implements UCharacterEnums.ECharacterDirection { + + // private constructor ========================================= + // CLOVER:OFF + /** + * Private constructor to prevent initialization + */ + private UCharacterDirection() + { + } + // CLOVER:ON + + /** + * Gets the name of the argument direction + * @param dir direction type to retrieve name + * @return directional name + * @stable ICU 2.1 + */ + public static String toString(int dir) { + switch(dir) + { + case LEFT_TO_RIGHT : + return "Left-to-Right"; + case RIGHT_TO_LEFT : + return "Right-to-Left"; + case EUROPEAN_NUMBER : + return "European Number"; + case EUROPEAN_NUMBER_SEPARATOR : + return "European Number Separator"; + case EUROPEAN_NUMBER_TERMINATOR : + return "European Number Terminator"; + case ARABIC_NUMBER : + return "Arabic Number"; + case COMMON_NUMBER_SEPARATOR : + return "Common Number Separator"; + case BLOCK_SEPARATOR : + return "Paragraph Separator"; + case SEGMENT_SEPARATOR : + return "Segment Separator"; + case WHITE_SPACE_NEUTRAL : + return "Whitespace"; + case OTHER_NEUTRAL : + return "Other Neutrals"; + case LEFT_TO_RIGHT_EMBEDDING : + return "Left-to-Right Embedding"; + case LEFT_TO_RIGHT_OVERRIDE : + return "Left-to-Right Override"; + case RIGHT_TO_LEFT_ARABIC : + return "Right-to-Left Arabic"; + case RIGHT_TO_LEFT_EMBEDDING : + return "Right-to-Left Embedding"; + case RIGHT_TO_LEFT_OVERRIDE : + return "Right-to-Left Override"; + case POP_DIRECTIONAL_FORMAT : + return "Pop Directional Format"; + case DIR_NON_SPACING_MARK : + return "Non-Spacing Mark"; + case BOUNDARY_NEUTRAL : + return "Boundary Neutral"; + } + return "Unassigned"; + } +} diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums$ECharacterCategory.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums$ECharacterCategory.class new file mode 100644 index 00000000..97b1e366 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums$ECharacterCategory.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums$ECharacterDirection.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums$ECharacterDirection.class new file mode 100644 index 00000000..0da72b14 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums$ECharacterDirection.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums.class b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums.class new file mode 100644 index 00000000..df83468e Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums.class differ diff --git a/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums.java b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums.java new file mode 100644 index 00000000..38f8684e --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/lang/UCharacterEnums.java @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* +/** + ******************************************************************************* + * Copyright (C) 2004, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ +// CHANGELOG +// 2005-05-19 Edward Wang +// - copy this file from icu4jsrc_3_2/src/com/ibm/icu/lang/UCharacterEnums.java +// - move from package com.ibm.icu.lang to package sun.net.idn +// +// 2011-09-06 Kurchi Subhra Hazra +// - Added @Deprecated tag to the following: +// - class UCharacterEnums +// - interfaces ECharacterCategory, ECharacterDirection +// - fields INITIAL_QUOTE_PUNCTUATION, FINAL_QUOTE_PUNCTUATION, +// DIRECTIONALITY_LEFT_TO_RIGHT, DIRECTIONALITY_RIGHT_TO_LEFT, +// DIRECTIONALITY_EUROPEAN_NUMBER, DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR +// DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR, DIRECTIONALITY_ARABIC_NUMBER, +// DIRECTIONALITY_COMMON_NUMBER_SEPARATOR, DIRECTIONALITY_PARAGRAPH_SEPARATOR, +// DIRECTIONALITY_SEGMENT_SEPARATOR, DIRECTIONALITY_WHITESPACE, +// DIRECTIONALITY_OTHER_NEUTRALS, DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING, +// DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE, DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC, +// DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING, DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE, +// DIRECTIONALITY_POP_DIRECTIONAL_FORMAT, DIRECTIONALITY_NON_SPACING_MARK, +// DIRECTIONALITY_BOUNDARY_NEUTRAL, DIRECTIONALITY_UNDEFINED +// + +package jdk.internal.icu.lang; + +/** + * A container for the different 'enumerated types' used by UCharacter. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + +@Deprecated +class UCharacterEnums { + + /** This is just a namespace, it is not instantiable. */ + private UCharacterEnums() {}; + + /** + * 'Enum' for the CharacterCategory constants. These constants are + * compatible in name but not in value with those defined in + * java.lang.Character. + * @see UCharacterCategory + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static interface ECharacterCategory { + /** + * Unassigned character type + * @stable ICU 2.1 + */ + public static final int UNASSIGNED = 0; + + /** + * Character type Cn + * Not Assigned (no characters in [UnicodeData.txt] have this property) + * @stable ICU 2.6 + */ + public static final int GENERAL_OTHER_TYPES = 0; + + /** + * Character type Lu + * @stable ICU 2.1 + */ + public static final int UPPERCASE_LETTER = 1; + + /** + * Character type Ll + * @stable ICU 2.1 + */ + public static final int LOWERCASE_LETTER = 2; + + /** + * Character type Lt + * @stable ICU 2.1 + */ + + public static final int TITLECASE_LETTER = 3; + + /** + * Character type Lm + * @stable ICU 2.1 + */ + public static final int MODIFIER_LETTER = 4; + + /** + * Character type Lo + * @stable ICU 2.1 + */ + public static final int OTHER_LETTER = 5; + + /** + * Character type Mn + * @stable ICU 2.1 + */ + public static final int NON_SPACING_MARK = 6; + + /** + * Character type Me + * @stable ICU 2.1 + */ + public static final int ENCLOSING_MARK = 7; + + /** + * Character type Mc + * @stable ICU 2.1 + */ + public static final int COMBINING_SPACING_MARK = 8; + + /** + * Character type Nd + * @stable ICU 2.1 + */ + public static final int DECIMAL_DIGIT_NUMBER = 9; + + /** + * Character type Nl + * @stable ICU 2.1 + */ + public static final int LETTER_NUMBER = 10; + + /** + * Character type No + * @stable ICU 2.1 + */ + public static final int OTHER_NUMBER = 11; + + /** + * Character type Zs + * @stable ICU 2.1 + */ + public static final int SPACE_SEPARATOR = 12; + + /** + * Character type Zl + * @stable ICU 2.1 + */ + public static final int LINE_SEPARATOR = 13; + + /** + * Character type Zp + * @stable ICU 2.1 + */ + public static final int PARAGRAPH_SEPARATOR = 14; + + /** + * Character type Cc + * @stable ICU 2.1 + */ + public static final int CONTROL = 15; + + /** + * Character type Cf + * @stable ICU 2.1 + */ + public static final int FORMAT = 16; + + /** + * Character type Co + * @stable ICU 2.1 + */ + public static final int PRIVATE_USE = 17; + + /** + * Character type Cs + * @stable ICU 2.1 + */ + public static final int SURROGATE = 18; + + /** + * Character type Pd + * @stable ICU 2.1 + */ + public static final int DASH_PUNCTUATION = 19; + + /** + * Character type Ps + * @stable ICU 2.1 + */ + public static final int START_PUNCTUATION = 20; + + /** + * Character type Pe + * @stable ICU 2.1 + */ + public static final int END_PUNCTUATION = 21; + + /** + * Character type Pc + * @stable ICU 2.1 + */ + public static final int CONNECTOR_PUNCTUATION = 22; + + /** + * Character type Po + * @stable ICU 2.1 + */ + public static final int OTHER_PUNCTUATION = 23; + + /** + * Character type Sm + * @stable ICU 2.1 + */ + public static final int MATH_SYMBOL = 24; + + /** + * Character type Sc + * @stable ICU 2.1 + */ + public static final int CURRENCY_SYMBOL = 25; + + /** + * Character type Sk + * @stable ICU 2.1 + */ + public static final int MODIFIER_SYMBOL = 26; + + /** + * Character type So + * @stable ICU 2.1 + */ + public static final int OTHER_SYMBOL = 27; + + /** + * Character type Pi + * @see #INITIAL_QUOTE_PUNCTUATION + * @stable ICU 2.1 + */ + public static final int INITIAL_PUNCTUATION = 28; + + /** + * Character type Pi + * This name is compatible with java.lang.Character's name for this type. + * @see #INITIAL_PUNCTUATION + * @draft ICU 2.8 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final int INITIAL_QUOTE_PUNCTUATION = 28; + + /** + * Character type Pf + * @see #FINAL_QUOTE_PUNCTUATION + * @stable ICU 2.1 + */ + public static final int FINAL_PUNCTUATION = 29; + + /** + * Character type Pf + * This name is compatible with java.lang.Character's name for this type. + * @see #FINAL_PUNCTUATION + * @draft ICU 2.8 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final int FINAL_QUOTE_PUNCTUATION = 29; + + /** + * Character type count + * @stable ICU 2.1 + */ + public static final int CHAR_CATEGORY_COUNT = 30; + } + + /** + * 'Enum' for the CharacterDirection constants. There are two sets + * of names, those used in ICU, and those used in the JDK. The + * JDK constants are compatible in name but not in value + * with those defined in java.lang.Character. + * @see UCharacterDirection + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + + @Deprecated + public static interface ECharacterDirection { + /** + * Directional type L + * @stable ICU 2.1 + */ + public static final int LEFT_TO_RIGHT = 0; + + /** + * JDK-compatible synonum for LEFT_TO_RIGHT. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_LEFT_TO_RIGHT = (byte)LEFT_TO_RIGHT; + + /** + * Directional type R + * @stable ICU 2.1 + */ + public static final int RIGHT_TO_LEFT = 1; + + /** + * JDK-compatible synonum for RIGHT_TO_LEFT. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_RIGHT_TO_LEFT = (byte)RIGHT_TO_LEFT; + + /** + * Directional type EN + * @stable ICU 2.1 + */ + public static final int EUROPEAN_NUMBER = 2; + + /** + * JDK-compatible synonum for EUROPEAN_NUMBER. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_EUROPEAN_NUMBER = (byte)EUROPEAN_NUMBER; + + /** + * Directional type ES + * @stable ICU 2.1 + */ + public static final int EUROPEAN_NUMBER_SEPARATOR = 3; + + /** + * JDK-compatible synonum for EUROPEAN_NUMBER_SEPARATOR. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR = (byte)EUROPEAN_NUMBER_SEPARATOR; + + /** + * Directional type ET + * @stable ICU 2.1 + */ + public static final int EUROPEAN_NUMBER_TERMINATOR = 4; + + /** + * JDK-compatible synonum for EUROPEAN_NUMBER_TERMINATOR. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR = (byte)EUROPEAN_NUMBER_TERMINATOR; + + /** + * Directional type AN + * @stable ICU 2.1 + */ + public static final int ARABIC_NUMBER = 5; + + /** + * JDK-compatible synonum for ARABIC_NUMBER. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_ARABIC_NUMBER = (byte)ARABIC_NUMBER; + + /** + * Directional type CS + * @stable ICU 2.1 + */ + public static final int COMMON_NUMBER_SEPARATOR = 6; + + /** + * JDK-compatible synonum for COMMON_NUMBER_SEPARATOR. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_COMMON_NUMBER_SEPARATOR = (byte)COMMON_NUMBER_SEPARATOR; + + /** + * Directional type B + * @stable ICU 2.1 + */ + public static final int BLOCK_SEPARATOR = 7; + + /** + * JDK-compatible synonum for BLOCK_SEPARATOR. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_PARAGRAPH_SEPARATOR = (byte)BLOCK_SEPARATOR; + + /** + * Directional type S + * @stable ICU 2.1 + */ + public static final int SEGMENT_SEPARATOR = 8; + + /** + * JDK-compatible synonum for SEGMENT_SEPARATOR. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_SEGMENT_SEPARATOR = (byte)SEGMENT_SEPARATOR; + + /** + * Directional type WS + * @stable ICU 2.1 + */ + public static final int WHITE_SPACE_NEUTRAL = 9; + + /** + * JDK-compatible synonum for WHITE_SPACE_NEUTRAL. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_WHITESPACE = (byte)WHITE_SPACE_NEUTRAL; + + /** + * Directional type ON + * @stable ICU 2.1 + */ + public static final int OTHER_NEUTRAL = 10; + + /** + * JDK-compatible synonum for OTHER_NEUTRAL. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_OTHER_NEUTRALS = (byte)OTHER_NEUTRAL; + + /** + * Directional type LRE + * @stable ICU 2.1 + */ + public static final int LEFT_TO_RIGHT_EMBEDDING = 11; + + /** + * JDK-compatible synonum for LEFT_TO_RIGHT_EMBEDDING. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING = (byte)LEFT_TO_RIGHT_EMBEDDING; + + /** + * Directional type LRO + * @stable ICU 2.1 + */ + public static final int LEFT_TO_RIGHT_OVERRIDE = 12; + + /** + * JDK-compatible synonum for LEFT_TO_RIGHT_OVERRIDE. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE = (byte)LEFT_TO_RIGHT_OVERRIDE; + + /** + * Directional type AL + * @stable ICU 2.1 + */ + public static final int RIGHT_TO_LEFT_ARABIC = 13; + + /** + * JDK-compatible synonum for RIGHT_TO_LEFT_ARABIC. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC = (byte)RIGHT_TO_LEFT_ARABIC; + + /** + * Directional type RLE + * @stable ICU 2.1 + */ + public static final int RIGHT_TO_LEFT_EMBEDDING = 14; + + /** + * JDK-compatible synonum for RIGHT_TO_LEFT_EMBEDDING. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING = (byte)RIGHT_TO_LEFT_EMBEDDING; + + /** + * Directional type RLO + * @stable ICU 2.1 + */ + public static final int RIGHT_TO_LEFT_OVERRIDE = 15; + + /** + * JDK-compatible synonum for RIGHT_TO_LEFT_OVERRIDE. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE = (byte)RIGHT_TO_LEFT_OVERRIDE; + + /** + * Directional type PDF + * @stable ICU 2.1 + */ + public static final int POP_DIRECTIONAL_FORMAT = 16; + + /** + * JDK-compatible synonum for POP_DIRECTIONAL_FORMAT. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_POP_DIRECTIONAL_FORMAT = (byte)POP_DIRECTIONAL_FORMAT; + + /** + * Directional type NSM + * @stable ICU 2.1 + */ + public static final int DIR_NON_SPACING_MARK = 17; + + /** + * JDK-compatible synonum for DIR_NON_SPACING_MARK. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_NON_SPACING_MARK = (byte)DIR_NON_SPACING_MARK; + + /** + * Directional type BN + * @stable ICU 2.1 + */ + public static final int BOUNDARY_NEUTRAL = 18; + + /** + * JDK-compatible synonum for BOUNDARY_NEUTRAL. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_BOUNDARY_NEUTRAL = (byte)BOUNDARY_NEUTRAL; + + /** + * Number of directional types + * @stable ICU 2.1 + */ + public static final int CHAR_DIRECTION_COUNT = 19; + + /** + * Undefined bidirectional character type. Undefined char + * values have undefined directionality in the Unicode specification. + * @draft ICU 3.0 + * @deprecated This is a draft API and might change in a future release of ICU. + */ + @Deprecated + public static final byte DIRECTIONALITY_UNDEFINED = -1; + } +} diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$BidiPairedBracketType.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$BidiPairedBracketType.class new file mode 100644 index 00000000..4b772fb5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$BidiPairedBracketType.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$BracketData.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$BracketData.class new file mode 100644 index 00000000..d81559dd Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$BracketData.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$ImpTabPair.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$ImpTabPair.class new file mode 100644 index 00000000..9fa76905 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$ImpTabPair.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$InsertPoints.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$InsertPoints.class new file mode 100644 index 00000000..fa15f367 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$InsertPoints.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$IsoRun.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$IsoRun.class new file mode 100644 index 00000000..be07ef43 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$IsoRun.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$Isolate.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$Isolate.class new file mode 100644 index 00000000..241c8ed7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$Isolate.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$LevState.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$LevState.class new file mode 100644 index 00000000..03ebfac1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$LevState.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$NumericShapings.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$NumericShapings.class new file mode 100644 index 00000000..89acd543 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$NumericShapings.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$Opening.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$Opening.class new file mode 100644 index 00000000..5e8bc303 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$Opening.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$Point.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$Point.class new file mode 100644 index 00000000..e8c52f6a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$Point.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$TextAttributeConstants$1.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$TextAttributeConstants$1.class new file mode 100644 index 00000000..f6bd2e9e Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$TextAttributeConstants$1.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase$TextAttributeConstants.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase$TextAttributeConstants.class new file mode 100644 index 00000000..b9d4e401 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase$TextAttributeConstants.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase.class b/tests/test_data/std/jdk/internal/icu/text/BidiBase.class new file mode 100644 index 00000000..cf458e4c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiBase.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiBase.java b/tests/test_data/std/jdk/internal/icu/text/BidiBase.java new file mode 100644 index 00000000..bfeb0b7e --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/BidiBase.java @@ -0,0 +1,4783 @@ +/* + * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +******************************************************************************* +* Copyright (C) 2001-2014, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +*/ + +/* FOOD FOR THOUGHT: currently the reordering modes are a mixture of + * algorithm for direct BiDi, algorithm for inverse Bidi and the bizarre + * concept of RUNS_ONLY which is a double operation. + * It could be advantageous to divide this into 3 concepts: + * a) Operation: direct / inverse / RUNS_ONLY + * b) Direct algorithm: default / NUMBERS_SPECIAL / GROUP_NUMBERS_WITH_L + * c) Inverse algorithm: default / INVERSE_LIKE_DIRECT / NUMBERS_SPECIAL + * This would allow combinations not possible today like RUNS_ONLY with + * NUMBERS_SPECIAL. + * Also allow to set INSERT_MARKS for the direct step of RUNS_ONLY and + * REMOVE_CONTROLS for the inverse step. + * Not all combinations would be supported, and probably not all do make sense. + * This would need to document which ones are supported and what are the + * fallbacks for unsupported combinations. + */ + +package jdk.internal.icu.text; + +import java.lang.reflect.Array; +import java.text.AttributedCharacterIterator; +import java.text.Bidi; +import java.util.Arrays; +import jdk.internal.access.JavaAWTFontAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.icu.lang.UCharacter; +import jdk.internal.icu.impl.UBiDiProps; + +/** + * + *

Bidi algorithm for ICU

+ * + * This is an implementation of the Unicode Bidirectional Algorithm. The + * algorithm is defined in the + * Unicode Standard Annex #9: + * Unicode Bidirectional Algorithm. + *

+ * + * Note: Libraries that perform a bidirectional algorithm and reorder strings + * accordingly are sometimes called "Storage Layout Engines". ICU's Bidi and + * shaping (ArabicShaping) classes can be used at the core of such "Storage + * Layout Engines". + * + *

General remarks about the API:

+ * + * The "limit" of a sequence of characters is the position just after + * their last character, i.e., one more than that position. + *

+ * + * Some of the API methods provide access to "runs". Such a + * "run" is defined as a sequence of characters that are at the same + * embedding level after performing the Bidi algorithm. + * + *

Basic concept: paragraph

+ * A piece of text can be divided into several paragraphs by characters + * with the Bidi class Block Separator. For handling of + * paragraphs, see: + *
    + *
  • {@link #countParagraphs} + *
  • {@link #getParaLevel} + *
  • {@link #getParagraph} + *
  • {@link #getParagraphByIndex} + *
+ * + *

Basic concept: text direction

+ * The direction of a piece of text may be: + *
    + *
  • {@link #LTR} + *
  • {@link #RTL} + *
  • {@link #MIXED} + *
  • {@link #NEUTRAL} + *
+ * + *

Basic concept: levels

+ * + * Levels in this API represent embedding levels according to the Unicode + * Bidirectional Algorithm. + * Their low-order bit (even/odd value) indicates the visual direction.

+ * + * Levels can be abstract values when used for the + * paraLevel and embeddingLevels + * arguments of setPara(); there: + *

    + *
  • the high-order bit of an embeddingLevels[] + * value indicates whether the using application is + * specifying the level of a character to override whatever the + * Bidi implementation would resolve it to.
  • + *
  • paraLevel can be set to the + * pseudo-level values LEVEL_DEFAULT_LTR + * and LEVEL_DEFAULT_RTL.
  • + *
+ * + *

The related constants are not real, valid level values. + * DEFAULT_XXX can be used to specify + * a default for the paragraph level for + * when the setPara() method + * shall determine it but there is no + * strongly typed character in the input.

+ * + * Note that the value for LEVEL_DEFAULT_LTR is even + * and the one for LEVEL_DEFAULT_RTL is odd, + * just like with normal LTR and RTL level values - + * these special values are designed that way. Also, the implementation + * assumes that MAX_EXPLICIT_LEVEL is odd. + * + *

See Also: + *

    + *
  • {@link #LEVEL_DEFAULT_LTR} + *
  • {@link #LEVEL_DEFAULT_RTL} + *
  • {@link #LEVEL_OVERRIDE} + *
  • {@link #MAX_EXPLICIT_LEVEL} + *
  • {@link #setPara} + *
+ * + *

Basic concept: Reordering Mode

+ * Reordering mode values indicate which variant of the Bidi algorithm to + * use. + * + *

See Also: + *

    + *
  • {@link #setReorderingMode} + *
  • {@link #REORDER_DEFAULT} + *
  • {@link #REORDER_NUMBERS_SPECIAL} + *
  • {@link #REORDER_GROUP_NUMBERS_WITH_R} + *
  • {@link #REORDER_RUNS_ONLY} + *
  • {@link #REORDER_INVERSE_NUMBERS_AS_L} + *
  • {@link #REORDER_INVERSE_LIKE_DIRECT} + *
  • {@link #REORDER_INVERSE_FOR_NUMBERS_SPECIAL} + *
+ * + *

Basic concept: Reordering Options

+ * Reordering options can be applied during Bidi text transformations. + * + *

See Also: + *

    + *
  • {@link #setReorderingOptions} + *
  • {@link #OPTION_DEFAULT} + *
  • {@link #OPTION_INSERT_MARKS} + *
  • {@link #OPTION_REMOVE_CONTROLS} + *
  • {@link #OPTION_STREAMING} + *
+ * + * + * @author Simon Montagu, Matitiahu Allouche (ported from C code written by Markus W. Scherer) + * @stable ICU 3.8 + * + * + *

Sample code for the ICU Bidi API

+ * + *
Rendering a paragraph with the ICU Bidi API
+ * + * This is (hypothetical) sample code that illustrates how the ICU Bidi API + * could be used to render a paragraph of text. Rendering code depends highly on + * the graphics system, therefore this sample code must make a lot of + * assumptions, which may or may not match any existing graphics system's + * properties. + * + *

+ * The basic assumptions are: + *

+ *
    + *
  • Rendering is done from left to right on a horizontal line.
  • + *
  • A run of single-style, unidirectional text can be rendered at once. + *
  • + *
  • Such a run of text is passed to the graphics system with characters + * (code units) in logical order.
  • + *
  • The line-breaking algorithm is very complicated and Locale-dependent - + * and therefore its implementation omitted from this sample code.
  • + *
+ * + *
{@code
+ *
+ *  package com.ibm.icu.dev.test.bidi;
+ *
+ *  import com.ibm.icu.text.Bidi;
+ *  import com.ibm.icu.text.BidiRun;
+ *
+ *  public class Sample {
+ *
+ *      static final int styleNormal = 0;
+ *      static final int styleSelected = 1;
+ *      static final int styleBold = 2;
+ *      static final int styleItalics = 4;
+ *      static final int styleSuper=8;
+ *      static final int styleSub = 16;
+ *
+ *      static class StyleRun {
+ *          int limit;
+ *          int style;
+ *
+ *          public StyleRun(int limit, int style) {
+ *              this.limit = limit;
+ *              this.style = style;
+ *          }
+ *      }
+ *
+ *      static class Bounds {
+ *          int start;
+ *          int limit;
+ *
+ *          public Bounds(int start, int limit) {
+ *              this.start = start;
+ *              this.limit = limit;
+ *          }
+ *      }
+ *
+ *      static int getTextWidth(String text, int start, int limit,
+ *                              StyleRun[] styleRuns, int styleRunCount) {
+ *          // simplistic way to compute the width
+ *          return limit - start;
+ *      }
+ *
+ *      // set limit and StyleRun limit for a line
+ *      // from text[start] and from styleRuns[styleRunStart]
+ *      // using Bidi.getLogicalRun(...)
+ *      // returns line width
+ *      static int getLineBreak(String text, Bounds line, Bidi para,
+ *                              StyleRun styleRuns[], Bounds styleRun) {
+ *          // dummy return
+ *          return 0;
+ *      }
+ *
+ *      // render runs on a line sequentially, always from left to right
+ *
+ *      // prepare rendering a new line
+ *      static void startLine(byte textDirection, int lineWidth) {
+ *          System.out.println();
+ *      }
+ *
+ *      // render a run of text and advance to the right by the run width
+ *      // the text[start..limit-1] is always in logical order
+ *      static void renderRun(String text, int start, int limit,
+ *                            byte textDirection, int style) {
+ *      }
+ *
+ *      // We could compute a cross-product
+ *      // from the style runs with the directional runs
+ *      // and then reorder it.
+ *      // Instead, here we iterate over each run type
+ *      // and render the intersections -
+ *      // with shortcuts in simple (and common) cases.
+ *      // renderParagraph() is the main function.
+ *
+ *      // render a directional run with
+ *      // (possibly) multiple style runs intersecting with it
+ *      static void renderDirectionalRun(String text, int start, int limit,
+ *                                       byte direction, StyleRun styleRuns[],
+ *                                       int styleRunCount) {
+ *          int i;
+ *
+ *          // iterate over style runs
+ *          if (direction == Bidi.LTR) {
+ *              int styleLimit;
+ *              for (i = 0; i < styleRunCount; ++i) {
+ *                  styleLimit = styleRuns[i].limit;
+ *                  if (start < styleLimit) {
+ *                      if (styleLimit > limit) {
+ *                          styleLimit = limit;
+ *                      }
+ *                      renderRun(text, start, styleLimit,
+ *                                direction, styleRuns[i].style);
+ *                      if (styleLimit == limit) {
+ *                          break;
+ *                      }
+ *                      start = styleLimit;
+ *                  }
+ *              }
+ *          } else {
+ *              int styleStart;
+ *
+ *              for (i = styleRunCount-1; i >= 0; --i) {
+ *                  if (i > 0) {
+ *                      styleStart = styleRuns[i-1].limit;
+ *                  } else {
+ *                      styleStart = 0;
+ *                  }
+ *                  if (limit >= styleStart) {
+ *                      if (styleStart < start) {
+ *                          styleStart = start;
+ *                      }
+ *                      renderRun(text, styleStart, limit, direction,
+ *                                styleRuns[i].style);
+ *                      if (styleStart == start) {
+ *                          break;
+ *                      }
+ *                      limit = styleStart;
+ *                  }
+ *              }
+ *          }
+ *      }
+ *
+ *      // the line object represents text[start..limit-1]
+ *      static void renderLine(Bidi line, String text, int start, int limit,
+ *                             StyleRun styleRuns[], int styleRunCount) {
+ *          byte direction = line.getDirection();
+ *          if (direction != Bidi.MIXED) {
+ *              // unidirectional
+ *              if (styleRunCount <= 1) {
+ *                  renderRun(text, start, limit, direction, styleRuns[0].style);
+ *              } else {
+ *                  renderDirectionalRun(text, start, limit, direction,
+ *                                       styleRuns, styleRunCount);
+ *              }
+ *          } else {
+ *              // mixed-directional
+ *              int count, i;
+ *              BidiRun run;
+ *
+ *              try {
+ *                  count = line.countRuns();
+ *              } catch (IllegalStateException e) {
+ *                  e.printStackTrace();
+ *                  return;
+ *              }
+ *              if (styleRunCount <= 1) {
+ *                  int style = styleRuns[0].style;
+ *
+ *                  // iterate over directional runs
+ *                  for (i = 0; i < count; ++i) {
+ *                      run = line.getVisualRun(i);
+ *                      renderRun(text, run.getStart(), run.getLimit(),
+ *                                run.getDirection(), style);
+ *                  }
+ *              } else {
+ *                  // iterate over both directional and style runs
+ *                  for (i = 0; i < count; ++i) {
+ *                      run = line.getVisualRun(i);
+ *                      renderDirectionalRun(text, run.getStart(),
+ *                                           run.getLimit(), run.getDirection(),
+ *                                           styleRuns, styleRunCount);
+ *                  }
+ *              }
+ *          }
+ *      }
+ *
+ *      static void renderParagraph(String text, byte textDirection,
+ *                                  StyleRun styleRuns[], int styleRunCount,
+ *                                  int lineWidth) {
+ *          int length = text.length();
+ *          Bidi para = new Bidi();
+ *          try {
+ *              para.setPara(text,
+ *                           textDirection != 0 ? Bidi.LEVEL_DEFAULT_RTL
+ *                                              : Bidi.LEVEL_DEFAULT_LTR,
+ *                           null);
+ *          } catch (Exception e) {
+ *              e.printStackTrace();
+ *              return;
+ *          }
+ *          byte paraLevel = (byte)(1 & para.getParaLevel());
+ *          StyleRun styleRun = new StyleRun(length, styleNormal);
+ *
+ *          if (styleRuns == null || styleRunCount <= 0) {
+ *              styleRuns = new StyleRun[1];
+ *              styleRunCount = 1;
+ *              styleRuns[0] = styleRun;
+ *          }
+ *          // assume styleRuns[styleRunCount-1].limit>=length
+ *
+ *          int width = getTextWidth(text, 0, length, styleRuns, styleRunCount);
+ *          if (width <= lineWidth) {
+ *              // everything fits onto one line
+ *
+ *              // prepare rendering a new line from either left or right
+ *              startLine(paraLevel, width);
+ *
+ *              renderLine(para, text, 0, length, styleRuns, styleRunCount);
+ *          } else {
+ *              // we need to render several lines
+ *              Bidi line = new Bidi(length, 0);
+ *              int start = 0, limit;
+ *              int styleRunStart = 0, styleRunLimit;
+ *
+ *              for (;;) {
+ *                  limit = length;
+ *                  styleRunLimit = styleRunCount;
+ *                  width = getLineBreak(text, new Bounds(start, limit),
+ *                                       para, styleRuns,
+ *                                       new Bounds(styleRunStart, styleRunLimit));
+ *                  try {
+ *                      line = para.setLine(start, limit);
+ *                  } catch (Exception e) {
+ *                      e.printStackTrace();
+ *                      return;
+ *                  }
+ *                  // prepare rendering a new line
+ *                  // from either left or right
+ *                  startLine(paraLevel, width);
+ *
+ *                  if (styleRunStart > 0) {
+ *                      int newRunCount = styleRuns.length - styleRunStart;
+ *                      StyleRun[] newRuns = new StyleRun[newRunCount];
+ *                      System.arraycopy(styleRuns, styleRunStart, newRuns, 0,
+ *                                       newRunCount);
+ *                      renderLine(line, text, start, limit, newRuns,
+ *                                 styleRunLimit - styleRunStart);
+ *                  } else {
+ *                      renderLine(line, text, start, limit, styleRuns,
+ *                                 styleRunLimit - styleRunStart);
+ *                  }
+ *                  if (limit == length) {
+ *                      break;
+ *                  }
+ *                  start = limit;
+ *                  styleRunStart = styleRunLimit - 1;
+ *                  if (start >= styleRuns[styleRunStart].limit) {
+ *                      ++styleRunStart;
+ *                  }
+ *              }
+ *          }
+ *      }
+ *
+ *      public static void main(String[] args)
+ *      {
+ *          renderParagraph("Some Latin text...", Bidi.LTR, null, 0, 80);
+ *          renderParagraph("Some Hebrew text...", Bidi.RTL, null, 0, 60);
+ *      }
+ *  }
+ *
+ * }
+ */ + +/* + * General implementation notes: + * + * Throughout the implementation, there are comments like (W2) that refer to + * rules of the BiDi algorithm, in this example to the second rule of the + * resolution of weak types. + * + * For handling surrogate pairs, where two UChar's form one "abstract" (or UTF-32) + * character according to UTF-16, the second UChar gets the directional property of + * the entire character assigned, while the first one gets a BN, a boundary + * neutral, type, which is ignored by most of the algorithm according to + * rule (X9) and the implementation suggestions of the BiDi algorithm. + * + * Later, adjustWSLevels() will set the level for each BN to that of the + * following character (UChar), which results in surrogate pairs getting the + * same level on each of their surrogates. + * + * In a UTF-8 implementation, the same thing could be done: the last byte of + * a multi-byte sequence would get the "real" property, while all previous + * bytes of that sequence would get BN. + * + * It is not possible to assign all those parts of a character the same real + * property because this would fail in the resolution of weak types with rules + * that look at immediately surrounding types. + * + * As a related topic, this implementation does not remove Boundary Neutral + * types from the input, but ignores them wherever this is relevant. + * For example, the loop for the resolution of the weak types reads + * types until it finds a non-BN. + * Also, explicit embedding codes are neither changed into BN nor removed. + * They are only treated the same way real BNs are. + * As stated before, adjustWSLevels() takes care of them at the end. + * For the purpose of conformance, the levels of all these codes + * do not matter. + * + * Note that this implementation modifies the dirProps + * after the initial setup, when applying X5c (replace FSI by LRI or RLI), + * X6, N0 (replace paired brackets by L or R). + * + * In this implementation, the resolution of weak types (W1 to W6), + * neutrals (N1 and N2), and the assignment of the resolved level (In) + * are all done in one single loop, in resolveImplicitLevels(). + * Changes of dirProp values are done on the fly, without writing + * them back to the dirProps array. + * + * + * This implementation contains code that allows to bypass steps of the + * algorithm that are not needed on the specific paragraph + * in order to speed up the most common cases considerably, + * like text that is entirely LTR, or RTL text without numbers. + * + * Most of this is done by setting a bit for each directional property + * in a flags variable and later checking for whether there are + * any LTR characters or any RTL characters, or both, whether + * there are any explicit embedding codes, etc. + * + * If the (Xn) steps are performed, then the flags are re-evaluated, + * because they will then not contain the embedding codes any more + * and will be adjusted for override codes, so that subsequently + * more bypassing may be possible than what the initial flags suggested. + * + * If the text is not mixed-directional, then the + * algorithm steps for the weak type resolution are not performed, + * and all levels are set to the paragraph level. + * + * If there are no explicit embedding codes, then the (Xn) steps + * are not performed. + * + * If embedding levels are supplied as a parameter, then all + * explicit embedding codes are ignored, and the (Xn) steps + * are not performed. + * + * White Space types could get the level of the run they belong to, + * and are checked with a test of (flags&MASK_EMBEDDING) to + * consider if the paragraph direction should be considered in + * the flags variable. + * + * If there are no White Space types in the paragraph, then + * (L1) is not necessary in adjustWSLevels(). + */ + +// Original filename in ICU4J: Bidi.java +public class BidiBase { + + static class Point { + int pos; /* position in text */ + int flag; /* flag for LRM/RLM, before/after */ + } + + static class InsertPoints { + int size; + int confirmed; + Point[] points = new Point[0]; + } + + static class Opening { + int position; /* position of opening bracket */ + int match; /* matching char or -position of closing bracket */ + int contextPos; /* position of last strong char found before opening */ + short flags; /* bits for L or R/AL found within the pair */ + byte contextDir; /* L or R according to last strong char before opening */ + } + + static class IsoRun { + int contextPos; /* position of char determining context */ + short start; /* index of first opening entry for this run */ + short limit; /* index after last opening entry for this run */ + byte level; /* level of this run */ + byte lastStrong; /* bidi class of last strong char found in this run */ + byte lastBase; /* bidi class of last base char found in this run */ + byte contextDir; /* L or R to use as context for following openings */ + } + + static class BracketData { + Opening[] openings = new Opening[SIMPLE_PARAS_COUNT]; + int isoRunLast; /* index of last used entry */ + /* array of nested isolated sequence entries; can never excess UBIDI_MAX_EXPLICIT_LEVEL + + 1 for index 0, + 1 for before the first isolated sequence */ + IsoRun[] isoRuns = new IsoRun[MAX_EXPLICIT_LEVEL+2]; + boolean isNumbersSpecial; /*reordering mode for NUMBERS_SPECIAL */ + } + + static class Isolate { + int startON; + int start1; + short stateImp; + short state; + } + + /** Paragraph level setting

+ * + * Constant indicating that the base direction depends on the first strong + * directional character in the text according to the Unicode Bidirectional + * Algorithm. If no strong directional character is present, + * then set the paragraph level to 0 (left-to-right).

+ * + * If this value is used in conjunction with reordering modes + * REORDER_INVERSE_LIKE_DIRECT or + * REORDER_INVERSE_FOR_NUMBERS_SPECIAL, the text to reorder + * is assumed to be visual LTR, and the text after reordering is required + * to be the corresponding logical string with appropriate contextual + * direction. The direction of the result string will be RTL if either + * the rightmost or leftmost strong character of the source text is RTL + * or Arabic Letter, the direction will be LTR otherwise.

+ * + * If reordering option OPTION_INSERT_MARKS is set, an RLM may + * be added at the beginning of the result string to ensure round trip + * (that the result string, when reordered back to visual, will produce + * the original source text). + * @see #REORDER_INVERSE_LIKE_DIRECT + * @see #REORDER_INVERSE_FOR_NUMBERS_SPECIAL + * @stable ICU 3.8 + */ + public static final byte LEVEL_DEFAULT_LTR = (byte)0x7e; + + /** Paragraph level setting

+ * + * Constant indicating that the base direction depends on the first strong + * directional character in the text according to the Unicode Bidirectional + * Algorithm. If no strong directional character is present, + * then set the paragraph level to 1 (right-to-left).

+ * + * If this value is used in conjunction with reordering modes + * REORDER_INVERSE_LIKE_DIRECT or + * REORDER_INVERSE_FOR_NUMBERS_SPECIAL, the text to reorder + * is assumed to be visual LTR, and the text after reordering is required + * to be the corresponding logical string with appropriate contextual + * direction. The direction of the result string will be RTL if either + * the rightmost or leftmost strong character of the source text is RTL + * or Arabic Letter, or if the text contains no strong character; + * the direction will be LTR otherwise.

+ * + * If reordering option OPTION_INSERT_MARKS is set, an RLM may + * be added at the beginning of the result string to ensure round trip + * (that the result string, when reordered back to visual, will produce + * the original source text). + * @see #REORDER_INVERSE_LIKE_DIRECT + * @see #REORDER_INVERSE_FOR_NUMBERS_SPECIAL + * @stable ICU 3.8 + */ + public static final byte LEVEL_DEFAULT_RTL = (byte)0x7f; + + /** + * Maximum explicit embedding level. + * (The maximum resolved level can be up to MAX_EXPLICIT_LEVEL+1). + * @stable ICU 3.8 + */ + public static final byte MAX_EXPLICIT_LEVEL = 125; + + /** + * Bit flag for level input. + * Overrides directional properties. + * @stable ICU 3.8 + */ + public static final byte LEVEL_OVERRIDE = (byte)0x80; + + /** + * Special value which can be returned by the mapping methods when a + * logical index has no corresponding visual index or vice-versa. This may + * happen for the logical-to-visual mapping of a Bidi control when option + * OPTION_REMOVE_CONTROLS is + * specified. This can also happen for the visual-to-logical mapping of a + * Bidi mark (LRM or RLM) inserted by option + * OPTION_INSERT_MARKS. + * @see #getVisualIndex + * @see #getVisualMap + * @see #getLogicalIndex + * @see #getLogicalMap + * @see #OPTION_INSERT_MARKS + * @see #OPTION_REMOVE_CONTROLS + * @stable ICU 3.8 + */ + public static final int MAP_NOWHERE = -1; + + /** + * Left-to-right text. + *

    + *
  • As return value for getDirection(), it means + * that the source string contains no right-to-left characters, or + * that the source string is empty and the paragraph level is even. + *
  • As return value for getBaseDirection(), it + * means that the first strong character of the source string has + * a left-to-right direction. + *
+ * @stable ICU 3.8 + */ + public static final byte LTR = 0; + + /** + * Right-to-left text. + *
    + *
  • As return value for getDirection(), it means + * that the source string contains no left-to-right characters, or + * that the source string is empty and the paragraph level is odd. + *
  • As return value for getBaseDirection(), it + * means that the first strong character of the source string has + * a right-to-left direction. + *
+ * @stable ICU 3.8 + */ + public static final byte RTL = 1; + + /** + * Mixed-directional text. + *

As return value for getDirection(), it means + * that the source string contains both left-to-right and + * right-to-left characters. + * @stable ICU 3.8 + */ + public static final byte MIXED = 2; + + /** + * option bit for writeReordered(): + * keep combining characters after their base characters in RTL runs + * + * @see #writeReordered + * @stable ICU 3.8 + */ + public static final short KEEP_BASE_COMBINING = 1; + + /** + * option bit for writeReordered(): + * replace characters with the "mirrored" property in RTL runs + * by their mirror-image mappings + * + * @see #writeReordered + * @stable ICU 3.8 + */ + public static final short DO_MIRRORING = 2; + + /** + * option bit for writeReordered(): + * surround the run with LRMs if necessary; + * this is part of the approximate "inverse Bidi" algorithm + * + *

This option does not imply corresponding adjustment of the index + * mappings.

+ * + * @see #setInverse + * @see #writeReordered + * @stable ICU 3.8 + */ + public static final short INSERT_LRM_FOR_NUMERIC = 4; + + /** + * option bit for writeReordered(): + * remove Bidi control characters + * (this does not affect INSERT_LRM_FOR_NUMERIC) + * + *

This option does not imply corresponding adjustment of the index + * mappings.

+ * + * @see #writeReordered + * @see #INSERT_LRM_FOR_NUMERIC + * @stable ICU 3.8 + */ + public static final short REMOVE_BIDI_CONTROLS = 8; + + /** + * option bit for writeReordered(): + * write the output in reverse order + * + *

This has the same effect as calling writeReordered() + * first without this option, and then calling + * writeReverse() without mirroring. + * Doing this in the same step is faster and avoids a temporary buffer. + * An example for using this option is output to a character terminal that + * is designed for RTL scripts and stores text in reverse order.

+ * + * @see #writeReordered + * @stable ICU 3.8 + */ + public static final short OUTPUT_REVERSE = 16; + + /** Reordering mode: Regular Logical to Visual Bidi algorithm according to Unicode. + * @see #setReorderingMode + * @stable ICU 3.8 + */ + private static final short REORDER_DEFAULT = 0; + + /** Reordering mode: Logical to Visual algorithm which handles numbers in + * a way which mimicks the behavior of Windows XP. + * @see #setReorderingMode + * @stable ICU 3.8 + */ + private static final short REORDER_NUMBERS_SPECIAL = 1; + + /** Reordering mode: Logical to Visual algorithm grouping numbers with + * adjacent R characters (reversible algorithm). + * @see #setReorderingMode + * @stable ICU 3.8 + */ + private static final short REORDER_GROUP_NUMBERS_WITH_R = 2; + + /** Reordering mode: Reorder runs only to transform a Logical LTR string + * to the logical RTL string with the same display, or vice-versa.
+ * If this mode is set together with option + * OPTION_INSERT_MARKS, some Bidi controls in the source + * text may be removed and other controls may be added to produce the + * minimum combination which has the required display. + * @see #OPTION_INSERT_MARKS + * @see #setReorderingMode + * @stable ICU 3.8 + */ + static final short REORDER_RUNS_ONLY = 3; + + /** Reordering mode: Visual to Logical algorithm which handles numbers + * like L (same algorithm as selected by setInverse(true). + * @see #setInverse + * @see #setReorderingMode + * @stable ICU 3.8 + */ + static final short REORDER_INVERSE_NUMBERS_AS_L = 4; + + /** Reordering mode: Visual to Logical algorithm equivalent to the regular + * Logical to Visual algorithm. + * @see #setReorderingMode + * @stable ICU 3.8 + */ + static final short REORDER_INVERSE_LIKE_DIRECT = 5; + + /** Reordering mode: Inverse Bidi (Visual to Logical) algorithm for the + * REORDER_NUMBERS_SPECIAL Bidi algorithm. + * @see #setReorderingMode + * @stable ICU 3.8 + */ + static final short REORDER_INVERSE_FOR_NUMBERS_SPECIAL = 6; + + /* Reordering mode values must be ordered so that all the regular logical to + * visual modes come first, and all inverse Bidi modes come last. + */ + private static final short REORDER_LAST_LOGICAL_TO_VISUAL = + REORDER_NUMBERS_SPECIAL; + + /** + * Option bit for setReorderingOptions: + * insert Bidi marks (LRM or RLM) when needed to ensure correct result of + * a reordering to a Logical order + * + *

This option must be set or reset before calling + * setPara.

+ * + *

This option is significant only with reordering modes which generate + * a result with Logical order, specifically.

+ *
    + *
  • REORDER_RUNS_ONLY
  • + *
  • REORDER_INVERSE_NUMBERS_AS_L
  • + *
  • REORDER_INVERSE_LIKE_DIRECT
  • + *
  • REORDER_INVERSE_FOR_NUMBERS_SPECIAL
  • + *
+ * + *

If this option is set in conjunction with reordering mode + * REORDER_INVERSE_NUMBERS_AS_L or with calling + * setInverse(true), it implies option + * INSERT_LRM_FOR_NUMERIC in calls to method + * writeReordered().

+ * + *

For other reordering modes, a minimum number of LRM or RLM characters + * will be added to the source text after reordering it so as to ensure + * round trip, i.e. when applying the inverse reordering mode on the + * resulting logical text with removal of Bidi marks + * (option OPTION_REMOVE_CONTROLS set before calling + * setPara() or option + * REMOVE_BIDI_CONTROLS in + * writeReordered), the result will be identical to the + * source text in the first transformation. + * + *

This option will be ignored if specified together with option + * OPTION_REMOVE_CONTROLS. It inhibits option + * REMOVE_BIDI_CONTROLS in calls to method + * writeReordered() and it implies option + * INSERT_LRM_FOR_NUMERIC in calls to method + * writeReordered() if the reordering mode is + * REORDER_INVERSE_NUMBERS_AS_L.

+ * + * @see #setReorderingMode + * @see #setReorderingOptions + * @see #INSERT_LRM_FOR_NUMERIC + * @see #REMOVE_BIDI_CONTROLS + * @see #OPTION_REMOVE_CONTROLS + * @see #REORDER_RUNS_ONLY + * @see #REORDER_INVERSE_NUMBERS_AS_L + * @see #REORDER_INVERSE_LIKE_DIRECT + * @see #REORDER_INVERSE_FOR_NUMBERS_SPECIAL + * @stable ICU 3.8 + */ + static final int OPTION_INSERT_MARKS = 1; + + /** + * Option bit for setReorderingOptions: + * remove Bidi control characters + * + *

This option must be set or reset before calling + * setPara.

+ * + *

This option nullifies option + * OPTION_INSERT_MARKS. It inhibits option + * INSERT_LRM_FOR_NUMERIC in calls to method + * writeReordered() and it implies option + * REMOVE_BIDI_CONTROLS in calls to that method.

+ * + * @see #setReorderingMode + * @see #setReorderingOptions + * @see #OPTION_INSERT_MARKS + * @see #INSERT_LRM_FOR_NUMERIC + * @see #REMOVE_BIDI_CONTROLS + * @stable ICU 3.8 + */ + static final int OPTION_REMOVE_CONTROLS = 2; + + /** + * Option bit for setReorderingOptions: + * process the output as part of a stream to be continued + * + *

This option must be set or reset before calling + * setPara.

+ * + *

This option specifies that the caller is interested in processing + * large text object in parts. The results of the successive calls are + * expected to be concatenated by the caller. Only the call for the last + * part will have this option bit off.

+ * + *

When this option bit is on, setPara() may process + * less than the full source text in order to truncate the text at a + * meaningful boundary. The caller should call + * getProcessedLength() immediately after calling + * setPara() in order to determine how much of the source + * text has been processed. Source text beyond that length should be + * resubmitted in following calls to setPara. The + * processed length may be less than the length of the source text if a + * character preceding the last character of the source text constitutes a + * reasonable boundary (like a block separator) for text to be continued.
+ * If the last character of the source text constitutes a reasonable + * boundary, the whole text will be processed at once.
+ * If nowhere in the source text there exists + * such a reasonable boundary, the processed length will be zero.
+ * The caller should check for such an occurrence and do one of the following: + *

  • submit a larger amount of text with a better chance to include + * a reasonable boundary.
  • + *
  • resubmit the same text after turning off option + * OPTION_STREAMING.
+ * In all cases, this option should be turned off before processing the last + * part of the text.

+ * + *

When the OPTION_STREAMING option is used, it is + * recommended to call orderParagraphsLTR(true) before calling + * setPara() so that later paragraphs may be concatenated to + * previous paragraphs on the right. + *

+ * + * @see #setReorderingMode + * @see #setReorderingOptions + * @see #getProcessedLength + * @stable ICU 3.8 + */ + private static final int OPTION_STREAMING = 4; + + /* + * Comparing the description of the Bidi algorithm with this implementation + * is easier with the same names for the Bidi types in the code as there. + * See UCharacterDirection + */ + /* private */ static final byte L = 0; + private static final byte R = 1; + private static final byte EN = 2; + private static final byte ES = 3; + private static final byte ET = 4; + private static final byte AN = 5; + private static final byte CS = 6; + static final byte B = 7; + private static final byte S = 8; + private static final byte WS = 9; + private static final byte ON = 10; + private static final byte LRE = 11; + private static final byte LRO = 12; + private static final byte AL = 13; + private static final byte RLE = 14; + private static final byte RLO = 15; + private static final byte PDF = 16; + private static final byte NSM = 17; + private static final byte BN = 18; + private static final byte FSI = 19; + private static final byte LRI = 20; + private static final byte RLI = 21; + private static final byte PDI = 22; + private static final byte ENL = PDI + 1; /* EN after W7 */ + private static final byte ENR = ENL + 1; /* EN not subject to W7 */ + + // Number of directional types + private static final int CHAR_DIRECTION_COUNT = 23; + + /** + * Enumerated property Bidi_Paired_Bracket_Type (new in Unicode 6.3). + * Used in + * Unicode Standard Annex #9: + * Unicode Bidirectional Algorithm. + * Returns UCharacter.BidiPairedBracketType values. + * @stable ICU 52 + */ + public static final int BIDI_PAIRED_BRACKET_TYPE = 0x1015; + + /** + * Bidi Paired Bracket Type constants. + * + * @see UProperty#BIDI_PAIRED_BRACKET_TYPE + * @stable ICU 52 + */ + public static interface BidiPairedBracketType { + /** + * Not a paired bracket. + * @stable ICU 52 + */ + public static final int NONE = 0; + /** + * Open paired bracket. + * @stable ICU 52 + */ + public static final int OPEN = 1; + /** + * Close paired bracket. + * @stable ICU 52 + */ + public static final int CLOSE = 2; + /** + * @stable ICU 52 + */ + public static final int COUNT = 3; + } + + /* number of paras entries allocated initially */ + static final int SIMPLE_PARAS_COUNT = 10; + + private static final char CR = '\r'; + private static final char LF = '\n'; + + static final int LRM_BEFORE = 1; + static final int LRM_AFTER = 2; + static final int RLM_BEFORE = 4; + static final int RLM_AFTER = 8; + + /* flags for Opening.flags */ + static final byte FOUND_L = (byte)DirPropFlag(L); + static final byte FOUND_R = (byte)DirPropFlag(R); + + /* + * The following bit is used for the directional isolate status. + * Stack entries corresponding to isolate sequences are greater than ISOLATE. + */ + static final int ISOLATE = 0x0100; + + /* + * reference to parent paragraph object (reference to self if this object is + * a paragraph object); set to null in a newly opened object; set to a + * real value after a successful execution of setPara or setLine + */ + BidiBase paraBidi; + + final UBiDiProps bdp; + + /* character array representing the current text */ + char[] text; + + /* length of the current text */ + int originalLength; + + /* if the option OPTION_STREAMING is set, this is the length of + * text actually processed by setPara, which may be shorter + * than the original length. Otherwise, it is identical to the original + * length. + */ + public int length; + + /* if option OPTION_REMOVE_CONTROLS is set, and/or Bidi + * marks are allowed to be inserted in one of the reordering modes, the + * length of the result string may be different from the processed length. + */ + int resultLength; + + /* indicators for whether memory may be allocated after construction */ + boolean mayAllocateText; + boolean mayAllocateRuns; + + /* arrays with one value per text-character */ + byte[] dirPropsMemory = new byte[1]; + byte[] levelsMemory = new byte[1]; + byte[] dirProps; + byte[] levels; + + /* are we performing an approximation of the "inverse Bidi" algorithm? */ + boolean isInverse; + + /* are we using the basic algorithm or its variation? */ + int reorderingMode; + + /* bitmask for reordering options */ + int reorderingOptions; + + /* must block separators receive level 0? */ + boolean orderParagraphsLTR; + + /* the paragraph level */ + byte paraLevel; + + /* original paraLevel when contextual */ + /* must be one of DEFAULT_xxx or 0 if not contextual */ + byte defaultParaLevel; + + /* the following is set in setPara, used in processPropertySeq */ + + ImpTabPair impTabPair; /* reference to levels state table pair */ + + /* the overall paragraph or line directionality*/ + byte direction; + + /* flags is a bit set for which directional properties are in the text */ + int flags; + + /* lastArabicPos is index to the last AL in the text, -1 if none */ + int lastArabicPos; + + /* characters after trailingWSStart are WS and are */ + /* implicitly at the paraLevel (rule (L1)) - levels may not reflect that */ + int trailingWSStart; + + /* fields for paragraph handling, set in getDirProps() */ + int paraCount; + int[] paras_limit = new int[SIMPLE_PARAS_COUNT]; + byte[] paras_level = new byte[SIMPLE_PARAS_COUNT]; + + /* fields for line reordering */ + int runCount; /* ==-1: runs not set up yet */ + BidiRun[] runsMemory = new BidiRun[0]; + BidiRun[] runs; + + /* for non-mixed text, we only need a tiny array of runs (no allocation) */ + BidiRun[] simpleRuns = {new BidiRun()}; + + /* fields for managing isolate sequences */ + Isolate[] isolates; + + /* maximum or current nesting depth of isolate sequences */ + /* Within resolveExplicitLevels() and checkExplicitLevels(), this is the maximal + nesting encountered. + Within resolveImplicitLevels(), this is the index of the current isolates + stack entry. */ + int isolateCount; + + /* mapping of runs in logical order to visual order */ + int[] logicalToVisualRunsMap; + /* flag to indicate that the map has been updated */ + boolean isGoodLogicalToVisualRunsMap; + + /* for inverse Bidi with insertion of directional marks */ + InsertPoints insertPoints = new InsertPoints(); + + /* for option OPTION_REMOVE_CONTROLS */ + int controlCount; + + /* + * Sometimes, bit values are more appropriate + * to deal with directionality properties. + * Abbreviations in these method names refer to names + * used in the Bidi algorithm. + */ + static int DirPropFlag(byte dir) { + return (1 << dir); + } + + boolean testDirPropFlagAt(int flag, int index) { + return ((DirPropFlag(dirProps[index]) & flag) != 0); + } + + static final int DirPropFlagMultiRuns = DirPropFlag((byte)31); + + /* to avoid some conditional statements, use tiny constant arrays */ + static final int DirPropFlagLR[] = { DirPropFlag(L), DirPropFlag(R) }; + static final int DirPropFlagE[] = { DirPropFlag(LRE), DirPropFlag(RLE) }; + static final int DirPropFlagO[] = { DirPropFlag(LRO), DirPropFlag(RLO) }; + + static final int DirPropFlagLR(byte level) { return DirPropFlagLR[level & 1]; } + static final int DirPropFlagE(byte level) { return DirPropFlagE[level & 1]; } + static final int DirPropFlagO(byte level) { return DirPropFlagO[level & 1]; } + static final byte DirFromStrong(byte strong) { return strong == L ? L : R; } + static final byte NoOverride(byte level) { return (byte)(level & ~LEVEL_OVERRIDE); } + + /* are there any characters that are LTR or RTL? */ + static final int MASK_LTR = + DirPropFlag(L)|DirPropFlag(EN)|DirPropFlag(ENL)|DirPropFlag(ENR)|DirPropFlag(AN)|DirPropFlag(LRE)|DirPropFlag(LRO)|DirPropFlag(LRI); + static final int MASK_RTL = DirPropFlag(R)|DirPropFlag(AL)|DirPropFlag(RLE)|DirPropFlag(RLO)|DirPropFlag(RLI); + + static final int MASK_R_AL = DirPropFlag(R)|DirPropFlag(AL); + + /* explicit embedding codes */ + private static final int MASK_EXPLICIT = DirPropFlag(LRE)|DirPropFlag(LRO)|DirPropFlag(RLE)|DirPropFlag(RLO)|DirPropFlag(PDF); + private static final int MASK_BN_EXPLICIT = DirPropFlag(BN)|MASK_EXPLICIT; + + /* explicit isolate codes */ + private static final int MASK_ISO = DirPropFlag(LRI)|DirPropFlag(RLI)|DirPropFlag(FSI)|DirPropFlag(PDI); + + /* paragraph and segment separators */ + private static final int MASK_B_S = DirPropFlag(B)|DirPropFlag(S); + + /* all types that are counted as White Space or Neutral in some steps */ + static final int MASK_WS = MASK_B_S|DirPropFlag(WS)|MASK_BN_EXPLICIT|MASK_ISO; + + /* types that are neutrals or could becomes neutrals in (Wn) */ + private static final int MASK_POSSIBLE_N = DirPropFlag(ON)|DirPropFlag(CS)|DirPropFlag(ES)|DirPropFlag(ET)|MASK_WS; + + /* + * These types may be changed to "e", + * the embedding type (L or R) of the run, + * in the Bidi algorithm (N2) + */ + private static final int MASK_EMBEDDING = DirPropFlag(NSM)|MASK_POSSIBLE_N; + + /* + * the dirProp's L and R are defined to 0 and 1 values in UCharacterDirection.java + */ + private static byte GetLRFromLevel(byte level) + { + return (byte)(level & 1); + } + + private static boolean IsDefaultLevel(byte level) + { + return ((level & LEVEL_DEFAULT_LTR) == LEVEL_DEFAULT_LTR); + } + + static boolean IsBidiControlChar(int c) + { + /* check for range 0x200c to 0x200f (ZWNJ, ZWJ, LRM, RLM) or + 0x202a to 0x202e (LRE, RLE, PDF, LRO, RLO) */ + return (((c & 0xfffffffc) == 0x200c) || ((c >= 0x202a) && (c <= 0x202e)) + || ((c >= 0x2066) && (c <= 0x2069))); + } + + void verifyValidPara() + { + if (!(this == this.paraBidi)) { + throw new IllegalStateException(); + } + } + + void verifyValidParaOrLine() + { + BidiBase para = this.paraBidi; + /* verify Para */ + if (this == para) { + return; + } + /* verify Line */ + if ((para == null) || (para != para.paraBidi)) { + throw new IllegalStateException(); + } + } + + void verifyRange(int index, int start, int limit) + { + if (index < start || index >= limit) { + throw new IllegalArgumentException("Value " + index + + " is out of range " + start + " to " + limit); + } + } + + /** + * Allocate a Bidi object with preallocated memory + * for internal structures. + * This method provides a Bidi object like the default constructor + * but it also preallocates memory for internal structures + * according to the sizings supplied by the caller.

+ * The preallocation can be limited to some of the internal memory + * by setting some values to 0 here. That means that if, e.g., + * maxRunCount cannot be reasonably predetermined and should not + * be set to maxLength (the only failproof value) to avoid + * wasting memory, then maxRunCount could be set to 0 here + * and the internal structures that are associated with it will be allocated + * on demand, just like with the default constructor. + * + * @param maxLength is the maximum text or line length that internal memory + * will be preallocated for. An attempt to associate this object with a + * longer text will fail, unless this value is 0, which leaves the allocation + * up to the implementation. + * + * @param maxRunCount is the maximum anticipated number of same-level runs + * that internal memory will be preallocated for. An attempt to access + * visual runs on an object that was not preallocated for as many runs + * as the text was actually resolved to will fail, + * unless this value is 0, which leaves the allocation up to the implementation.

+ * The number of runs depends on the actual text and maybe anywhere between + * 1 and maxLength. It is typically small. + * + * @throws IllegalArgumentException if maxLength or maxRunCount is less than 0 + * @stable ICU 3.8 + */ + public BidiBase(int maxLength, int maxRunCount) + { + /* check the argument values */ + if (maxLength < 0 || maxRunCount < 0) { + throw new IllegalArgumentException(); + } + + /* reset the object, all reference variables null, all flags false, + all sizes 0. + In fact, we don't need to do anything, since class members are + initialized as zero when an instance is created. + */ + /* + mayAllocateText = false; + mayAllocateRuns = false; + orderParagraphsLTR = false; + paraCount = 0; + runCount = 0; + trailingWSStart = 0; + flags = 0; + paraLevel = 0; + defaultParaLevel = 0; + direction = 0; + */ + /* get Bidi properties */ + bdp = UBiDiProps.INSTANCE; + + /* allocate memory for arrays as requested */ + if (maxLength > 0) { + getInitialDirPropsMemory(maxLength); + getInitialLevelsMemory(maxLength); + } else { + mayAllocateText = true; + } + + if (maxRunCount > 0) { + // if maxRunCount == 1, use simpleRuns[] + if (maxRunCount > 1) { + getInitialRunsMemory(maxRunCount); + } + } else { + mayAllocateRuns = true; + } + } + + /* + * We are allowed to allocate memory if object==null or + * mayAllocate==true for each array that we need. + * + * Assume sizeNeeded>0. + * If object != null, then assume size > 0. + */ + private Object getMemory(String label, Object array, Class arrayClass, + boolean mayAllocate, int sizeNeeded) + { + int len = Array.getLength(array); + + /* we have at least enough memory and must not allocate */ + if (sizeNeeded == len) { + return array; + } + if (!mayAllocate) { + /* we must not allocate */ + if (sizeNeeded <= len) { + return array; + } + throw new OutOfMemoryError("Failed to allocate memory for " + + label); + } + /* we may try to grow or shrink */ + /* FOOD FOR THOUGHT: when shrinking it should be possible to avoid + the allocation altogether and rely on this.length */ + try { + return Array.newInstance(arrayClass, sizeNeeded); + } catch (Exception e) { + throw new OutOfMemoryError("Failed to allocate memory for " + + label); + } + } + + /* helper methods for each allocated array */ + private void getDirPropsMemory(boolean mayAllocate, int len) + { + Object array = getMemory("DirProps", dirPropsMemory, Byte.TYPE, mayAllocate, len); + dirPropsMemory = (byte[]) array; + } + + void getDirPropsMemory(int len) + { + getDirPropsMemory(mayAllocateText, len); + } + + private void getLevelsMemory(boolean mayAllocate, int len) + { + Object array = getMemory("Levels", levelsMemory, Byte.TYPE, mayAllocate, len); + levelsMemory = (byte[]) array; + } + + void getLevelsMemory(int len) + { + getLevelsMemory(mayAllocateText, len); + } + + private void getRunsMemory(boolean mayAllocate, int len) + { + Object array = getMemory("Runs", runsMemory, BidiRun.class, mayAllocate, len); + runsMemory = (BidiRun[]) array; + } + + void getRunsMemory(int len) + { + getRunsMemory(mayAllocateRuns, len); + } + + /* additional methods used by constructor - always allow allocation */ + private void getInitialDirPropsMemory(int len) + { + getDirPropsMemory(true, len); + } + + private void getInitialLevelsMemory(int len) + { + getLevelsMemory(true, len); + } + + private void getInitialRunsMemory(int len) + { + getRunsMemory(true, len); + } + + /** + * Is this Bidi object set to perform the inverse Bidi + * algorithm? + *

Note: calling this method after setting the reordering mode with + * setReorderingMode will return true if the + * reordering mode was set to + * REORDER_INVERSE_NUMBERS_AS_L, false + * for all other values.

+ * + * @return true if the Bidi object is set to + * perform the inverse Bidi algorithm by handling numbers as L. + * + * @see #setInverse + * @see #setReorderingMode + * @see #REORDER_INVERSE_NUMBERS_AS_L + * @stable ICU 3.8 + */ + public boolean isInverse() { + return isInverse; + } + + /* perform (P2)..(P3) ------------------------------------------------------- */ + + /* + * Check that there are enough entries in the arrays paras_limit and paras_level + */ + private void checkParaCount() { + int[] saveLimits; + byte[] saveLevels; + int count = paraCount; + if (count <= paras_level.length) + return; + int oldLength = paras_level.length; + saveLimits = paras_limit; + saveLevels = paras_level; + try { + paras_limit = new int[count * 2]; + paras_level = new byte[count * 2]; + } catch (Exception e) { + throw new OutOfMemoryError("Failed to allocate memory for paras"); + } + System.arraycopy(saveLimits, 0, paras_limit, 0, oldLength); + System.arraycopy(saveLevels, 0, paras_level, 0, oldLength); + } + + /* + * Get the directional properties for the text, calculate the flags bit-set, and + * determine the paragraph level if necessary (in paras_level[i]). + * FSI initiators are also resolved and their dirProp replaced with LRI or RLI. + * When encountering an FSI, it is initially replaced with an LRI, which is the + * default. Only if a strong R or AL is found within its scope will the LRI be + * replaced by an RLI. + */ + static final int NOT_SEEKING_STRONG = 0; /* 0: not contextual paraLevel, not after FSI */ + static final int SEEKING_STRONG_FOR_PARA = 1; /* 1: looking for first strong char in para */ + static final int SEEKING_STRONG_FOR_FSI = 2; /* 2: looking for first strong after FSI */ + static final int LOOKING_FOR_PDI = 3; /* 3: found strong after FSI, looking for PDI */ + + private void getDirProps() + { + int i = 0, i0, i1; + flags = 0; /* collect all directionalities in the text */ + int uchar; + byte dirProp; + byte defaultParaLevel = 0; /* initialize to avoid compiler warnings */ + boolean isDefaultLevel = IsDefaultLevel(paraLevel); + /* for inverse Bidi, the default para level is set to RTL if there is a + strong R or AL character at either end of the text */ + boolean isDefaultLevelInverse=isDefaultLevel && + (reorderingMode == REORDER_INVERSE_LIKE_DIRECT || + reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL); + lastArabicPos = -1; + int controlCount = 0; + boolean removeBidiControls = (reorderingOptions & OPTION_REMOVE_CONTROLS) != 0; + + byte state; + byte lastStrong = ON; /* for default level & inverse Bidi */ + /* The following stacks are used to manage isolate sequences. Those + sequences may be nested, but obviously never more deeply than the + maximum explicit embedding level. + lastStack is the index of the last used entry in the stack. A value of -1 + means that there is no open isolate sequence. + lastStack is reset to -1 on paragraph boundaries. */ + /* The following stack contains the position of the initiator of + each open isolate sequence */ + int[] isolateStartStack= new int[MAX_EXPLICIT_LEVEL+1]; + /* The following stack contains the last known state before + encountering the initiator of an isolate sequence */ + byte[] previousStateStack = new byte[MAX_EXPLICIT_LEVEL+1]; + int stackLast=-1; + + if ((reorderingOptions & OPTION_STREAMING) != 0) + length = 0; + defaultParaLevel = (byte)(paraLevel & 1); + + if (isDefaultLevel) { + paras_level[0] = defaultParaLevel; + lastStrong = defaultParaLevel; + state = SEEKING_STRONG_FOR_PARA; + } else { + paras_level[0] = paraLevel; + state = NOT_SEEKING_STRONG; + } + /* count paragraphs and determine the paragraph level (P2..P3) */ + /* + * see comment on constant fields: + * the LEVEL_DEFAULT_XXX values are designed so that + * their low-order bit alone yields the intended default + */ + + for (i = 0; i < originalLength; /* i is incremented in the loop */) { + i0 = i; /* index of first code unit */ + uchar = UTF16.charAt(text, 0, originalLength, i); + i += UTF16.getCharCount(uchar); + i1 = i - 1; /* index of last code unit, gets the directional property */ + + dirProp = (byte)getCustomizedClass(uchar); + flags |= DirPropFlag(dirProp); + dirProps[i1] = dirProp; + if (i1 > i0) { /* set previous code units' properties to BN */ + flags |= DirPropFlag(BN); + do { + dirProps[--i1] = BN; + } while (i1 > i0); + } + if (removeBidiControls && IsBidiControlChar(uchar)) { + controlCount++; + } + if (dirProp == L) { + if (state == SEEKING_STRONG_FOR_PARA) { + paras_level[paraCount - 1] = 0; + state = NOT_SEEKING_STRONG; + } + else if (state == SEEKING_STRONG_FOR_FSI) { + if (stackLast <= MAX_EXPLICIT_LEVEL) { + /* no need for next statement, already set by default */ + /* dirProps[isolateStartStack[stackLast]] = LRI; */ + flags |= DirPropFlag(LRI); + } + state = LOOKING_FOR_PDI; + } + lastStrong = L; + continue; + } + if (dirProp == R || dirProp == AL) { + if (state == SEEKING_STRONG_FOR_PARA) { + paras_level[paraCount - 1] = 1; + state = NOT_SEEKING_STRONG; + } + else if (state == SEEKING_STRONG_FOR_FSI) { + if (stackLast <= MAX_EXPLICIT_LEVEL) { + dirProps[isolateStartStack[stackLast]] = RLI; + flags |= DirPropFlag(RLI); + } + state = LOOKING_FOR_PDI; + } + lastStrong = R; + if (dirProp == AL) + lastArabicPos = i - 1; + continue; + } + if (dirProp >= FSI && dirProp <= RLI) { /* FSI, LRI or RLI */ + stackLast++; + if (stackLast <= MAX_EXPLICIT_LEVEL) { + isolateStartStack[stackLast] = i - 1; + previousStateStack[stackLast] = state; + } + if (dirProp == FSI) { + dirProps[i-1] = LRI; /* default if no strong char */ + state = SEEKING_STRONG_FOR_FSI; + } + else + state = LOOKING_FOR_PDI; + continue; + } + if (dirProp == PDI) { + if (state == SEEKING_STRONG_FOR_FSI) { + if (stackLast <= MAX_EXPLICIT_LEVEL) { + /* no need for next statement, already set by default */ + /* dirProps[isolateStartStack[stackLast]] = LRI; */ + flags |= DirPropFlag(LRI); + } + } + if (stackLast >= 0) { + if (stackLast <= MAX_EXPLICIT_LEVEL) + state = previousStateStack[stackLast]; + stackLast--; + } + continue; + } + if (dirProp == B) { + if (i < originalLength && uchar == CR && text[i] == LF) /* do nothing on the CR */ + continue; + paras_limit[paraCount - 1] = i; + if (isDefaultLevelInverse && lastStrong == R) + paras_level[paraCount - 1] = 1; + if ((reorderingOptions & OPTION_STREAMING) != 0) { + /* When streaming, we only process whole paragraphs + thus some updates are only done on paragraph boundaries */ + length = i; /* i is index to next character */ + this.controlCount = controlCount; + } + if (i < originalLength) { /* B not last char in text */ + paraCount++; + checkParaCount(); /* check that there is enough memory for a new para entry */ + if (isDefaultLevel) { + paras_level[paraCount - 1] = defaultParaLevel; + state = SEEKING_STRONG_FOR_PARA; + lastStrong = defaultParaLevel; + } else { + paras_level[paraCount - 1] = paraLevel; + state = NOT_SEEKING_STRONG; + } + stackLast = -1; + } + continue; + } + } + /* +Ignore still open isolate sequences with overflow */ + if (stackLast > MAX_EXPLICIT_LEVEL) { + stackLast = MAX_EXPLICIT_LEVEL; + state=SEEKING_STRONG_FOR_FSI; /* to be on the safe side */ + } + /* Resolve direction of still unresolved open FSI sequences */ + while (stackLast >= 0) { + if (state == SEEKING_STRONG_FOR_FSI) { + /* no need for next statement, already set by default */ + /* dirProps[isolateStartStack[stackLast]] = LRI; */ + flags |= DirPropFlag(LRI); + break; + } + state = previousStateStack[stackLast]; + stackLast--; + } + /* When streaming, ignore text after the last paragraph separator */ + if ((reorderingOptions & OPTION_STREAMING) != 0) { + if (length < originalLength) + paraCount--; + } else { + paras_limit[paraCount - 1] = originalLength; + this.controlCount = controlCount; + } + /* For inverse bidi, default para direction is RTL if there is + a strong R or AL at either end of the paragraph */ + if (isDefaultLevelInverse && lastStrong == R) { + paras_level[paraCount - 1] = 1; + } + if (isDefaultLevel) { + paraLevel = paras_level[0]; + } + /* The following is needed to resolve the text direction for default level + paragraphs containing no strong character */ + for (i = 0; i < paraCount; i++) + flags |= DirPropFlagLR(paras_level[i]); + + if (orderParagraphsLTR && (flags & DirPropFlag(B)) != 0) { + flags |= DirPropFlag(L); + } + } + + /* determine the paragraph level at position index */ + byte GetParaLevelAt(int pindex) + { + if (defaultParaLevel == 0 || pindex < paras_limit[0]) + return paraLevel; + int i; + for (i = 1; i < paraCount; i++) + if (pindex < paras_limit[i]) + break; + if (i >= paraCount) + i = paraCount - 1; + return paras_level[i]; + } + + /* Functions for handling paired brackets ----------------------------------- */ + + /* In the isoRuns array, the first entry is used for text outside of any + isolate sequence. Higher entries are used for each more deeply nested + isolate sequence. isoRunLast is the index of the last used entry. The + openings array is used to note the data of opening brackets not yet + matched by a closing bracket, or matched but still susceptible to change + level. + Each isoRun entry contains the index of the first and + one-after-last openings entries for pending opening brackets it + contains. The next openings entry to use is the one-after-last of the + most deeply nested isoRun entry. + isoRun entries also contain their current embedding level and the last + encountered strong character, since these will be needed to resolve + the level of paired brackets. */ + + private void bracketInit(BracketData bd) { + bd.isoRunLast = 0; + bd.isoRuns[0] = new IsoRun(); + bd.isoRuns[0].start = 0; + bd.isoRuns[0].limit = 0; + bd.isoRuns[0].level = GetParaLevelAt(0); + bd.isoRuns[0].lastStrong = bd.isoRuns[0].lastBase = bd.isoRuns[0].contextDir = (byte)(GetParaLevelAt(0) & 1); + bd.isoRuns[0].contextPos = 0; + bd.openings = new Opening[SIMPLE_PARAS_COUNT]; + bd.isNumbersSpecial = reorderingMode == REORDER_NUMBERS_SPECIAL || + reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL; + } + + /* paragraph boundary */ + private void bracketProcessB(BracketData bd, byte level) { + bd.isoRunLast = 0; + bd.isoRuns[0].limit = 0; + bd.isoRuns[0].level = level; + bd.isoRuns[0].lastStrong = bd.isoRuns[0].lastBase = bd.isoRuns[0].contextDir = (byte)(level & 1); + bd.isoRuns[0].contextPos = 0; + } + + /* LRE, LRO, RLE, RLO, PDF */ + private void bracketProcessBoundary(BracketData bd, int lastCcPos, + byte contextLevel, byte embeddingLevel) { + IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + if ((DirPropFlag(dirProps[lastCcPos]) & MASK_ISO) != 0) /* after an isolate */ + return; + if (NoOverride(embeddingLevel) > NoOverride(contextLevel)) /* not a PDF */ + contextLevel = embeddingLevel; + pLastIsoRun.limit = pLastIsoRun.start; + pLastIsoRun.level = embeddingLevel; + pLastIsoRun.lastStrong = pLastIsoRun.lastBase = pLastIsoRun.contextDir = (byte)(contextLevel & 1); + pLastIsoRun.contextPos = lastCcPos; + } + + /* LRI or RLI */ + private void bracketProcessLRI_RLI(BracketData bd, byte level) { + IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + short lastLimit; + pLastIsoRun.lastBase = ON; + lastLimit = pLastIsoRun.limit; + bd.isoRunLast++; + pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + if (pLastIsoRun == null) + pLastIsoRun = bd.isoRuns[bd.isoRunLast] = new IsoRun(); + pLastIsoRun.start = pLastIsoRun.limit = lastLimit; + pLastIsoRun.level = level; + pLastIsoRun.lastStrong = pLastIsoRun.lastBase = pLastIsoRun.contextDir = (byte)(level & 1); + pLastIsoRun.contextPos = 0; + } + + /* PDI */ + private void bracketProcessPDI(BracketData bd) { + IsoRun pLastIsoRun; + bd.isoRunLast--; + pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + pLastIsoRun.lastBase = ON; + } + + /* newly found opening bracket: create an openings entry */ + private void bracketAddOpening(BracketData bd, char match, int position) { + IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + Opening pOpening; + if (pLastIsoRun.limit >= bd.openings.length) { /* no available new entry */ + Opening[] saveOpenings = bd.openings; + int count; + try { + count = bd.openings.length; + bd.openings = new Opening[count * 2]; + } catch (Exception e) { + throw new OutOfMemoryError("Failed to allocate memory for openings"); + } + System.arraycopy(saveOpenings, 0, bd.openings, 0, count); + } + pOpening = bd.openings[pLastIsoRun.limit]; + if (pOpening == null) + pOpening = bd.openings[pLastIsoRun.limit]= new Opening(); + pOpening.position = position; + pOpening.match = match; + pOpening.contextDir = pLastIsoRun.contextDir; + pOpening.contextPos = pLastIsoRun.contextPos; + pOpening.flags = 0; + pLastIsoRun.limit++; + } + + /* change N0c1 to N0c2 when a preceding bracket is assigned the embedding level */ + private void fixN0c(BracketData bd, int openingIndex, int newPropPosition, byte newProp) { + /* This function calls itself recursively */ + IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + Opening qOpening; + int k, openingPosition, closingPosition; + for (k = openingIndex+1; k < pLastIsoRun.limit; k++) { + qOpening = bd.openings[k]; + if (qOpening.match >= 0) /* not an N0c match */ + continue; + if (newPropPosition < qOpening.contextPos) + break; + if (newPropPosition >= qOpening.position) + continue; + if (newProp == qOpening.contextDir) + break; + openingPosition = qOpening.position; + dirProps[openingPosition] = newProp; + closingPosition = -(qOpening.match); + dirProps[closingPosition] = newProp; + qOpening.match = 0; /* prevent further changes */ + fixN0c(bd, k, openingPosition, newProp); + fixN0c(bd, k, closingPosition, newProp); + } + } + + /* process closing bracket; return L or R if N0b or N0c, ON if N0d */ + private byte bracketProcessClosing(BracketData bd, int openIdx, int position) { + IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + Opening pOpening, qOpening; + byte direction; + boolean stable; + byte newProp; + pOpening = bd.openings[openIdx]; + direction = (byte)(pLastIsoRun.level & 1); + stable = true; /* assume stable until proved otherwise */ + + /* The stable flag is set when brackets are paired and their + level is resolved and cannot be changed by what will be + found later in the source string. + An unstable match can occur only when applying N0c, where + the resolved level depends on the preceding context, and + this context may be affected by text occurring later. + Example: RTL paragraph containing: abc[(latin) HEBREW] + When the closing parenthesis is encountered, it appears + that N0c1 must be applied since 'abc' sets an opposite + direction context and both parentheses receive level 2. + However, when the closing square bracket is processed, + N0b applies because of 'HEBREW' being included within the + brackets, thus the square brackets are treated like R and + receive level 1. However, this changes the preceding + context of the opening parenthesis, and it now appears + that N0c2 must be applied to the parentheses rather than + N0c1. */ + + if ((direction == 0 && (pOpening.flags & FOUND_L) > 0) || + (direction == 1 && (pOpening.flags & FOUND_R) > 0)) { /* N0b */ + newProp = direction; + } + else if ((pOpening.flags & (FOUND_L | FOUND_R)) != 0) { /* N0c */ + /* it is stable if there is no preceding text or in + conditions too complicated and not worth checking */ + stable = (openIdx == pLastIsoRun.start); + if (direction != pOpening.contextDir) + newProp = pOpening.contextDir; /* N0c1 */ + else + newProp = direction; /* N0c2 */ + } else { + /* forget this and any brackets nested within this pair */ + pLastIsoRun.limit = (short)openIdx; + return ON; /* N0d */ + } + dirProps[pOpening.position] = newProp; + dirProps[position] = newProp; + /* Update nested N0c pairs that may be affected */ + fixN0c(bd, openIdx, pOpening.position, newProp); + if (stable) { + pLastIsoRun.limit = (short)openIdx; /* forget any brackets nested within this pair */ + /* remove lower located synonyms if any */ + while (pLastIsoRun.limit > pLastIsoRun.start && + bd.openings[pLastIsoRun.limit - 1].position == pOpening.position) + pLastIsoRun.limit--; + } else { + int k; + pOpening.match = -position; + /* neutralize lower located synonyms if any */ + k = openIdx - 1; + while (k >= pLastIsoRun.start && + bd.openings[k].position == pOpening.position) + bd.openings[k--].match = 0; + /* neutralize any unmatched opening between the current pair; + this will also neutralize higher located synonyms if any */ + for (k = openIdx + 1; k < pLastIsoRun.limit; k++) { + qOpening =bd.openings[k]; + if (qOpening.position >= position) + break; + if (qOpening.match > 0) + qOpening.match = 0; + } + } + return newProp; + } + + /* handle strong characters, digits and candidates for closing brackets */ + private void bracketProcessChar(BracketData bd, int position) { + IsoRun pLastIsoRun = bd.isoRuns[bd.isoRunLast]; + byte dirProp, newProp; + byte level; + dirProp = dirProps[position]; + if (dirProp == ON) { + char c, match; + int idx; + /* First see if it is a matching closing bracket. Hopefully, this is + more efficient than checking if it is a closing bracket at all */ + c = text[position]; + for (idx = pLastIsoRun.limit - 1; idx >= pLastIsoRun.start; idx--) { + if (bd.openings[idx].match != c) + continue; + /* We have a match */ + newProp = bracketProcessClosing(bd, idx, position); + if(newProp == ON) { /* N0d */ + c = 0; /* prevent handling as an opening */ + break; + } + pLastIsoRun.lastBase = ON; + pLastIsoRun.contextDir = newProp; + pLastIsoRun.contextPos = position; + level = levels[position]; + if ((level & LEVEL_OVERRIDE) != 0) { /* X4, X5 */ + short flag; + int i; + newProp = (byte)(level & 1); + pLastIsoRun.lastStrong = newProp; + flag = (short)DirPropFlag(newProp); + for (i = pLastIsoRun.start; i < idx; i++) + bd.openings[i].flags |= flag; + /* matching brackets are not overridden by LRO/RLO */ + levels[position] &= ~LEVEL_OVERRIDE; + } + /* matching brackets are not overridden by LRO/RLO */ + levels[bd.openings[idx].position] &= ~LEVEL_OVERRIDE; + return; + } + /* We get here only if the ON character is not a matching closing + bracket or it is a case of N0d */ + /* Now see if it is an opening bracket */ + if (c != 0) { + match = (char)UCharacter.getBidiPairedBracket(c); /* get the matching char */ + } else { + match = 0; + } + if (match != c && /* has a matching char */ + UCharacter.getIntPropertyValue(c, BIDI_PAIRED_BRACKET_TYPE) == + /* opening bracket */ BidiPairedBracketType.OPEN) { + /* special case: process synonyms + create an opening entry for each synonym */ + if (match == 0x232A) { /* RIGHT-POINTING ANGLE BRACKET */ + bracketAddOpening(bd, (char)0x3009, position); + } + else if (match == 0x3009) { /* RIGHT ANGLE BRACKET */ + bracketAddOpening(bd, (char)0x232A, position); + } + bracketAddOpening(bd, match, position); + } + } + level = levels[position]; + if ((level & LEVEL_OVERRIDE) != 0) { /* X4, X5 */ + newProp = (byte)(level & 1); + if (dirProp != S && dirProp != WS && dirProp != ON) + dirProps[position] = newProp; + pLastIsoRun.lastBase = newProp; + pLastIsoRun.lastStrong = newProp; + pLastIsoRun.contextDir = newProp; + pLastIsoRun.contextPos = position; + } + else if (dirProp <= R || dirProp == AL) { + newProp = DirFromStrong(dirProp); + pLastIsoRun.lastBase = dirProp; + pLastIsoRun.lastStrong = dirProp; + pLastIsoRun.contextDir = newProp; + pLastIsoRun.contextPos = position; + } + else if(dirProp == EN) { + pLastIsoRun.lastBase = EN; + if (pLastIsoRun.lastStrong == L) { + newProp = L; /* W7 */ + if (!bd.isNumbersSpecial) + dirProps[position] = ENL; + pLastIsoRun.contextDir = L; + pLastIsoRun.contextPos = position; + } + else { + newProp = R; /* N0 */ + if (pLastIsoRun.lastStrong == AL) + dirProps[position] = AN; /* W2 */ + else + dirProps[position] = ENR; + pLastIsoRun.contextDir = R; + pLastIsoRun.contextPos = position; + } + } + else if (dirProp == AN) { + newProp = R; /* N0 */ + pLastIsoRun.lastBase = AN; + pLastIsoRun.contextDir = R; + pLastIsoRun.contextPos = position; + } + else if (dirProp == NSM) { + /* if the last real char was ON, change NSM to ON so that it + will stay ON even if the last real char is a bracket which + may be changed to L or R */ + newProp = pLastIsoRun.lastBase; + if (newProp == ON) + dirProps[position] = newProp; + } + else { + newProp = dirProp; + pLastIsoRun.lastBase = dirProp; + } + if (newProp <= R || newProp == AL) { + int i; + short flag = (short)DirPropFlag(DirFromStrong(newProp)); + for (i = pLastIsoRun.start; i < pLastIsoRun.limit; i++) + if (position > bd.openings[i].position) + bd.openings[i].flags |= flag; + } + } + + /* perform (X1)..(X9) ------------------------------------------------------- */ + + /* determine if the text is mixed-directional or single-directional */ + private byte directionFromFlags() { + + /* if the text contains AN and neutrals, then some neutrals may become RTL */ + if (!((flags & MASK_RTL) != 0 || + ((flags & DirPropFlag(AN)) != 0 && + (flags & MASK_POSSIBLE_N) != 0))) { + return LTR; + } else if ((flags & MASK_LTR) == 0) { + return RTL; + } else { + return MIXED; + } + } + + /* + * Resolve the explicit levels as specified by explicit embedding codes. + * Recalculate the flags to have them reflect the real properties + * after taking the explicit embeddings into account. + * + * The BiDi algorithm is designed to result in the same behavior whether embedding + * levels are externally specified (from "styled text", supposedly the preferred + * method) or set by explicit embedding codes (LRx, RLx, PDF, FSI, PDI) in the plain text. + * That is why (X9) instructs to remove all not-isolate explicit codes (and BN). + * However, in a real implementation, the removal of these codes and their index + * positions in the plain text is undesirable since it would result in + * reallocated, reindexed text. + * Instead, this implementation leaves the codes in there and just ignores them + * in the subsequent processing. + * In order to get the same reordering behavior, positions with a BN or a not-isolate + * explicit embedding code just get the same level assigned as the last "real" + * character. + * + * Some implementations, not this one, then overwrite some of these + * directionality properties at "real" same-level-run boundaries by + * L or R codes so that the resolution of weak types can be performed on the + * entire paragraph at once instead of having to parse it once more and + * perform that resolution on same-level-runs. + * This limits the scope of the implicit rules in effectively + * the same way as the run limits. + * + * Instead, this implementation does not modify these codes, except for + * paired brackets whose properties (ON) may be replaced by L or R. + * On one hand, the paragraph has to be scanned for same-level-runs, but + * on the other hand, this saves another loop to reset these codes, + * or saves making and modifying a copy of dirProps[]. + * + * + * Note that (Pn) and (Xn) changed significantly from version 4 of the BiDi algorithm. + * + * + * Handling the stack of explicit levels (Xn): + * + * With the BiDi stack of explicit levels, as pushed with each + * LRE, RLE, LRO, RLO, LRI, RLI and FSI and popped with each PDF and PDI, + * the explicit level must never exceed MAX_EXPLICIT_LEVEL. + * + * In order to have a correct push-pop semantics even in the case of overflows, + * overflow counters and a valid isolate counter are used as described in UAX#9 + * section 3.3.2 "Explicit Levels and Directions". + * + * This implementation assumes that MAX_EXPLICIT_LEVEL is odd. + * + * Returns the direction + * + */ + private byte resolveExplicitLevels() { + int i = 0; + byte dirProp; + byte level = GetParaLevelAt(0); + byte dirct; + isolateCount = 0; + + /* determine if the text is mixed-directional or single-directional */ + dirct = directionFromFlags(); + + /* we may not need to resolve any explicit levels */ + if (dirct != MIXED) { + /* not mixed directionality: levels don't matter - trailingWSStart will be 0 */ + return dirct; + } + + if (reorderingMode > REORDER_LAST_LOGICAL_TO_VISUAL) { + /* inverse BiDi: mixed, but all characters are at the same embedding level */ + /* set all levels to the paragraph level */ + int paraIndex, start, limit; + for (paraIndex = 0; paraIndex < paraCount; paraIndex++) { + if (paraIndex == 0) + start = 0; + else + start = paras_limit[paraIndex - 1]; + limit = paras_limit[paraIndex]; + level = paras_level[paraIndex]; + for (i = start; i < limit; i++) + levels[i] =level; + } + return dirct; /* no bracket matching for inverse BiDi */ + } + if ((flags & (MASK_EXPLICIT | MASK_ISO)) == 0) { + /* no embeddings, set all levels to the paragraph level */ + /* we still have to perform bracket matching */ + int paraIndex, start, limit; + BracketData bracketData = new BracketData(); + bracketInit(bracketData); + for (paraIndex = 0; paraIndex < paraCount; paraIndex++) { + if (paraIndex == 0) + start = 0; + else + start = paras_limit[paraIndex-1]; + limit = paras_limit[paraIndex]; + level = paras_level[paraIndex]; + for (i = start; i < limit; i++) { + levels[i] = level; + dirProp = dirProps[i]; + if (dirProp == BN) + continue; + if (dirProp == B) { + if ((i + 1) < length) { + if (text[i] == CR && text[i + 1] == LF) + continue; /* skip CR when followed by LF */ + bracketProcessB(bracketData, level); + } + continue; + } + bracketProcessChar(bracketData, i); + } + } + return dirct; + } + /* continue to perform (Xn) */ + + /* (X1) level is set for all codes, embeddingLevel keeps track of the push/pop operations */ + /* both variables may carry the LEVEL_OVERRIDE flag to indicate the override status */ + byte embeddingLevel = level, newLevel; + byte previousLevel = level; /* previous level for regular (not CC) characters */ + int lastCcPos = 0; /* index of last effective LRx,RLx, PDx */ + + /* The following stack remembers the embedding level and the ISOLATE flag of level runs. + stackLast points to its current entry. */ + short[] stack = new short[MAX_EXPLICIT_LEVEL + 2]; /* we never push anything >= MAX_EXPLICIT_LEVEL + but we need one more entry as base */ + int stackLast = 0; + int overflowIsolateCount = 0; + int overflowEmbeddingCount = 0; + int validIsolateCount = 0; + BracketData bracketData = new BracketData(); + bracketInit(bracketData); + stack[0] = level; /* initialize base entry to para level, no override, no isolate */ + + /* recalculate the flags */ + flags = 0; + + for (i = 0; i < length; i++) { + dirProp = dirProps[i]; + switch (dirProp) { + case LRE: + case RLE: + case LRO: + case RLO: + /* (X2, X3, X4, X5) */ + flags |= DirPropFlag(BN); + levels[i] = previousLevel; + if (dirProp == LRE || dirProp == LRO) { + /* least greater even level */ + newLevel = (byte)((embeddingLevel+2) & ~(LEVEL_OVERRIDE | 1)); + } else { + /* least greater odd level */ + newLevel = (byte)((NoOverride(embeddingLevel) + 1) | 1); + } + if (newLevel <= MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 && + overflowEmbeddingCount == 0) { + lastCcPos = i; + embeddingLevel = newLevel; + if (dirProp == LRO || dirProp == RLO) + embeddingLevel |= LEVEL_OVERRIDE; + stackLast++; + stack[stackLast] = embeddingLevel; + /* we don't need to set LEVEL_OVERRIDE off for LRE and RLE + since this has already been done for newLevel which is + the source for embeddingLevel. + */ + } else { + if (overflowIsolateCount == 0) + overflowEmbeddingCount++; + } + break; + case PDF: + /* (X7) */ + flags |= DirPropFlag(BN); + levels[i] = previousLevel; + /* handle all the overflow cases first */ + if (overflowIsolateCount > 0) { + break; + } + if (overflowEmbeddingCount > 0) { + overflowEmbeddingCount--; + break; + } + if (stackLast > 0 && stack[stackLast] < ISOLATE) { /* not an isolate entry */ + lastCcPos = i; + stackLast--; + embeddingLevel = (byte)stack[stackLast]; + } + break; + case LRI: + case RLI: + flags |= DirPropFlag(ON) | DirPropFlagLR(embeddingLevel); + levels[i] = NoOverride(embeddingLevel); + if (NoOverride(embeddingLevel) != NoOverride(previousLevel)) { + bracketProcessBoundary(bracketData, lastCcPos, + previousLevel, embeddingLevel); + flags |= DirPropFlagMultiRuns; + } + previousLevel = embeddingLevel; + /* (X5a, X5b) */ + if (dirProp == LRI) + /* least greater even level */ + newLevel=(byte)((embeddingLevel+2)&~(LEVEL_OVERRIDE|1)); + else + /* least greater odd level */ + newLevel=(byte)((NoOverride(embeddingLevel)+1)|1); + if (newLevel <= MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 + && overflowEmbeddingCount == 0) { + flags |= DirPropFlag(dirProp); + lastCcPos = i; + validIsolateCount++; + if (validIsolateCount > isolateCount) + isolateCount = validIsolateCount; + embeddingLevel = newLevel; + /* we can increment stackLast without checking because newLevel + will exceed UBIDI_MAX_EXPLICIT_LEVEL before stackLast overflows */ + stackLast++; + stack[stackLast] = (short)(embeddingLevel + ISOLATE); + bracketProcessLRI_RLI(bracketData, embeddingLevel); + } else { + /* make it WS so that it is handled by adjustWSLevels() */ + dirProps[i] = WS; + overflowIsolateCount++; + } + break; + case PDI: + if (NoOverride(embeddingLevel) != NoOverride(previousLevel)) { + bracketProcessBoundary(bracketData, lastCcPos, + previousLevel, embeddingLevel); + flags |= DirPropFlagMultiRuns; + } + /* (X6a) */ + if (overflowIsolateCount > 0) { + overflowIsolateCount--; + /* make it WS so that it is handled by adjustWSLevels() */ + dirProps[i] = WS; + } + else if (validIsolateCount > 0) { + flags |= DirPropFlag(PDI); + lastCcPos = i; + overflowEmbeddingCount = 0; + while (stack[stackLast] < ISOLATE) /* pop embedding entries */ + stackLast--; /* until the last isolate entry */ + stackLast--; /* pop also the last isolate entry */ + validIsolateCount--; + bracketProcessPDI(bracketData); + } else + /* make it WS so that it is handled by adjustWSLevels() */ + dirProps[i] = WS; + embeddingLevel = (byte)(stack[stackLast] & ~ISOLATE); + flags |= DirPropFlag(ON) | DirPropFlagLR(embeddingLevel); + previousLevel = embeddingLevel; + levels[i] = NoOverride(embeddingLevel); + break; + case B: + flags |= DirPropFlag(B); + levels[i] = GetParaLevelAt(i); + if ((i + 1) < length) { + if (text[i] == CR && text[i + 1] == LF) + break; /* skip CR when followed by LF */ + overflowEmbeddingCount = overflowIsolateCount = 0; + validIsolateCount = 0; + stackLast = 0; + previousLevel = embeddingLevel = GetParaLevelAt(i + 1); + stack[0] = embeddingLevel; /* initialize base entry to para level, no override, no isolate */ + bracketProcessB(bracketData, embeddingLevel); + } + break; + case BN: + /* BN, LRE, RLE, and PDF are supposed to be removed (X9) */ + /* they will get their levels set correctly in adjustWSLevels() */ + levels[i] = previousLevel; + flags |= DirPropFlag(BN); + break; + default: + /* all other types are normal characters and get the "real" level */ + if (NoOverride(embeddingLevel) != NoOverride(previousLevel)) { + bracketProcessBoundary(bracketData, lastCcPos, + previousLevel, embeddingLevel); + flags |= DirPropFlagMultiRuns; + if ((embeddingLevel & LEVEL_OVERRIDE) != 0) + flags |= DirPropFlagO(embeddingLevel); + else + flags |= DirPropFlagE(embeddingLevel); + } + previousLevel = embeddingLevel; + levels[i] = embeddingLevel; + bracketProcessChar(bracketData, i); + /* the dirProp may have been changed in bracketProcessChar() */ + flags |= DirPropFlag(dirProps[i]); + break; + } + } + if ((flags & MASK_EMBEDDING) != 0) { + flags |= DirPropFlagLR(paraLevel); + } + if (orderParagraphsLTR && (flags & DirPropFlag(B)) != 0) { + flags |= DirPropFlag(L); + } + /* again, determine if the text is mixed-directional or single-directional */ + dirct = directionFromFlags(); + + return dirct; + } + + /* + * Use a pre-specified embedding levels array: + * + * Adjust the directional properties for overrides (->LEVEL_OVERRIDE), + * ignore all explicit codes (X9), + * and check all the preset levels. + * + * Recalculate the flags to have them reflect the real properties + * after taking the explicit embeddings into account. + */ + private byte checkExplicitLevels() { + byte dirProp; + int i; + int isolateCount = 0; + + this.flags = 0; /* collect all directionalities in the text */ + byte level; + this.isolateCount = 0; + + for (i = 0; i < length; ++i) { + if (levels[i] == 0) { + levels[i] = paraLevel; + } + + // for backward compatibility + if (MAX_EXPLICIT_LEVEL < (levels[i]&0x7f)) { + if ((levels[i] & LEVEL_OVERRIDE) != 0) { + levels[i] = (byte)(paraLevel|LEVEL_OVERRIDE); + } else { + levels[i] = paraLevel; + } + } + + level = levels[i]; + dirProp = dirProps[i]; + if (dirProp == LRI || dirProp == RLI) { + isolateCount++; + if (isolateCount > this.isolateCount) + this.isolateCount = isolateCount; + } + else if (dirProp == PDI) { + isolateCount--; + } else if (dirProp == B) { + isolateCount = 0; + } + if ((level & LEVEL_OVERRIDE) != 0) { + /* keep the override flag in levels[i] but adjust the flags */ + level &= ~LEVEL_OVERRIDE; /* make the range check below simpler */ + flags |= DirPropFlagO(level); + } else { + /* set the flags */ + flags |= DirPropFlagE(level) | DirPropFlag(dirProp); + } + if ((level < GetParaLevelAt(i) && + !((0 == level) && (dirProp == B))) || + (MAX_EXPLICIT_LEVEL < level)) { + /* level out of bounds */ + throw new IllegalArgumentException("level " + level + + " out of bounds at " + i); + } + } + if ((flags & MASK_EMBEDDING) != 0) { + flags |= DirPropFlagLR(paraLevel); + } + /* determine if the text is mixed-directional or single-directional */ + return directionFromFlags(); + } + + /* *******************************************************************/ + /* The Properties state machine table */ + /* *******************************************************************/ + /* */ + /* All table cells are 8 bits: */ + /* bits 0..4: next state */ + /* bits 5..7: action to perform (if > 0) */ + /* */ + /* Cells may be of format "n" where n represents the next state */ + /* (except for the rightmost column). */ + /* Cells may also be of format "_(x,y)" where x represents an action */ + /* to perform and y represents the next state. */ + /* */ + /* *******************************************************************/ + /* Definitions and type for properties state tables */ + /* *******************************************************************/ + private static final int IMPTABPROPS_COLUMNS = 16; + private static final int IMPTABPROPS_RES = IMPTABPROPS_COLUMNS - 1; + private static short GetStateProps(short cell) { + return (short)(cell & 0x1f); + } + private static short GetActionProps(short cell) { + return (short)(cell >> 5); + } + + private static final short groupProp[] = /* dirProp regrouped */ + { + /* L R EN ES ET AN CS B S WS ON LRE LRO AL RLE RLO PDF NSM BN FSI LRI RLI PDI ENL ENR */ + 0, 1, 2, 7, 8, 3, 9, 6, 5, 4, 4, 10, 10, 12, 10, 10, 10, 11, 10, 4, 4, 4, 4, 13, 14 + }; + private static final short _L = 0; + private static final short _R = 1; + private static final short _EN = 2; + private static final short _AN = 3; + private static final short _ON = 4; + private static final short _S = 5; + private static final short _B = 6; /* reduced dirProp */ + + /* *******************************************************************/ + /* */ + /* PROPERTIES STATE TABLE */ + /* */ + /* In table impTabProps, */ + /* - the ON column regroups ON and WS, FSI, RLI, LRI and PDI */ + /* - the BN column regroups BN, LRE, RLE, LRO, RLO, PDF */ + /* - the Res column is the reduced property assigned to a run */ + /* */ + /* Action 1: process current run1, init new run1 */ + /* 2: init new run2 */ + /* 3: process run1, process run2, init new run1 */ + /* 4: process run1, set run1=run2, init new run2 */ + /* */ + /* Notes: */ + /* 1) This table is used in resolveImplicitLevels(). */ + /* 2) This table triggers actions when there is a change in the Bidi*/ + /* property of incoming characters (action 1). */ + /* 3) Most such property sequences are processed immediately (in */ + /* fact, passed to processPropertySeq(). */ + /* 4) However, numbers are assembled as one sequence. This means */ + /* that undefined situations (like CS following digits, until */ + /* it is known if the next char will be a digit) are held until */ + /* following chars define them. */ + /* Example: digits followed by CS, then comes another CS or ON; */ + /* the digits will be processed, then the CS assigned */ + /* as the start of an ON sequence (action 3). */ + /* 5) There are cases where more than one sequence must be */ + /* processed, for instance digits followed by CS followed by L: */ + /* the digits must be processed as one sequence, and the CS */ + /* must be processed as an ON sequence, all this before starting */ + /* assembling chars for the opening L sequence. */ + /* */ + /* */ + private static final short impTabProps[][] = + { +/* L, R, EN, AN, ON, S, B, ES, ET, CS, BN, NSM, AL, ENL, ENR, Res */ +/* 0 Init */ { 1, 2, 4, 5, 7, 15, 17, 7, 9, 7, 0, 7, 3, 18, 21, _ON }, +/* 1 L */ { 1, 32+2, 32+4, 32+5, 32+7, 32+15, 32+17, 32+7, 32+9, 32+7, 1, 1, 32+3, 32+18, 32+21, _L }, +/* 2 R */ { 32+1, 2, 32+4, 32+5, 32+7, 32+15, 32+17, 32+7, 32+9, 32+7, 2, 2, 32+3, 32+18, 32+21, _R }, +/* 3 AL */ { 32+1, 32+2, 32+6, 32+6, 32+8, 32+16, 32+17, 32+8, 32+8, 32+8, 3, 3, 3, 32+18, 32+21, _R }, +/* 4 EN */ { 32+1, 32+2, 4, 32+5, 32+7, 32+15, 32+17, 64+10, 11, 64+10, 4, 4, 32+3, 18, 21, _EN }, +/* 5 AN */ { 32+1, 32+2, 32+4, 5, 32+7, 32+15, 32+17, 32+7, 32+9, 64+12, 5, 5, 32+3, 32+18, 32+21, _AN }, +/* 6 AL:EN/AN */ { 32+1, 32+2, 6, 6, 32+8, 32+16, 32+17, 32+8, 32+8, 64+13, 6, 6, 32+3, 18, 21, _AN }, +/* 7 ON */ { 32+1, 32+2, 32+4, 32+5, 7, 32+15, 32+17, 7, 64+14, 7, 7, 7, 32+3, 32+18, 32+21, _ON }, +/* 8 AL:ON */ { 32+1, 32+2, 32+6, 32+6, 8, 32+16, 32+17, 8, 8, 8, 8, 8, 32+3, 32+18, 32+21, _ON }, +/* 9 ET */ { 32+1, 32+2, 4, 32+5, 7, 32+15, 32+17, 7, 9, 7, 9, 9, 32+3, 18, 21, _ON }, +/*10 EN+ES/CS */ { 96+1, 96+2, 4, 96+5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 10, 128+7, 96+3, 18, 21, _EN }, +/*11 EN+ET */ { 32+1, 32+2, 4, 32+5, 32+7, 32+15, 32+17, 32+7, 11, 32+7, 11, 11, 32+3, 18, 21, _EN }, +/*12 AN+CS */ { 96+1, 96+2, 96+4, 5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 12, 128+7, 96+3, 96+18, 96+21, _AN }, +/*13 AL:EN/AN+CS */ { 96+1, 96+2, 6, 6, 128+8, 96+16, 96+17, 128+8, 128+8, 128+8, 13, 128+8, 96+3, 18, 21, _AN }, +/*14 ON+ET */ { 32+1, 32+2, 128+4, 32+5, 7, 32+15, 32+17, 7, 14, 7, 14, 14, 32+3,128+18,128+21, _ON }, +/*15 S */ { 32+1, 32+2, 32+4, 32+5, 32+7, 15, 32+17, 32+7, 32+9, 32+7, 15, 32+7, 32+3, 32+18, 32+21, _S }, +/*16 AL:S */ { 32+1, 32+2, 32+6, 32+6, 32+8, 16, 32+17, 32+8, 32+8, 32+8, 16, 32+8, 32+3, 32+18, 32+21, _S }, +/*17 B */ { 32+1, 32+2, 32+4, 32+5, 32+7, 32+15, 17, 32+7, 32+9, 32+7, 17, 32+7, 32+3, 32+18, 32+21, _B }, +/*18 ENL */ { 32+1, 32+2, 18, 32+5, 32+7, 32+15, 32+17, 64+19, 20, 64+19, 18, 18, 32+3, 18, 21, _L }, +/*19 ENL+ES/CS */ { 96+1, 96+2, 18, 96+5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 19, 128+7, 96+3, 18, 21, _L }, +/*20 ENL+ET */ { 32+1, 32+2, 18, 32+5, 32+7, 32+15, 32+17, 32+7, 20, 32+7, 20, 20, 32+3, 18, 21, _L }, +/*21 ENR */ { 32+1, 32+2, 21, 32+5, 32+7, 32+15, 32+17, 64+22, 23, 64+22, 21, 21, 32+3, 18, 21, _AN }, +/*22 ENR+ES/CS */ { 96+1, 96+2, 21, 96+5, 128+7, 96+15, 96+17, 128+7,128+14, 128+7, 22, 128+7, 96+3, 18, 21, _AN }, +/*23 ENR+ET */ { 32+1, 32+2, 21, 32+5, 32+7, 32+15, 32+17, 32+7, 23, 32+7, 23, 23, 32+3, 18, 21, _AN } + }; + + /* *******************************************************************/ + /* The levels state machine tables */ + /* *******************************************************************/ + /* */ + /* All table cells are 8 bits: */ + /* bits 0..3: next state */ + /* bits 4..7: action to perform (if > 0) */ + /* */ + /* Cells may be of format "n" where n represents the next state */ + /* (except for the rightmost column). */ + /* Cells may also be of format "_(x,y)" where x represents an action */ + /* to perform and y represents the next state. */ + /* */ + /* This format limits each table to 16 states each and to 15 actions.*/ + /* */ + /* *******************************************************************/ + /* Definitions and type for levels state tables */ + /* *******************************************************************/ + private static final int IMPTABLEVELS_COLUMNS = _B + 2; + private static final int IMPTABLEVELS_RES = IMPTABLEVELS_COLUMNS - 1; + private static short GetState(byte cell) { return (short)(cell & 0x0f); } + private static short GetAction(byte cell) { return (short)(cell >> 4); } + + private static class ImpTabPair { + byte[][][] imptab; + short[][] impact; + + ImpTabPair(byte[][] table1, byte[][] table2, + short[] act1, short[] act2) { + imptab = new byte[][][] {table1, table2}; + impact = new short[][] {act1, act2}; + } + } + + /* *******************************************************************/ + /* */ + /* LEVELS STATE TABLES */ + /* */ + /* In all levels state tables, */ + /* - state 0 is the initial state */ + /* - the Res column is the increment to add to the text level */ + /* for this property sequence. */ + /* */ + /* The impact arrays for each table of a pair map the local action */ + /* numbers of the table to the total list of actions. For instance, */ + /* action 2 in a given table corresponds to the action number which */ + /* appears in entry [2] of the impact array for that table. */ + /* The first entry of all impact arrays must be 0. */ + /* */ + /* Action 1: init conditional sequence */ + /* 2: prepend conditional sequence to current sequence */ + /* 3: set ON sequence to new level - 1 */ + /* 4: init EN/AN/ON sequence */ + /* 5: fix EN/AN/ON sequence followed by R */ + /* 6: set previous level sequence to level 2 */ + /* */ + /* Notes: */ + /* 1) These tables are used in processPropertySeq(). The input */ + /* is property sequences as determined by resolveImplicitLevels. */ + /* 2) Most such property sequences are processed immediately */ + /* (levels are assigned). */ + /* 3) However, some sequences cannot be assigned a final level till */ + /* one or more following sequences are received. For instance, */ + /* ON following an R sequence within an even-level paragraph. */ + /* If the following sequence is R, the ON sequence will be */ + /* assigned basic run level+1, and so will the R sequence. */ + /* 4) S is generally handled like ON, since its level will be fixed */ + /* to paragraph level in adjustWSLevels(). */ + /* */ + + private static final byte impTabL_DEFAULT[][] = /* Even paragraph level */ + /* In this table, conditional sequences receive the lower possible level + until proven otherwise. + */ + { + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 0, 1, 0, 2, 0, 0, 0, 0 }, + /* 1 : R */ { 0, 1, 3, 3, 0x14, 0x14, 0, 1 }, + /* 2 : AN */ { 0, 1, 0, 2, 0x15, 0x15, 0, 2 }, + /* 3 : R+EN/AN */ { 0, 1, 3, 3, 0x14, 0x14, 0, 2 }, + /* 4 : R+ON */ { 0, 0x21, 0x33, 0x33, 4, 4, 0, 0 }, + /* 5 : AN+ON */ { 0, 0x21, 0, 0x32, 5, 5, 0, 0 } + }; + + private static final byte impTabR_DEFAULT[][] = /* Odd paragraph level */ + /* In this table, conditional sequences receive the lower possible level + until proven otherwise. + */ + { + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 1, 0, 2, 2, 0, 0, 0, 0 }, + /* 1 : L */ { 1, 0, 1, 3, 0x14, 0x14, 0, 1 }, + /* 2 : EN/AN */ { 1, 0, 2, 2, 0, 0, 0, 1 }, + /* 3 : L+AN */ { 1, 0, 1, 3, 5, 5, 0, 1 }, + /* 4 : L+ON */ { 0x21, 0, 0x21, 3, 4, 4, 0, 0 }, + /* 5 : L+AN+ON */ { 1, 0, 1, 3, 5, 5, 0, 0 } + }; + + private static final short[] impAct0 = {0,1,2,3,4}; + + private static final ImpTabPair impTab_DEFAULT = new ImpTabPair( + impTabL_DEFAULT, impTabR_DEFAULT, impAct0, impAct0); + + private static final byte impTabL_NUMBERS_SPECIAL[][] = { /* Even paragraph level */ + /* In this table, conditional sequences receive the lower possible + level until proven otherwise. + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 0, 2, 0x11, 0x11, 0, 0, 0, 0 }, + /* 1 : L+EN/AN */ { 0, 0x42, 1, 1, 0, 0, 0, 0 }, + /* 2 : R */ { 0, 2, 4, 4, 0x13, 0x13, 0, 1 }, + /* 3 : R+ON */ { 0, 0x22, 0x34, 0x34, 3, 3, 0, 0 }, + /* 4 : R+EN/AN */ { 0, 2, 4, 4, 0x13, 0x13, 0, 2 } + }; + private static final ImpTabPair impTab_NUMBERS_SPECIAL = new ImpTabPair( + impTabL_NUMBERS_SPECIAL, impTabR_DEFAULT, impAct0, impAct0); + + private static final byte impTabL_GROUP_NUMBERS_WITH_R[][] = { + /* In this table, EN/AN+ON sequences receive levels as if associated with R + until proven that there is L or sor/eor on both sides. AN is handled like EN. + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 init */ { 0, 3, 0x11, 0x11, 0, 0, 0, 0 }, + /* 1 EN/AN */ { 0x20, 3, 1, 1, 2, 0x20, 0x20, 2 }, + /* 2 EN/AN+ON */ { 0x20, 3, 1, 1, 2, 0x20, 0x20, 1 }, + /* 3 R */ { 0, 3, 5, 5, 0x14, 0, 0, 1 }, + /* 4 R+ON */ { 0x20, 3, 5, 5, 4, 0x20, 0x20, 1 }, + /* 5 R+EN/AN */ { 0, 3, 5, 5, 0x14, 0, 0, 2 } + }; + private static final byte impTabR_GROUP_NUMBERS_WITH_R[][] = { + /* In this table, EN/AN+ON sequences receive levels as if associated with R + until proven that there is L on both sides. AN is handled like EN. + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 init */ { 2, 0, 1, 1, 0, 0, 0, 0 }, + /* 1 EN/AN */ { 2, 0, 1, 1, 0, 0, 0, 1 }, + /* 2 L */ { 2, 0, 0x14, 0x14, 0x13, 0, 0, 1 }, + /* 3 L+ON */ { 0x22, 0, 4, 4, 3, 0, 0, 0 }, + /* 4 L+EN/AN */ { 0x22, 0, 4, 4, 3, 0, 0, 1 } + }; + private static final ImpTabPair impTab_GROUP_NUMBERS_WITH_R = new + ImpTabPair(impTabL_GROUP_NUMBERS_WITH_R, + impTabR_GROUP_NUMBERS_WITH_R, impAct0, impAct0); + + private static final byte impTabL_INVERSE_NUMBERS_AS_L[][] = { + /* This table is identical to the Default LTR table except that EN and AN + are handled like L. + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 0, 1, 0, 0, 0, 0, 0, 0 }, + /* 1 : R */ { 0, 1, 0, 0, 0x14, 0x14, 0, 1 }, + /* 2 : AN */ { 0, 1, 0, 0, 0x15, 0x15, 0, 2 }, + /* 3 : R+EN/AN */ { 0, 1, 0, 0, 0x14, 0x14, 0, 2 }, + /* 4 : R+ON */ { 0x20, 1, 0x20, 0x20, 4, 4, 0x20, 1 }, + /* 5 : AN+ON */ { 0x20, 1, 0x20, 0x20, 5, 5, 0x20, 1 } + }; + private static final byte impTabR_INVERSE_NUMBERS_AS_L[][] = { + /* This table is identical to the Default RTL table except that EN and AN + are handled like L. + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 1, 0, 1, 1, 0, 0, 0, 0 }, + /* 1 : L */ { 1, 0, 1, 1, 0x14, 0x14, 0, 1 }, + /* 2 : EN/AN */ { 1, 0, 1, 1, 0, 0, 0, 1 }, + /* 3 : L+AN */ { 1, 0, 1, 1, 5, 5, 0, 1 }, + /* 4 : L+ON */ { 0x21, 0, 0x21, 0x21, 4, 4, 0, 0 }, + /* 5 : L+AN+ON */ { 1, 0, 1, 1, 5, 5, 0, 0 } + }; + private static final ImpTabPair impTab_INVERSE_NUMBERS_AS_L = new ImpTabPair + (impTabL_INVERSE_NUMBERS_AS_L, impTabR_INVERSE_NUMBERS_AS_L, + impAct0, impAct0); + + private static final byte impTabR_INVERSE_LIKE_DIRECT[][] = { /* Odd paragraph level */ + /* In this table, conditional sequences receive the lower possible level + until proven otherwise. + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 1, 0, 2, 2, 0, 0, 0, 0 }, + /* 1 : L */ { 1, 0, 1, 2, 0x13, 0x13, 0, 1 }, + /* 2 : EN/AN */ { 1, 0, 2, 2, 0, 0, 0, 1 }, + /* 3 : L+ON */ { 0x21, 0x30, 6, 4, 3, 3, 0x30, 0 }, + /* 4 : L+ON+AN */ { 0x21, 0x30, 6, 4, 5, 5, 0x30, 3 }, + /* 5 : L+AN+ON */ { 0x21, 0x30, 6, 4, 5, 5, 0x30, 2 }, + /* 6 : L+ON+EN */ { 0x21, 0x30, 6, 4, 3, 3, 0x30, 1 } + }; + private static final short[] impAct1 = {0,1,13,14}; + private static final ImpTabPair impTab_INVERSE_LIKE_DIRECT = new ImpTabPair( + impTabL_DEFAULT, impTabR_INVERSE_LIKE_DIRECT, impAct0, impAct1); + + private static final byte impTabL_INVERSE_LIKE_DIRECT_WITH_MARKS[][] = { + /* The case handled in this table is (visually): R EN L + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 0, 0x63, 0, 1, 0, 0, 0, 0 }, + /* 1 : L+AN */ { 0, 0x63, 0, 1, 0x12, 0x30, 0, 4 }, + /* 2 : L+AN+ON */ { 0x20, 0x63, 0x20, 1, 2, 0x30, 0x20, 3 }, + /* 3 : R */ { 0, 0x63, 0x55, 0x56, 0x14, 0x30, 0, 3 }, + /* 4 : R+ON */ { 0x30, 0x43, 0x55, 0x56, 4, 0x30, 0x30, 3 }, + /* 5 : R+EN */ { 0x30, 0x43, 5, 0x56, 0x14, 0x30, 0x30, 4 }, + /* 6 : R+AN */ { 0x30, 0x43, 0x55, 6, 0x14, 0x30, 0x30, 4 } + }; + private static final byte impTabR_INVERSE_LIKE_DIRECT_WITH_MARKS[][] = { + /* The cases handled in this table are (visually): R EN L + R L AN L + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 0x13, 0, 1, 1, 0, 0, 0, 0 }, + /* 1 : R+EN/AN */ { 0x23, 0, 1, 1, 2, 0x40, 0, 1 }, + /* 2 : R+EN/AN+ON */ { 0x23, 0, 1, 1, 2, 0x40, 0, 0 }, + /* 3 : L */ { 3, 0, 3, 0x36, 0x14, 0x40, 0, 1 }, + /* 4 : L+ON */ { 0x53, 0x40, 5, 0x36, 4, 0x40, 0x40, 0 }, + /* 5 : L+ON+EN */ { 0x53, 0x40, 5, 0x36, 4, 0x40, 0x40, 1 }, + /* 6 : L+AN */ { 0x53, 0x40, 6, 6, 4, 0x40, 0x40, 3 } + }; + private static final short[] impAct2 = {0,1,2,5,6,7,8}; + private static final short[] impAct3 = {0,1,9,10,11,12}; + private static final ImpTabPair impTab_INVERSE_LIKE_DIRECT_WITH_MARKS = + new ImpTabPair(impTabL_INVERSE_LIKE_DIRECT_WITH_MARKS, + impTabR_INVERSE_LIKE_DIRECT_WITH_MARKS, impAct2, impAct3); + + private static final ImpTabPair impTab_INVERSE_FOR_NUMBERS_SPECIAL = new ImpTabPair( + impTabL_NUMBERS_SPECIAL, impTabR_INVERSE_LIKE_DIRECT, impAct0, impAct1); + + private static final byte impTabL_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS[][] = { + /* The case handled in this table is (visually): R EN L + */ + /* L, R, EN, AN, ON, S, B, Res */ + /* 0 : init */ { 0, 0x62, 1, 1, 0, 0, 0, 0 }, + /* 1 : L+EN/AN */ { 0, 0x62, 1, 1, 0, 0x30, 0, 4 }, + /* 2 : R */ { 0, 0x62, 0x54, 0x54, 0x13, 0x30, 0, 3 }, + /* 3 : R+ON */ { 0x30, 0x42, 0x54, 0x54, 3, 0x30, 0x30, 3 }, + /* 4 : R+EN/AN */ { 0x30, 0x42, 4, 4, 0x13, 0x30, 0x30, 4 } + }; + private static final ImpTabPair impTab_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS = new + ImpTabPair(impTabL_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS, + impTabR_INVERSE_LIKE_DIRECT_WITH_MARKS, impAct2, impAct3); + + private static class LevState { + byte[][] impTab; /* level table pointer */ + short[] impAct; /* action map array */ + int startON; /* start of ON sequence */ + int startL2EN; /* start of level 2 sequence */ + int lastStrongRTL; /* index of last found R or AL */ + int runStart; /* start position of the run */ + short state; /* current state */ + byte runLevel; /* run level before implicit solving */ + } + + /*------------------------------------------------------------------------*/ + + static final int FIRSTALLOC = 10; + /* + * param pos: position where to insert + * param flag: one of LRM_BEFORE, LRM_AFTER, RLM_BEFORE, RLM_AFTER + */ + private void addPoint(int pos, int flag) + { + Point point = new Point(); + + int len = insertPoints.points.length; + if (len == 0) { + insertPoints.points = new Point[FIRSTALLOC]; + len = FIRSTALLOC; + } + if (insertPoints.size >= len) { /* no room for new point */ + Point[] savePoints = insertPoints.points; + insertPoints.points = new Point[len * 2]; + System.arraycopy(savePoints, 0, insertPoints.points, 0, len); + } + point.pos = pos; + point.flag = flag; + insertPoints.points[insertPoints.size] = point; + insertPoints.size++; + } + + private void setLevelsOutsideIsolates(int start, int limit, byte level) + { + byte dirProp; + int isolateCount = 0, k; + for (k = start; k < limit; k++) { + dirProp = dirProps[k]; + if (dirProp == PDI) + isolateCount--; + if (isolateCount == 0) { + levels[k] = level; + } + if (dirProp == LRI || dirProp == RLI) + isolateCount++; + } + } + + /* perform rules (Wn), (Nn), and (In) on a run of the text ------------------ */ + + /* + * This implementation of the (Wn) rules applies all rules in one pass. + * In order to do so, it needs a look-ahead of typically 1 character + * (except for W5: sequences of ET) and keeps track of changes + * in a rule Wp that affect a later Wq (p= 0) { + addPoint(levState.startL2EN, LRM_BEFORE); + } + levState.startL2EN = -1; /* not within previous if since could also be -2 */ + /* check if we had any relevant EN/AN after R/AL */ + if ((insertPoints.points.length == 0) || + (insertPoints.size <= insertPoints.confirmed)) { + /* nothing, just clean up */ + levState.lastStrongRTL = -1; + /* check if we have a pending conditional segment */ + level = impTab[oldStateSeq][IMPTABLEVELS_RES]; + if ((level & 1) != 0 && levState.startON > 0) { /* after ON */ + start = levState.startON; /* reset to basic run level */ + } + if (_prop == _S) { /* add LRM before S */ + addPoint(start0, LRM_BEFORE); + insertPoints.confirmed = insertPoints.size; + } + break; + } + /* reset previous RTL cont to level for LTR text */ + for (k = levState.lastStrongRTL + 1; k < start0; k++) { + /* reset odd level, leave runLevel+2 as is */ + levels[k] = (byte)((levels[k] - 2) & ~1); + } + /* mark insert points as confirmed */ + insertPoints.confirmed = insertPoints.size; + levState.lastStrongRTL = -1; + if (_prop == _S) { /* add LRM before S */ + addPoint(start0, LRM_BEFORE); + insertPoints.confirmed = insertPoints.size; + } + break; + + case 6: /* R/AL after possible relevant EN/AN */ + /* just clean up */ + if (insertPoints.points.length > 0) + /* remove all non confirmed insert points */ + insertPoints.size = insertPoints.confirmed; + levState.startON = -1; + levState.startL2EN = -1; + levState.lastStrongRTL = limit - 1; + break; + + case 7: /* EN/AN after R/AL + possible cont */ + /* check for real AN */ + + if ((_prop == _AN) && (dirProps[start0] == AN) && + (reorderingMode != REORDER_INVERSE_FOR_NUMBERS_SPECIAL)) + { + /* real AN */ + if (levState.startL2EN == -1) { /* if no relevant EN already found */ + /* just note the rightmost digit as a strong RTL */ + levState.lastStrongRTL = limit - 1; + break; + } + if (levState.startL2EN >= 0) { /* after EN, no AN */ + addPoint(levState.startL2EN, LRM_BEFORE); + levState.startL2EN = -2; + } + /* note AN */ + addPoint(start0, LRM_BEFORE); + break; + } + /* if first EN/AN after R/AL */ + if (levState.startL2EN == -1) { + levState.startL2EN = start0; + } + break; + + case 8: /* note location of latest R/AL */ + levState.lastStrongRTL = limit - 1; + levState.startON = -1; + break; + + case 9: /* L after R+ON/EN/AN */ + /* include possible adjacent number on the left */ + for (k = start0-1; k >= 0 && ((levels[k] & 1) == 0); k--) { + } + if (k >= 0) { + addPoint(k, RLM_BEFORE); /* add RLM before */ + insertPoints.confirmed = insertPoints.size; /* confirm it */ + } + levState.startON = start0; + break; + + case 10: /* AN after L */ + /* AN numbers between L text on both sides may be trouble. */ + /* tentatively bracket with LRMs; will be confirmed if followed by L */ + addPoint(start0, LRM_BEFORE); /* add LRM before */ + addPoint(start0, LRM_AFTER); /* add LRM after */ + break; + + case 11: /* R after L+ON/EN/AN */ + /* false alert, infirm LRMs around previous AN */ + insertPoints.size=insertPoints.confirmed; + if (_prop == _S) { /* add RLM before S */ + addPoint(start0, RLM_BEFORE); + insertPoints.confirmed = insertPoints.size; + } + break; + + case 12: /* L after L+ON/AN */ + level = (byte)(levState.runLevel + addLevel); + for (k=levState.startON; k < start0; k++) { + if (levels[k] < level) { + levels[k] = level; + } + } + insertPoints.confirmed = insertPoints.size; /* confirm inserts */ + levState.startON = start0; + break; + + case 13: /* L after L+ON+EN/AN/ON */ + level = levState.runLevel; + for (k = start0-1; k >= levState.startON; k--) { + if (levels[k] == level+3) { + while (levels[k] == level+3) { + levels[k--] -= 2; + } + while (levels[k] == level) { + k--; + } + } + if (levels[k] == level+2) { + levels[k] = level; + continue; + } + levels[k] = (byte)(level+1); + } + break; + + case 14: /* R after L+ON+EN/AN/ON */ + level = (byte)(levState.runLevel+1); + for (k = start0-1; k >= levState.startON; k--) { + if (levels[k] > level) { + levels[k] -= 2; + } + } + break; + + default: /* we should never get here */ + throw new IllegalStateException("Internal ICU error in processPropertySeq"); + } + } + if ((addLevel) != 0 || (start < start0)) { + level = (byte)(levState.runLevel + addLevel); + if (start >= levState.runStart) { + for (k = start; k < limit; k++) { + levels[k] = level; + } + } else { + setLevelsOutsideIsolates(start, limit, level); + } + } + } + + private void resolveImplicitLevels(int start, int limit, short sor, short eor) + { + byte dirProp; + LevState levState = new LevState(); + int i, start1, start2; + short oldStateImp, stateImp, actionImp; + short gprop, resProp, cell; + boolean inverseRTL; + short nextStrongProp = R; + int nextStrongPos = -1; + + /* check for RTL inverse Bidi mode */ + /* FOOD FOR THOUGHT: in case of RTL inverse Bidi, it would make sense to + * loop on the text characters from end to start. + * This would need a different properties state table (at least different + * actions) and different levels state tables (maybe very similar to the + * LTR corresponding ones. + */ + inverseRTL=((start0) && + (reorderingMode == REORDER_INVERSE_LIKE_DIRECT || + reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL)); + /* initialize for property and levels state table */ + levState.startL2EN = -1; /* used for INVERSE_LIKE_DIRECT_WITH_MARKS */ + levState.lastStrongRTL = -1; /* used for INVERSE_LIKE_DIRECT_WITH_MARKS */ + levState.runStart = start; + levState.runLevel = levels[start]; + levState.impTab = impTabPair.imptab[levState.runLevel & 1]; + levState.impAct = impTabPair.impact[levState.runLevel & 1]; + + /* The isolates[] entries contain enough information to + resume the bidi algorithm in the same state as it was + when it was interrupted by an isolate sequence. */ + if (dirProps[start] == PDI) { + levState.startON = isolates[isolateCount].startON; + start1 = isolates[isolateCount].start1; + stateImp = isolates[isolateCount].stateImp; + levState.state = isolates[isolateCount].state; + isolateCount--; + } else { + levState.startON = -1; + start1 = start; + if (dirProps[start] == NSM) + stateImp = (short)(1 + sor); + else + stateImp = 0; + levState.state = 0; + processPropertySeq(levState, sor, start, start); + } + start2 = start; /* to make the Java compiler happy */ + + for (i = start; i <= limit; i++) { + if (i >= limit) { + int k; + for (k = limit - 1; + k > start && + (DirPropFlag(dirProps[k]) & MASK_BN_EXPLICIT) != 0; + k--); + dirProp = dirProps[k]; + if (dirProp == LRI || dirProp == RLI) + break; /* no forced closing for sequence ending with LRI/RLI */ + gprop = eor; + } else { + byte prop, prop1; + prop = dirProps[i]; + if (prop == B) + isolateCount = -1; /* current isolates stack entry == none */ + if (inverseRTL) { + if (prop == AL) { + /* AL before EN does not make it AN */ + prop = R; + } else if (prop == EN) { + if (nextStrongPos <= i) { + /* look for next strong char (L/R/AL) */ + int j; + nextStrongProp = R; /* set default */ + nextStrongPos = limit; + for (j = i+1; j < limit; j++) { + prop1 = dirProps[j]; + if (prop1 == L || prop1 == R || prop1 == AL) { + nextStrongProp = prop1; + nextStrongPos = j; + break; + } + } + } + if (nextStrongProp == AL) { + prop = AN; + } + } + } + gprop = groupProp[prop]; + } + oldStateImp = stateImp; + cell = impTabProps[oldStateImp][gprop]; + stateImp = GetStateProps(cell); /* isolate the new state */ + actionImp = GetActionProps(cell); /* isolate the action */ + if ((i == limit) && (actionImp == 0)) { + /* there is an unprocessed sequence if its property == eor */ + actionImp = 1; /* process the last sequence */ + } + if (actionImp != 0) { + resProp = impTabProps[oldStateImp][IMPTABPROPS_RES]; + switch (actionImp) { + case 1: /* process current seq1, init new seq1 */ + processPropertySeq(levState, resProp, start1, i); + start1 = i; + break; + case 2: /* init new seq2 */ + start2 = i; + break; + case 3: /* process seq1, process seq2, init new seq1 */ + processPropertySeq(levState, resProp, start1, start2); + processPropertySeq(levState, _ON, start2, i); + start1 = i; + break; + case 4: /* process seq1, set seq1=seq2, init new seq2 */ + processPropertySeq(levState, resProp, start1, start2); + start1 = start2; + start2 = i; + break; + default: /* we should never get here */ + throw new IllegalStateException("Internal ICU error in resolveImplicitLevels"); + } + } + } + + /* look for the last char not a BN or LRE/RLE/LRO/RLO/PDF */ + for (i = limit - 1; + i > start && + (DirPropFlag(dirProps[i]) & MASK_BN_EXPLICIT) != 0; + i--); + dirProp = dirProps[i]; + if ((dirProp == LRI || dirProp == RLI) && limit < length) { + isolateCount++; + if (isolates[isolateCount] == null) + isolates[isolateCount] = new Isolate(); + isolates[isolateCount].stateImp = stateImp; + isolates[isolateCount].state = levState.state; + isolates[isolateCount].start1 = start1; + isolates[isolateCount].startON = levState.startON; + } + else + processPropertySeq(levState, eor, limit, limit); + } + + /* perform (L1) and (X9) ---------------------------------------------------- */ + + /* + * Reset the embedding levels for some non-graphic characters (L1). + * This method also sets appropriate levels for BN, and + * explicit embedding types that are supposed to have been removed + * from the paragraph in (X9). + */ + private void adjustWSLevels() { + int i; + + if ((flags & MASK_WS) != 0) { + int flag; + i = trailingWSStart; + while (i > 0) { + /* reset a sequence of WS/BN before eop and B/S to the paragraph paraLevel */ + while (i > 0 && ((flag = DirPropFlag(dirProps[--i])) & MASK_WS) != 0) { + if (orderParagraphsLTR && (flag & DirPropFlag(B)) != 0) { + levels[i] = 0; + } else { + levels[i] = GetParaLevelAt(i); + } + } + + /* reset BN to the next character's paraLevel until B/S, which restarts above loop */ + /* here, i+1 is guaranteed to be 0) { + flag = DirPropFlag(dirProps[--i]); + if ((flag & MASK_BN_EXPLICIT) != 0) { + levels[i] = levels[i + 1]; + } else if (orderParagraphsLTR && (flag & DirPropFlag(B)) != 0) { + levels[i] = 0; + break; + } else if ((flag & MASK_B_S) != 0){ + levels[i] = GetParaLevelAt(i); + break; + } + } + } + } + } + + private void setParaSuccess() { + paraBidi = this; /* mark successful setPara */ + } + + private int Bidi_Min(int x, int y) { + return x < y ? x : y; + } + + private int Bidi_Abs(int x) { + return x >= 0 ? x : -x; + } + + void setParaRunsOnly(char[] parmText, byte parmParaLevel) { + int[] visualMap; + String visualText; + int saveLength, saveTrailingWSStart; + byte[] saveLevels; + byte saveDirection; + int i, j, visualStart, logicalStart, + oldRunCount, runLength, addedRuns, insertRemove, + start, limit, step, indexOddBit, logicalPos, + index, index1; + int saveOptions; + + reorderingMode = REORDER_DEFAULT; + int parmLength = parmText.length; + if (parmLength == 0) { + setPara(parmText, parmParaLevel, null); + reorderingMode = REORDER_RUNS_ONLY; + return; + } + /* obtain memory for mapping table and visual text */ + saveOptions = reorderingOptions; + if ((saveOptions & OPTION_INSERT_MARKS) > 0) { + reorderingOptions &= ~OPTION_INSERT_MARKS; + reorderingOptions |= OPTION_REMOVE_CONTROLS; + } + parmParaLevel &= 1; /* accept only 0 or 1 */ + setPara(parmText, parmParaLevel, null); + /* we cannot access directly levels since it is not yet set if + * direction is not MIXED + */ + saveLevels = new byte[this.length]; + System.arraycopy(getLevels(), 0, saveLevels, 0, this.length); + saveTrailingWSStart = trailingWSStart; + + /* FOOD FOR THOUGHT: instead of writing the visual text, we could use + * the visual map and the dirProps array to drive the second call + * to setPara (but must make provision for possible removal of + * Bidi controls. Alternatively, only use the dirProps array via + * customized classifier callback. + */ + visualText = writeReordered(DO_MIRRORING); + visualMap = getVisualMap(); + this.reorderingOptions = saveOptions; + saveLength = this.length; + saveDirection=this.direction; + + this.reorderingMode = REORDER_INVERSE_LIKE_DIRECT; + parmParaLevel ^= 1; + setPara(visualText, parmParaLevel, null); + BidiLine.getRuns(this); + /* check if some runs must be split, count how many splits */ + addedRuns = 0; + oldRunCount = this.runCount; + visualStart = 0; + for (i = 0; i < oldRunCount; i++, visualStart += runLength) { + runLength = runs[i].limit - visualStart; + if (runLength < 2) { + continue; + } + logicalStart = runs[i].start; + for (j = logicalStart+1; j < logicalStart+runLength; j++) { + index = visualMap[j]; + index1 = visualMap[j-1]; + if ((Bidi_Abs(index-index1)!=1) || (saveLevels[index]!=saveLevels[index1])) { + addedRuns++; + } + } + } + if (addedRuns > 0) { + getRunsMemory(oldRunCount + addedRuns); + if (runCount == 1) { + /* because we switch from UBiDi.simpleRuns to UBiDi.runs */ + runsMemory[0] = runs[0]; + } else { + System.arraycopy(runs, 0, runsMemory, 0, runCount); + } + runs = runsMemory; + runCount += addedRuns; + for (i = oldRunCount; i < runCount; i++) { + if (runs[i] == null) { + runs[i] = new BidiRun(0, 0, (byte)0); + } + } + } + /* split runs which are not consecutive in source text */ + int newI; + for (i = oldRunCount-1; i >= 0; i--) { + newI = i + addedRuns; + runLength = i==0 ? runs[0].limit : + runs[i].limit - runs[i-1].limit; + logicalStart = runs[i].start; + indexOddBit = runs[i].level & 1; + if (runLength < 2) { + if (addedRuns > 0) { + runs[newI].copyFrom(runs[i]); + } + logicalPos = visualMap[logicalStart]; + runs[newI].start = logicalPos; + runs[newI].level = (byte)(saveLevels[logicalPos] ^ indexOddBit); + continue; + } + if (indexOddBit > 0) { + start = logicalStart; + limit = logicalStart + runLength - 1; + step = 1; + } else { + start = logicalStart + runLength - 1; + limit = logicalStart; + step = -1; + } + for (j = start; j != limit; j += step) { + index = visualMap[j]; + index1 = visualMap[j+step]; + if ((Bidi_Abs(index-index1)!=1) || (saveLevels[index]!=saveLevels[index1])) { + logicalPos = Bidi_Min(visualMap[start], index); + runs[newI].start = logicalPos; + runs[newI].level = (byte)(saveLevels[logicalPos] ^ indexOddBit); + runs[newI].limit = runs[i].limit; + runs[i].limit -= Bidi_Abs(j - start) + 1; + insertRemove = runs[i].insertRemove & (LRM_AFTER|RLM_AFTER); + runs[newI].insertRemove = insertRemove; + runs[i].insertRemove &= ~insertRemove; + start = j + step; + addedRuns--; + newI--; + } + } + if (addedRuns > 0) { + runs[newI].copyFrom(runs[i]); + } + logicalPos = Bidi_Min(visualMap[start], visualMap[limit]); + runs[newI].start = logicalPos; + runs[newI].level = (byte)(saveLevels[logicalPos] ^ indexOddBit); + } + + cleanup1: + /* restore initial paraLevel */ + this.paraLevel ^= 1; + cleanup2: + /* restore real text */ + this.text = parmText; + this.length = saveLength; + this.originalLength = parmLength; + this.direction=saveDirection; + this.levels = saveLevels; + this.trailingWSStart = saveTrailingWSStart; + if (runCount > 1) { + this.direction = MIXED; + } + cleanup3: + this.reorderingMode = REORDER_RUNS_ONLY; + } + + /** + * Perform the Unicode Bidi algorithm. It is defined in the + * Unicode Standard Annex #9: + * Unicode Bidirectional Algorithm, version 13, + * also described in The Unicode Standard, Version 4.0 .

+ * + * This method takes a piece of plain text containing one or more paragraphs, + * with or without externally specified embedding levels from styled + * text and computes the left-right-directionality of each character.

+ * + * If the entire text is all of the same directionality, then + * the method may not perform all the steps described by the algorithm, + * i.e., some levels may not be the same as if all steps were performed. + * This is not relevant for unidirectional text.
+ * For example, in pure LTR text with numbers the numbers would get + * a resolved level of 2 higher than the surrounding text according to + * the algorithm. This implementation may set all resolved levels to + * the same value in such a case.

+ * + * The text can be composed of multiple paragraphs. Occurrence of a block + * separator in the text terminates a paragraph, and whatever comes next starts + * a new paragraph. The exception to this rule is when a Carriage Return (CR) + * is followed by a Line Feed (LF). Both CR and LF are block separators, but + * in that case, the pair of characters is considered as terminating the + * preceding paragraph, and a new paragraph will be started by a character + * coming after the LF. + * + * Although the text is passed here as a String, it is + * stored internally as an array of characters. Therefore the + * documentation will refer to indexes of the characters in the text. + * + * @param text contains the text that the Bidi algorithm will be performed + * on. This text can be retrieved with getText() or + * getTextAsString.
+ * + * @param paraLevel specifies the default level for the text; + * it is typically 0 (LTR) or 1 (RTL). + * If the method shall determine the paragraph level from the text, + * then paraLevel can be set to + * either LEVEL_DEFAULT_LTR + * or LEVEL_DEFAULT_RTL; if the text contains multiple + * paragraphs, the paragraph level shall be determined separately for + * each paragraph; if a paragraph does not include any strongly typed + * character, then the desired default is used (0 for LTR or 1 for RTL). + * Any other value between 0 and MAX_EXPLICIT_LEVEL + * is also valid, with odd levels indicating RTL. + * + * @param embeddingLevels (in) may be used to preset the embedding and override levels, + * ignoring characters like LRE and PDF in the text. + * A level overrides the directional property of its corresponding + * (same index) character if the level has the + * LEVEL_OVERRIDE bit set.

+ * Except for that bit, it must be + * paraLevel<=embeddingLevels[]<=MAX_EXPLICIT_LEVEL, + * with one exception: a level of zero may be specified for a + * paragraph separator even if paraLevel>0 when multiple + * paragraphs are submitted in the same call to setPara().

+ * Caution: A reference to this array, not a copy + * of the levels, will be stored in the Bidi object; + * the embeddingLevels + * should not be modified to avoid unexpected results on subsequent + * Bidi operations. However, the setPara() and + * setLine() methods may modify some or all of the + * levels.

+ * Note: the embeddingLevels array must + * have one entry for each character in text. + * + * @throws IllegalArgumentException if the values in embeddingLevels are + * not within the allowed range + * + * @see #LEVEL_DEFAULT_LTR + * @see #LEVEL_DEFAULT_RTL + * @see #LEVEL_OVERRIDE + * @see #MAX_EXPLICIT_LEVEL + * @stable ICU 3.8 + */ + void setPara(String text, byte paraLevel, byte[] embeddingLevels) + { + if (text == null) { + setPara(new char[0], paraLevel, embeddingLevels); + } else { + setPara(text.toCharArray(), paraLevel, embeddingLevels); + } + } + + /** + * Perform the Unicode Bidi algorithm. It is defined in the + * Unicode Standard Annex #9: + * Unicode Bidirectional Algorithm, version 13, + * also described in The Unicode Standard, Version 4.0 .

+ * + * This method takes a piece of plain text containing one or more paragraphs, + * with or without externally specified embedding levels from styled + * text and computes the left-right-directionality of each character.

+ * + * If the entire text is all of the same directionality, then + * the method may not perform all the steps described by the algorithm, + * i.e., some levels may not be the same as if all steps were performed. + * This is not relevant for unidirectional text.
+ * For example, in pure LTR text with numbers the numbers would get + * a resolved level of 2 higher than the surrounding text according to + * the algorithm. This implementation may set all resolved levels to + * the same value in such a case. + * + * The text can be composed of multiple paragraphs. Occurrence of a block + * separator in the text terminates a paragraph, and whatever comes next starts + * a new paragraph. The exception to this rule is when a Carriage Return (CR) + * is followed by a Line Feed (LF). Both CR and LF are block separators, but + * in that case, the pair of characters is considered as terminating the + * preceding paragraph, and a new paragraph will be started by a character + * coming after the LF. + * + * The text is stored internally as an array of characters. Therefore the + * documentation will refer to indexes of the characters in the text. + * + * @param chars contains the text that the Bidi algorithm will be performed + * on. This text can be retrieved with getText() or + * getTextAsString.
+ * + * @param paraLevel specifies the default level for the text; + * it is typically 0 (LTR) or 1 (RTL). + * If the method shall determine the paragraph level from the text, + * then paraLevel can be set to + * either LEVEL_DEFAULT_LTR + * or LEVEL_DEFAULT_RTL; if the text contains multiple + * paragraphs, the paragraph level shall be determined separately for + * each paragraph; if a paragraph does not include any strongly typed + * character, then the desired default is used (0 for LTR or 1 for RTL). + * Any other value between 0 and MAX_EXPLICIT_LEVEL + * is also valid, with odd levels indicating RTL. + * + * @param embeddingLevels (in) may be used to preset the embedding and + * override levels, ignoring characters like LRE and PDF in the text. + * A level overrides the directional property of its corresponding + * (same index) character if the level has the + * LEVEL_OVERRIDE bit set.

+ * Except for that bit, it must be + * paraLevel<=embeddingLevels[]<=MAX_EXPLICIT_LEVEL, + * with one exception: a level of zero may be specified for a + * paragraph separator even if paraLevel>0 when multiple + * paragraphs are submitted in the same call to setPara().

+ * Caution: A reference to this array, not a copy + * of the levels, will be stored in the Bidi object; + * the embeddingLevels + * should not be modified to avoid unexpected results on subsequent + * Bidi operations. However, the setPara() and + * setLine() methods may modify some or all of the + * levels.

+ * Note: the embeddingLevels array must + * have one entry for each character in text. + * + * @throws IllegalArgumentException if the values in embeddingLevels are + * not within the allowed range + * + * @see #LEVEL_DEFAULT_LTR + * @see #LEVEL_DEFAULT_RTL + * @see #LEVEL_OVERRIDE + * @see #MAX_EXPLICIT_LEVEL + * @stable ICU 3.8 + */ + void setPara(char[] chars, byte paraLevel, byte[] embeddingLevels) + { + /* check the argument values */ + if (paraLevel < LEVEL_DEFAULT_LTR) { + verifyRange(paraLevel, 0, MAX_EXPLICIT_LEVEL + 1); + } + if (chars == null) { + chars = new char[0]; + } + + /* special treatment for RUNS_ONLY mode */ + if (reorderingMode == REORDER_RUNS_ONLY) { + setParaRunsOnly(chars, paraLevel); + return; + } + + /* initialize the Bidi object */ + this.paraBidi = null; /* mark unfinished setPara */ + this.text = chars; + this.length = this.originalLength = this.resultLength = text.length; + this.paraLevel = paraLevel; + this.direction = (byte)(paraLevel & 1); + this.paraCount = 1; + + /* Allocate zero-length arrays instead of setting to null here; then + * checks for null in various places can be eliminated. + */ + dirProps = new byte[0]; + levels = new byte[0]; + runs = new BidiRun[0]; + isGoodLogicalToVisualRunsMap = false; + insertPoints.size = 0; /* clean up from last call */ + insertPoints.confirmed = 0; /* clean up from last call */ + + /* + * Save the original paraLevel if contextual; otherwise, set to 0. + */ + defaultParaLevel = IsDefaultLevel(paraLevel) ? paraLevel : 0; + + if (length == 0) { + /* + * For an empty paragraph, create a Bidi object with the paraLevel and + * the flags and the direction set but without allocating zero-length arrays. + * There is nothing more to do. + */ + if (IsDefaultLevel(paraLevel)) { + this.paraLevel &= 1; + defaultParaLevel = 0; + } + flags = DirPropFlagLR(paraLevel); + runCount = 0; + paraCount = 0; + setParaSuccess(); + return; + } + + runCount = -1; + + /* + * Get the directional properties, + * the flags bit-set, and + * determine the paragraph level if necessary. + */ + getDirPropsMemory(length); + dirProps = dirPropsMemory; + getDirProps(); + /* the processed length may have changed if OPTION_STREAMING is set */ + trailingWSStart = length; /* the levels[] will reflect the WS run */ + + /* are explicit levels specified? */ + if (embeddingLevels == null) { + /* no: determine explicit levels according to the (Xn) rules */ + getLevelsMemory(length); + levels = levelsMemory; + direction = resolveExplicitLevels(); + } else { + /* set BN for all explicit codes, check that all levels are 0 or paraLevel..MAX_EXPLICIT_LEVEL */ + levels = embeddingLevels; + direction = checkExplicitLevels(); + } + + /* allocate isolate memory */ + if (isolateCount > 0) { + if (isolates == null || isolates.length < isolateCount) + isolates = new Isolate[isolateCount + 3]; /* keep some reserve */ + } + isolateCount = -1; /* current isolates stack entry == none */ + + /* + * The steps after (X9) in the Bidi algorithm are performed only if + * the paragraph text has mixed directionality! + */ + switch (direction) { + case LTR: + /* all levels are implicitly at paraLevel (important for getLevels()) */ + trailingWSStart = 0; + break; + case RTL: + /* all levels are implicitly at paraLevel (important for getLevels()) */ + trailingWSStart = 0; + break; + default: + /* + * Choose the right implicit state table + */ + switch(reorderingMode) { + case REORDER_DEFAULT: + this.impTabPair = impTab_DEFAULT; + break; + case REORDER_NUMBERS_SPECIAL: + this.impTabPair = impTab_NUMBERS_SPECIAL; + break; + case REORDER_GROUP_NUMBERS_WITH_R: + this.impTabPair = impTab_GROUP_NUMBERS_WITH_R; + break; + case REORDER_RUNS_ONLY: + /* we should never get here */ + throw new InternalError("Internal ICU error in setPara"); + /* break; */ + case REORDER_INVERSE_NUMBERS_AS_L: + this.impTabPair = impTab_INVERSE_NUMBERS_AS_L; + break; + case REORDER_INVERSE_LIKE_DIRECT: + if ((reorderingOptions & OPTION_INSERT_MARKS) != 0) { + this.impTabPair = impTab_INVERSE_LIKE_DIRECT_WITH_MARKS; + } else { + this.impTabPair = impTab_INVERSE_LIKE_DIRECT; + } + break; + case REORDER_INVERSE_FOR_NUMBERS_SPECIAL: + if ((reorderingOptions & OPTION_INSERT_MARKS) != 0) { + this.impTabPair = impTab_INVERSE_FOR_NUMBERS_SPECIAL_WITH_MARKS; + } else { + this.impTabPair = impTab_INVERSE_FOR_NUMBERS_SPECIAL; + } + break; + } + /* + * If there are no external levels specified and there + * are no significant explicit level codes in the text, + * then we can treat the entire paragraph as one run. + * Otherwise, we need to perform the following rules on runs of + * the text with the same embedding levels. (X10) + * "Significant" explicit level codes are ones that actually + * affect non-BN characters. + * Examples for "insignificant" ones are empty embeddings + * LRE-PDF, LRE-RLE-PDF-PDF, etc. + */ + if (embeddingLevels == null && paraCount <= 1 && + (flags & DirPropFlagMultiRuns) == 0) { + resolveImplicitLevels(0, length, + GetLRFromLevel(GetParaLevelAt(0)), + GetLRFromLevel(GetParaLevelAt(length - 1))); + } else { + /* sor, eor: start and end types of same-level-run */ + int start, limit = 0; + byte level, nextLevel; + short sor, eor; + + /* determine the first sor and set eor to it because of the loop body (sor=eor there) */ + level = GetParaLevelAt(0); + nextLevel = levels[0]; + if (level < nextLevel) { + eor = GetLRFromLevel(nextLevel); + } else { + eor = GetLRFromLevel(level); + } + + do { + /* determine start and limit of the run (end points just behind the run) */ + + /* the values for this run's start are the same as for the previous run's end */ + start = limit; + level = nextLevel; + if ((start > 0) && (dirProps[start - 1] == B)) { + /* except if this is a new paragraph, then set sor = para level */ + sor = GetLRFromLevel(GetParaLevelAt(start)); + } else { + sor = eor; + } + + /* search for the limit of this run */ + while ((++limit < length) && + ((levels[limit] == level) || + ((DirPropFlag(dirProps[limit]) & MASK_BN_EXPLICIT) != 0))) {} + + /* get the correct level of the next run */ + if (limit < length) { + nextLevel = levels[limit]; + } else { + nextLevel = GetParaLevelAt(length - 1); + } + + /* determine eor from max(level, nextLevel); sor is last run's eor */ + if (NoOverride(level) < NoOverride(nextLevel)) { + eor = GetLRFromLevel(nextLevel); + } else { + eor = GetLRFromLevel(level); + } + + /* if the run consists of overridden directional types, then there + are no implicit types to be resolved */ + if ((level & LEVEL_OVERRIDE) == 0) { + resolveImplicitLevels(start, limit, sor, eor); + } else { + /* remove the LEVEL_OVERRIDE flags */ + do { + levels[start++] &= ~LEVEL_OVERRIDE; + } while (start < limit); + } + } while (limit < length); + } + + /* reset the embedding levels for some non-graphic characters (L1), (X9) */ + adjustWSLevels(); + + break; + } + + /* add RLM for inverse Bidi with contextual orientation resolving + * to RTL which would not round-trip otherwise + */ + if ((defaultParaLevel > 0) && + ((reorderingOptions & OPTION_INSERT_MARKS) != 0) && + ((reorderingMode == REORDER_INVERSE_LIKE_DIRECT) || + (reorderingMode == REORDER_INVERSE_FOR_NUMBERS_SPECIAL))) { + int start, last; + byte level; + byte dirProp; + for (int i = 0; i < paraCount; i++) { + last = paras_limit[i] - 1; + level = paras_level[i]; + if (level == 0) + continue; /* LTR paragraph */ + start = i == 0 ? 0 : paras_limit[i - 1]; + for (int j = last; j >= start; j--) { + dirProp = dirProps[j]; + if (dirProp == L) { + if (j < last) { + while (dirProps[last] == B) { + last--; + } + } + addPoint(last, RLM_BEFORE); + break; + } + if ((DirPropFlag(dirProp) & MASK_R_AL) != 0) { + break; + } + } + } + } + + if ((reorderingOptions & OPTION_REMOVE_CONTROLS) != 0) { + resultLength -= controlCount; + } else { + resultLength += insertPoints.size; + } + setParaSuccess(); + } + + /** + * Perform the Unicode Bidi algorithm on a given paragraph, as defined in the + * Unicode Standard Annex #9: + * Unicode Bidirectional Algorithm, version 13, + * also described in The Unicode Standard, Version 4.0 .

+ * + * This method takes a paragraph of text and computes the + * left-right-directionality of each character. The text should not + * contain any Unicode block separators.

+ * + * The RUN_DIRECTION attribute in the text, if present, determines the base + * direction (left-to-right or right-to-left). If not present, the base + * direction is computed using the Unicode Bidirectional Algorithm, + * defaulting to left-to-right if there are no strong directional characters + * in the text. This attribute, if present, must be applied to all the text + * in the paragraph.

+ * + * The BIDI_EMBEDDING attribute in the text, if present, represents + * embedding level information. Negative values from -1 to -62 indicate + * overrides at the absolute value of the level. Positive values from 1 to + * 62 indicate embeddings. Where values are zero or not defined, the base + * embedding level as determined by the base direction is assumed.

+ * + * The NUMERIC_SHAPING attribute in the text, if present, converts European + * digits to other decimal digits before running the bidi algorithm. This + * attribute, if present, must be applied to all the text in the paragraph. + * + * If the entire text is all of the same directionality, then + * the method may not perform all the steps described by the algorithm, + * i.e., some levels may not be the same as if all steps were performed. + * This is not relevant for unidirectional text.
+ * For example, in pure LTR text with numbers the numbers would get + * a resolved level of 2 higher than the surrounding text according to + * the algorithm. This implementation may set all resolved levels to + * the same value in such a case.

+ * + * @param paragraph a paragraph of text with optional character and + * paragraph attribute information + * @stable ICU 3.8 + */ + public void setPara(AttributedCharacterIterator paragraph) + { + byte paraLvl; + char ch = paragraph.first(); + Boolean runDirection = + (Boolean) paragraph.getAttribute(TextAttributeConstants.RUN_DIRECTION); + Object shaper = paragraph.getAttribute(TextAttributeConstants.NUMERIC_SHAPING); + + if (runDirection == null) { + paraLvl = LEVEL_DEFAULT_LTR; + } else { + paraLvl = (runDirection.equals(TextAttributeConstants.RUN_DIRECTION_LTR)) ? + LTR : RTL; + } + + byte[] lvls = null; + int len = paragraph.getEndIndex() - paragraph.getBeginIndex(); + byte[] embeddingLevels = new byte[len]; + char[] txt = new char[len]; + int i = 0; + while (ch != AttributedCharacterIterator.DONE) { + txt[i] = ch; + Integer embedding = + (Integer) paragraph.getAttribute(TextAttributeConstants.BIDI_EMBEDDING); + if (embedding != null) { + byte level = embedding.byteValue(); + if (level == 0) { + /* no-op */ + } else if (level < 0) { + lvls = embeddingLevels; + embeddingLevels[i] = (byte)((0 - level) | LEVEL_OVERRIDE); + } else { + lvls = embeddingLevels; + embeddingLevels[i] = level; + } + } + ch = paragraph.next(); + ++i; + } + + if (shaper != null) { + NumericShapings.shape(shaper, txt, 0, len); + } + setPara(txt, paraLvl, lvls); + } + + /** + * Specify whether block separators must be allocated level zero, + * so that successive paragraphs will progress from left to right. + * This method must be called before setPara(). + * Paragraph separators (B) may appear in the text. Setting them to level zero + * means that all paragraph separators (including one possibly appearing + * in the last text position) are kept in the reordered text after the text + * that they follow in the source text. + * When this feature is not enabled, a paragraph separator at the last + * position of the text before reordering will go to the first position + * of the reordered text when the paragraph level is odd. + * + * @param ordarParaLTR specifies whether paragraph separators (B) must + * receive level 0, so that successive paragraphs progress from left to right. + * + * @see #setPara + * @stable ICU 3.8 + */ + public void orderParagraphsLTR(boolean ordarParaLTR) { + orderParagraphsLTR = ordarParaLTR; + } + + /** + * Get the directionality of the text. + * + * @return a value of LTR, RTL or MIXED + * that indicates if the entire text + * represented by this object is unidirectional, + * and which direction, or if it is mixed-directional. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * + * @see #LTR + * @see #RTL + * @see #MIXED + * @stable ICU 3.8 + */ + public byte getDirection() + { + verifyValidParaOrLine(); + return direction; + } + + /** + * Get the length of the text. + * + * @return The length of the text that the Bidi object was + * created for. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @stable ICU 3.8 + */ + public int getLength() + { + verifyValidParaOrLine(); + return originalLength; + } + + /* paragraphs API methods ------------------------------------------------- */ + + /** + * Get the paragraph level of the text. + * + * @return The paragraph level. If there are multiple paragraphs, their + * level may vary if the required paraLevel is LEVEL_DEFAULT_LTR or + * LEVEL_DEFAULT_RTL. In that case, the level of the first paragraph + * is returned. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * + * @see #LEVEL_DEFAULT_LTR + * @see #LEVEL_DEFAULT_RTL + * @see #getParagraph + * @see #getParagraphByIndex + * @stable ICU 3.8 + */ + public byte getParaLevel() + { + verifyValidParaOrLine(); + return paraLevel; + } + + /** + * Retrieves the Bidi class for a given code point. + *

If a BidiClassifier is defined and returns a value + * other than CLASS_DEFAULT, that value is used; otherwise + * the default class determination mechanism is invoked.

+ * + * @param c The code point to get a Bidi class for. + * + * @return The Bidi class for the character c that is in effect + * for this Bidi instance. + * + * @stable ICU 3.8 + */ + public int getCustomizedClass(int c) { + int dir; + + dir = bdp.getClass(c); + if (dir >= CHAR_DIRECTION_COUNT) + dir = ON; + return dir; + } + + /** + * setLine() returns a Bidi object to + * contain the reordering information, especially the resolved levels, + * for all the characters in a line of text. This line of text is + * specified by referring to a Bidi object representing + * this information for a piece of text containing one or more paragraphs, + * and by specifying a range of indexes in this text.

+ * In the new line object, the indexes will range from 0 to limit-start-1.

+ * + * This is used after calling setPara() + * for a piece of text, and after line-breaking on that text. + * It is not necessary if each paragraph is treated as a single line.

+ * + * After line-breaking, rules (L1) and (L2) for the treatment of + * trailing WS and for reordering are performed on + * a Bidi object that represents a line.

+ * + * Important: the line Bidi object may + * reference data within the global text Bidi object. + * You should not alter the content of the global text object until + * you are finished using the line object. + * + * @param start is the line's first index into the text. + * + * @param limit is just behind the line's last index into the text + * (its last index +1). + * + * @return a Bidi object that will now represent a line of the text. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara + * @throws IllegalArgumentException if start and limit are not in the range + * 0<=start<limit<=getProcessedLength(), + * or if the specified line crosses a paragraph boundary + * + * @see #setPara + * @see #getProcessedLength + * @stable ICU 3.8 + */ + public Bidi setLine(Bidi bidi, BidiBase bidiBase, Bidi newBidi, BidiBase newBidiBase, int start, int limit) + { + verifyValidPara(); + verifyRange(start, 0, limit); + verifyRange(limit, 0, length+1); + + return BidiLine.setLine(this, newBidi, newBidiBase, start, limit); + } + + /** + * Get the level for one character. + * + * @param charIndex the index of a character. + * + * @return The level for the character at charIndex. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @throws IllegalArgumentException if charIndex is not in the range + * 0<=charIndex<getProcessedLength() + * + * @see #getProcessedLength + * @stable ICU 3.8 + */ + public byte getLevelAt(int charIndex) + { + // for backward compatibility + if (charIndex < 0 || charIndex >= length) { + return (byte)getBaseLevel(); + } + + verifyValidParaOrLine(); + verifyRange(charIndex, 0, length); + return BidiLine.getLevelAt(this, charIndex); + } + + /** + * Get an array of levels for each character.

+ * + * Note that this method may allocate memory under some + * circumstances, unlike getLevelAt(). + * + * @return The levels array for the text, + * or null if an error occurs. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @stable ICU 3.8 + */ + byte[] getLevels() + { + verifyValidParaOrLine(); + if (length <= 0) { + return new byte[0]; + } + return BidiLine.getLevels(this); + } + + /** + * Get the number of runs. + * This method may invoke the actual reordering on the + * Bidi object, after setPara() + * may have resolved only the levels of the text. Therefore, + * countRuns() may have to allocate memory, + * and may throw an exception if it fails to do so. + * + * @return The number of runs. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @stable ICU 3.8 + */ + public int countRuns() + { + verifyValidParaOrLine(); + BidiLine.getRuns(this); + return runCount; + } + + /** + * + * Get a BidiRun object according to its index. BidiRun methods + * may be used to retrieve the run's logical start, length and level, + * which can be even for an LTR run or odd for an RTL run. + * In an RTL run, the character at the logical start is + * visually on the right of the displayed run. + * The length is the number of characters in the run.

+ * countRuns() is normally called + * before the runs are retrieved. + * + *

+ * Example: + *

+     *  Bidi bidi = new Bidi();
+     *  String text = "abc 123 DEFG xyz";
+     *  bidi.setPara(text, Bidi.RTL, null);
+     *  int i, count=bidi.countRuns(), logicalStart, visualIndex=0, length;
+     *  BidiRun run;
+     *  for (i = 0; i < count; ++i) {
+     *      run = bidi.getVisualRun(i);
+     *      logicalStart = run.getStart();
+     *      length = run.getLength();
+     *      if (Bidi.LTR == run.getEmbeddingLevel()) {
+     *          do { // LTR
+     *              show_char(text.charAt(logicalStart++), visualIndex++);
+     *          } while (--length > 0);
+     *      } else {
+     *          logicalStart += length;  // logicalLimit
+     *          do { // RTL
+     *              show_char(text.charAt(--logicalStart), visualIndex++);
+     *          } while (--length > 0);
+     *      }
+     *  }
+     * 
+ *

+ * Note that in right-to-left runs, code like this places + * second surrogates before first ones (which is generally a bad idea) + * and combining characters before base characters. + *

+ * Use of {@link #writeReordered}, optionally with the + * {@link #KEEP_BASE_COMBINING} option, can be considered in + * order to avoid these issues. + * + * @param runIndex is the number of the run in visual order, in the + * range [0..countRuns()-1]. + * + * @return a BidiRun object containing the details of the run. The + * directionality of the run is + * LTR==0 or RTL==1, + * never MIXED. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @throws IllegalArgumentException if runIndex is not in + * the range 0<=runIndex<countRuns() + * + * @see #countRuns() + * @see com.ibm.icu.text.BidiRun + * @see com.ibm.icu.text.BidiRun#getStart() + * @see com.ibm.icu.text.BidiRun#getLength() + * @see com.ibm.icu.text.BidiRun#getEmbeddingLevel() + * @stable ICU 3.8 + */ + BidiRun getVisualRun(int runIndex) + { + verifyValidParaOrLine(); + BidiLine.getRuns(this); + verifyRange(runIndex, 0, runCount); + return BidiLine.getVisualRun(this, runIndex); + } + + /** + * Get a visual-to-logical index map (array) for the characters in the + * Bidi (paragraph or line) object. + *

+ * Some values in the map may be MAP_NOWHERE if the + * corresponding text characters are Bidi marks inserted in the visual + * output by the option OPTION_INSERT_MARKS. + *

+ * When the visual output is altered by using options of + * writeReordered() such as INSERT_LRM_FOR_NUMERIC, + * KEEP_BASE_COMBINING, OUTPUT_REVERSE, + * REMOVE_BIDI_CONTROLS, the logical positions returned may not + * be correct. It is advised to use, when possible, reordering options + * such as {@link #OPTION_INSERT_MARKS} and {@link #OPTION_REMOVE_CONTROLS}. + * + * @return an array of getResultLength() + * indexes which will reflect the reordering of the characters.

+ * The index map will result in + * indexMap[visualIndex]==logicalIndex, where + * indexMap represents the returned array. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * + * @see #getLogicalMap + * @see #getLogicalIndex + * @see #getResultLength + * @see #MAP_NOWHERE + * @see #OPTION_INSERT_MARKS + * @see #writeReordered + * @stable ICU 3.8 + */ + private int[] getVisualMap() + { + /* countRuns() checks successful call to setPara/setLine */ + countRuns(); + if (resultLength <= 0) { + return new int[0]; + } + return BidiLine.getVisualMap(this); + } + + /** + * This is a convenience method that does not use a Bidi object. + * It is intended to be used for when an application has determined the levels + * of objects (character sequences) and just needs to have them reordered (L2). + * This is equivalent to using getVisualMap() on a + * Bidi object. + * + * @param levels is an array of levels that have been determined by + * the application. + * + * @return an array of levels.length + * indexes which will reflect the reordering of the characters.

+ * The index map will result in + * indexMap[visualIndex]==logicalIndex, where + * indexMap represents the returned array. + * + * @stable ICU 3.8 + */ + private static int[] reorderVisual(byte[] levels) + { + return BidiLine.reorderVisual(levels); + } + + /** + * Constant indicating that the base direction depends on the first strong + * directional character in the text according to the Unicode Bidirectional + * Algorithm. If no strong directional character is present, the base + * direction is right-to-left. + * @stable ICU 3.8 + */ + public static final int DIRECTION_DEFAULT_RIGHT_TO_LEFT = LEVEL_DEFAULT_RTL; + + /** + * Create Bidi from the given text, embedding, and direction information. + * The embeddings array may be null. If present, the values represent + * embedding level information. Negative values from -1 to -61 indicate + * overrides at the absolute value of the level. Positive values from 1 to + * 61 indicate embeddings. Where values are zero, the base embedding level + * as determined by the base direction is assumed.

+ * + * Note: this constructor calls setPara() internally. + * + * @param text an array containing the paragraph of text to process. + * @param textStart the index into the text array of the start of the + * paragraph. + * @param embeddings an array containing embedding values for each character + * in the paragraph. This can be null, in which case it is assumed + * that there is no external embedding information. + * @param embStart the index into the embedding array of the start of the + * paragraph. + * @param paragraphLength the length of the paragraph in the text and + * embeddings arrays. + * @param flags a collection of flags that control the algorithm. The + * algorithm understands the flags DIRECTION_LEFT_TO_RIGHT, + * DIRECTION_RIGHT_TO_LEFT, DIRECTION_DEFAULT_LEFT_TO_RIGHT, and + * DIRECTION_DEFAULT_RIGHT_TO_LEFT. Other values are reserved. + * + * @throws IllegalArgumentException if the values in embeddings are + * not within the allowed range + * + * @see #DIRECTION_LEFT_TO_RIGHT + * @see #DIRECTION_RIGHT_TO_LEFT + * @see #DIRECTION_DEFAULT_LEFT_TO_RIGHT + * @see #DIRECTION_DEFAULT_RIGHT_TO_LEFT + * @stable ICU 3.8 + */ + public BidiBase(char[] text, + int textStart, + byte[] embeddings, + int embStart, + int paragraphLength, + int flags) + { + this(0, 0); + byte paraLvl; + switch (flags) { + case Bidi.DIRECTION_LEFT_TO_RIGHT: + default: + paraLvl = LTR; + break; + case Bidi.DIRECTION_RIGHT_TO_LEFT: + paraLvl = RTL; + break; + case Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT: + paraLvl = LEVEL_DEFAULT_LTR; + break; + case Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT: + paraLvl = LEVEL_DEFAULT_RTL; + break; + } + byte[] paraEmbeddings; + if (embeddings == null) { + paraEmbeddings = null; + } else { + paraEmbeddings = new byte[paragraphLength]; + byte lev; + for (int i = 0; i < paragraphLength; i++) { + lev = embeddings[i + embStart]; + if (lev < 0) { + lev = (byte)((- lev) | LEVEL_OVERRIDE); + } else if (lev == 0) { + lev = paraLvl; + if (paraLvl > MAX_EXPLICIT_LEVEL) { + lev &= 1; + } + } + paraEmbeddings[i] = lev; + } + } + + char[] paraText = new char[paragraphLength]; + System.arraycopy(text, textStart, paraText, 0, paragraphLength); + setPara(paraText, paraLvl, paraEmbeddings); + } + + /** + * Return true if the line is not left-to-right or right-to-left. This means + * it either has mixed runs of left-to-right and right-to-left text, or the + * base direction differs from the direction of the only run of text. + * + * @return true if the line is not left-to-right or right-to-left. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara + * @stable ICU 3.8 + */ + public boolean isMixed() + { + return (!isLeftToRight() && !isRightToLeft()); + } + + /** + * Return true if the line is all left-to-right text and the base direction + * is left-to-right. + * + * @return true if the line is all left-to-right text and the base direction + * is left-to-right. + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara + * @stable ICU 3.8 + */ + public boolean isLeftToRight() + { + return (getDirection() == LTR && (paraLevel & 1) == 0); + } + + /** + * Return true if the line is all right-to-left text, and the base direction + * is right-to-left + * + * @return true if the line is all right-to-left text, and the base + * direction is right-to-left + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara + * @stable ICU 3.8 + */ + public boolean isRightToLeft() + { + return (getDirection() == RTL && (paraLevel & 1) == 1); + } + + /** + * Return true if the base direction is left-to-right + * + * @return true if the base direction is left-to-right + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * + * @stable ICU 3.8 + */ + public boolean baseIsLeftToRight() + { + return (getParaLevel() == LTR); + } + + /** + * Return the base level (0 if left-to-right, 1 if right-to-left). + * + * @return the base level + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * + * @stable ICU 3.8 + */ + public int getBaseLevel() + { + return getParaLevel(); + } + + /** + * Compute the logical to visual run mapping + */ + void getLogicalToVisualRunsMap() + { + if (isGoodLogicalToVisualRunsMap) { + return; + } + int count = countRuns(); + if ((logicalToVisualRunsMap == null) || + (logicalToVisualRunsMap.length < count)) { + logicalToVisualRunsMap = new int[count]; + } + int i; + long[] keys = new long[count]; + for (i = 0; i < count; i++) { + keys[i] = ((long)(runs[i].start)<<32) + i; + } + Arrays.sort(keys); + for (i = 0; i < count; i++) { + logicalToVisualRunsMap[i] = (int)(keys[i] & 0x00000000FFFFFFFF); + } + isGoodLogicalToVisualRunsMap = true; + } + + /** + * Return the level of the nth logical run in this line. + * + * @param run the index of the run, between 0 and countRuns()-1 + * + * @return the level of the run + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @throws IllegalArgumentException if run is not in + * the range 0<=run<countRuns() + * @stable ICU 3.8 + */ + public int getRunLevel(int run) + { + verifyValidParaOrLine(); + BidiLine.getRuns(this); + + // for backward compatibility + if (run < 0 || run >= runCount) { + return getParaLevel(); + } + + getLogicalToVisualRunsMap(); + return runs[logicalToVisualRunsMap[run]].level; + } + + /** + * Return the index of the character at the start of the nth logical run in + * this line, as an offset from the start of the line. + * + * @param run the index of the run, between 0 and countRuns() + * + * @return the start of the run + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @throws IllegalArgumentException if run is not in + * the range 0<=run<countRuns() + * @stable ICU 3.8 + */ + public int getRunStart(int run) + { + verifyValidParaOrLine(); + BidiLine.getRuns(this); + + // for backward compatibility + if (runCount == 1) { + return 0; + } else if (run == runCount) { + return length; + } + + getLogicalToVisualRunsMap(); + return runs[logicalToVisualRunsMap[run]].start; + } + + /** + * Return the index of the character past the end of the nth logical run in + * this line, as an offset from the start of the line. For example, this + * will return the length of the line for the last run on the line. + * + * @param run the index of the run, between 0 and countRuns() + * + * @return the limit of the run + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * @throws IllegalArgumentException if run is not in + * the range 0<=run<countRuns() + * @stable ICU 3.8 + */ + public int getRunLimit(int run) + { + verifyValidParaOrLine(); + BidiLine.getRuns(this); + + // for backward compatibility + if (runCount == 1) { + return length; + } + + getLogicalToVisualRunsMap(); + int idx = logicalToVisualRunsMap[run]; + int len = idx == 0 ? runs[idx].limit : + runs[idx].limit - runs[idx-1].limit; + return runs[idx].start + len; + } + + /** + * Return true if the specified text requires bidi analysis. If this returns + * false, the text will display left-to-right. Clients can then avoid + * constructing a Bidi object. Text in the Arabic Presentation Forms area of + * Unicode is presumed to already be shaped and ordered for display, and so + * will not cause this method to return true. + * + * @param text the text containing the characters to test + * @param start the start of the range of characters to test + * @param limit the limit of the range of characters to test + * + * @return true if the range of characters requires bidi analysis + * + * @stable ICU 3.8 + */ + public static boolean requiresBidi(char[] text, + int start, + int limit) + { + final int RTLMask = (1 << R | + 1 << AL | + 1 << RLE | + 1 << RLO | + 1 << AN); + + if (0 > start || start > limit || limit > text.length) { + throw new IllegalArgumentException("Value start " + start + + " is out of range 0 to " + limit + ", or limit " + limit + + " is beyond the text length " + text.length); + } + + for (int i = start; i < limit; ++i) { + if (Character.isHighSurrogate(text[i]) && i < (limit-1) && + Character.isLowSurrogate(text[i+1])) { + if (((1 << UCharacter.getDirection(Character.codePointAt(text, i))) & RTLMask) != 0) { + return true; + } + } else if (((1 << UCharacter.getDirection(text[i])) & RTLMask) != 0) { + return true; + } + } + + return false; + } + + /** + * Reorder the objects in the array into visual order based on their levels. + * This is a utility method to use when you have a collection of objects + * representing runs of text in logical order, each run containing text at a + * single level. The elements at index from + * objectStart up to objectStart + count in the + * objects array will be reordered into visual order assuming + * each run of text has the level indicated by the corresponding element in + * the levels array (at index - objectStart + levelStart). + * + * @param levels an array representing the bidi level of each object + * @param levelStart the start position in the levels array + * @param objects the array of objects to be reordered into visual order + * @param objectStart the start position in the objects array + * @param count the number of objects to reorder + * @stable ICU 3.8 + */ + public static void reorderVisually(byte[] levels, + int levelStart, + Object[] objects, + int objectStart, + int count) + { + // for backward compatibility + if (0 > levelStart || levels.length <= levelStart) { + throw new IllegalArgumentException("Value levelStart " + + levelStart + " is out of range 0 to " + + (levels.length-1)); + } + if (0 > objectStart || objects.length <= objectStart) { + throw new IllegalArgumentException("Value objectStart " + + objectStart + " is out of range 0 to " + + (objects.length-1)); + } + if (0 > count || objects.length - count < objectStart) { + throw new IllegalArgumentException("Value count " + + count + " is less than zero, or objectStart + count" + + " is beyond objects length " + objects.length); + } + + byte[] reorderLevels = new byte[count]; + System.arraycopy(levels, levelStart, reorderLevels, 0, count); + int[] indexMap = reorderVisual(reorderLevels); + Object[] temp = new Object[count]; + System.arraycopy(objects, objectStart, temp, 0, count); + for (int i = 0; i < count; ++i) { + objects[objectStart + i] = temp[indexMap[i]]; + } + } + + /** + * Take a Bidi object containing the reordering + * information for a piece of text (one or more paragraphs) set by + * setPara() or for a line of text set by setLine() + * and return a string containing the reordered text. + * + *

The text may have been aliased (only a reference was stored + * without copying the contents), thus it must not have been modified + * since the setPara() call.

+ * + * This method preserves the integrity of characters with multiple + * code units and (optionally) combining characters. + * Characters in RTL runs can be replaced by mirror-image characters + * in the returned string. Note that "real" mirroring has to be done in a + * rendering engine by glyph selection and that for many "mirrored" + * characters there are no Unicode characters as mirror-image equivalents. + * There are also options to insert or remove Bidi control + * characters; see the descriptions of the return value and the + * options parameter, and of the option bit flags. + * + * @param options A bit set of options for the reordering that control + * how the reordered text is written. + * The options include mirroring the characters on a code + * point basis and inserting LRM characters, which is used + * especially for transforming visually stored text + * to logically stored text (although this is still an + * imperfect implementation of an "inverse Bidi" algorithm + * because it uses the "forward Bidi" algorithm at its core). + * The available options are: + * DO_MIRRORING, + * INSERT_LRM_FOR_NUMERIC, + * KEEP_BASE_COMBINING, + * OUTPUT_REVERSE, + * REMOVE_BIDI_CONTROLS, + * STREAMING + * + * @return The reordered text. + * If the INSERT_LRM_FOR_NUMERIC option is set, then + * the length of the returned string could be as large as + * getLength()+2*countRuns().
+ * If the REMOVE_BIDI_CONTROLS option is set, then the + * length of the returned string may be less than + * getLength().
+ * If none of these options is set, then the length of the returned + * string will be exactly getProcessedLength(). + * + * @throws IllegalStateException if this call is not preceded by a successful + * call to setPara or setLine + * + * @see #DO_MIRRORING + * @see #INSERT_LRM_FOR_NUMERIC + * @see #KEEP_BASE_COMBINING + * @see #OUTPUT_REVERSE + * @see #REMOVE_BIDI_CONTROLS + * @see #OPTION_STREAMING + * @see #getProcessedLength + * @stable ICU 3.8 + */ + public String writeReordered(int options) + { + verifyValidParaOrLine(); + if (length == 0) { + /* nothing to do */ + return ""; + } + return BidiWriter.writeReordered(this, options); + } + + /** + * Display the bidi internal state, used in debugging. + */ + public String toString() { + StringBuilder buf = new StringBuilder(getClass().getName()); + + buf.append("[dir: "); + buf.append(direction); + buf.append(" baselevel: "); + buf.append(paraLevel); + buf.append(" length: "); + buf.append(length); + buf.append(" runs: "); + if (levels == null) { + buf.append("none"); + } else { + buf.append('['); + buf.append(levels[0]); + for (int i = 1; i < levels.length; i++) { + buf.append(' '); + buf.append(levels[i]); + } + buf.append(']'); + } + buf.append(" text: [0x"); + buf.append(Integer.toHexString(text[0])); + for (int i = 1; i < text.length; i++) { + buf.append(" 0x"); + buf.append(Integer.toHexString(text[i])); + } + buf.append("]]"); + + return buf.toString(); + } + + /** + * A class that provides access to constants defined by + * java.awt.font.TextAttribute without creating a static dependency. + */ + private static class TextAttributeConstants { + // Make sure to load the AWT's TextAttribute class before using the constants, if any. + static { + try { + Class.forName("java.awt.font.TextAttribute", true, null); + } catch (ClassNotFoundException e) {} + } + static final JavaAWTFontAccess jafa = SharedSecrets.getJavaAWTFontAccess(); + + /** + * TextAttribute instances (or a fake Attribute type if + * java.awt.font.TextAttribute is not present) + */ + static final AttributedCharacterIterator.Attribute RUN_DIRECTION = + getTextAttribute("RUN_DIRECTION"); + static final AttributedCharacterIterator.Attribute NUMERIC_SHAPING = + getTextAttribute("NUMERIC_SHAPING"); + static final AttributedCharacterIterator.Attribute BIDI_EMBEDDING = + getTextAttribute("BIDI_EMBEDDING"); + + /** + * TextAttribute.RUN_DIRECTION_LTR + */ + static final Boolean RUN_DIRECTION_LTR = (jafa == null) ? + Boolean.FALSE : (Boolean)jafa.getTextAttributeConstant("RUN_DIRECTION_LTR"); + + @SuppressWarnings("serial") + private static AttributedCharacterIterator.Attribute + getTextAttribute(String name) + { + if (jafa == null) { + // fake attribute + return new AttributedCharacterIterator.Attribute(name) { }; + } else { + return (AttributedCharacterIterator.Attribute)jafa.getTextAttributeConstant(name); + } + } + } + + /** + * A class that provides access to java.awt.font.NumericShaper without + * creating a static dependency. + */ + private static class NumericShapings { + // Make sure to load the AWT's NumericShaper class before calling shape, if any. + static { + try { + Class.forName("java.awt.font.NumericShaper", true, null); + } catch (ClassNotFoundException e) {} + } + static final JavaAWTFontAccess jafa = SharedSecrets.getJavaAWTFontAccess(); + + /** + * Invokes NumericShaping shape(text,start,count) method. + */ + static void shape(Object shaper, char[] text, int start, int count) { + if (jafa != null) { + jafa.shape(shaper, text, start, count); + } + } + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiLine.class b/tests/test_data/std/jdk/internal/icu/text/BidiLine.class new file mode 100644 index 00000000..423ee209 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiLine.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiLine.java b/tests/test_data/std/jdk/internal/icu/text/BidiLine.java new file mode 100644 index 00000000..54c7e9de --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/BidiLine.java @@ -0,0 +1,836 @@ +/* + * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +******************************************************************************* +* Copyright (C) 2001-2014, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +*/ +/* Written by Simon Montagu, Matitiahu Allouche + * (ported from C code written by Markus W. Scherer) + */ + +package jdk.internal.icu.text; + +import java.text.Bidi; +import java.util.Arrays; + +final class BidiLine { + + /* + * General remarks about the functions in this file: + * + * These functions deal with the aspects of potentially mixed-directional + * text in a single paragraph or in a line of a single paragraph + * which has already been processed according to + * the Unicode 3.0 Bidi algorithm as defined in + * Unicode Standard Annex #9: + * Unicode Bidirectional Algorithm, version 13, + * also described in The Unicode Standard, Version 4.0.1 . + * + * This means that there is a Bidi object with a levels + * and a dirProps array. + * paraLevel and direction are also set. + * Only if the length of the text is zero, then levels==dirProps==NULL. + * + * The overall directionality of the paragraph + * or line is used to bypass the reordering steps if possible. + * Even purely RTL text does not need reordering there because + * the getLogical/VisualIndex() methods can compute the + * index on the fly in such a case. + * + * The implementation of the access to same-level-runs and of the reordering + * do attempt to provide better performance and less memory usage compared to + * a direct implementation of especially rule (L2) with an array of + * one (32-bit) integer per text character. + * + * Here, the levels array is scanned as soon as necessary, and a vector of + * same-level-runs is created. Reordering then is done on this vector. + * For each run of text positions that were resolved to the same level, + * only 8 bytes are stored: the first text position of the run and the visual + * position behind the run after reordering. + * One sign bit is used to hold the directionality of the run. + * This is inefficient if there are many very short runs. If the average run + * length is <2, then this uses more memory. + * + * In a further attempt to save memory, the levels array is never changed + * after all the resolution rules (Xn, Wn, Nn, In). + * Many methods have to consider the field trailingWSStart: + * if it is less than length, then there is an implicit trailing run + * at the paraLevel, + * which is not reflected in the levels array. + * This allows a line Bidi object to use the same levels array as + * its paragraph parent object. + * + * When a Bidi object is created for a line of a paragraph, then the + * paragraph's levels and dirProps arrays are reused by way of setting + * a pointer into them, not by copying. This again saves memory and forbids to + * change the now shared levels for (L1). + */ + + /* handle trailing WS (L1) -------------------------------------------------- */ + + /* + * setTrailingWSStart() sets the start index for a trailing + * run of WS in the line. This is necessary because we do not modify + * the paragraph's levels array that we just point into. + * Using trailingWSStart is another form of performing (L1). + * + * To make subsequent operations easier, we also include the run + * before the WS if it is at the paraLevel - we merge the two here. + * + * This method is called only from setLine(), so paraLevel is + * set correctly for the line even when contextual multiple paragraphs. + */ + + static void setTrailingWSStart(BidiBase bidiBase) + { + byte[] dirProps = bidiBase.dirProps; + byte[] levels = bidiBase.levels; + int start = bidiBase.length; + byte paraLevel = bidiBase.paraLevel; + + /* If the line is terminated by a block separator, all preceding WS etc... + are already set to paragraph level. + Setting trailingWSStart to pBidi->length will avoid changing the + level of B chars from 0 to paraLevel in getLevels when + orderParagraphsLTR==true + */ + if (dirProps[start - 1] == BidiBase.B) { + bidiBase.trailingWSStart = start; /* currently == bidiBase.length */ + return; + } + /* go backwards across all WS, BN, explicit codes */ + while (start > 0 && + (BidiBase.DirPropFlag(dirProps[start - 1]) & BidiBase.MASK_WS) != 0) { + --start; + } + + /* if the WS run can be merged with the previous run then do so here */ + while (start > 0 && levels[start - 1] == paraLevel) { + --start; + } + + bidiBase.trailingWSStart=start; + } + + static Bidi setLine(BidiBase paraBidi, + Bidi newBidi, BidiBase lineBidi, + int start, int limit) { + int length; + + /* set the values in lineBidi from its paraBidi parent */ + /* class members are already initialized to 0 */ + // lineBidi.paraBidi = null; /* mark unfinished setLine */ + // lineBidi.flags = 0; + // lineBidi.controlCount = 0; + + length = lineBidi.length = lineBidi.originalLength = + lineBidi.resultLength = limit - start; + + lineBidi.text = new char[length]; + System.arraycopy(paraBidi.text, start, lineBidi.text, 0, length); + lineBidi.paraLevel = paraBidi.GetParaLevelAt(start); + lineBidi.paraCount = paraBidi.paraCount; + lineBidi.runs = new BidiRun[0]; + lineBidi.reorderingMode = paraBidi.reorderingMode; + lineBidi.reorderingOptions = paraBidi.reorderingOptions; + if (paraBidi.controlCount > 0) { + int j; + for (j = start; j < limit; j++) { + if (BidiBase.IsBidiControlChar(paraBidi.text[j])) { + lineBidi.controlCount++; + } + } + lineBidi.resultLength -= lineBidi.controlCount; + } + /* copy proper subset of DirProps */ + lineBidi.getDirPropsMemory(length); + lineBidi.dirProps = lineBidi.dirPropsMemory; + System.arraycopy(paraBidi.dirProps, start, lineBidi.dirProps, 0, + length); + /* copy proper subset of Levels */ + lineBidi.getLevelsMemory(length); + lineBidi.levels = lineBidi.levelsMemory; + System.arraycopy(paraBidi.levels, start, lineBidi.levels, 0, + length); + lineBidi.runCount = -1; + + if (paraBidi.direction != BidiBase.MIXED) { + /* the parent is already trivial */ + lineBidi.direction = paraBidi.direction; + + /* + * The parent's levels are all either + * implicitly or explicitly ==paraLevel; + * do the same here. + */ + if (paraBidi.trailingWSStart <= start) { + lineBidi.trailingWSStart = 0; + } else if (paraBidi.trailingWSStart < limit) { + lineBidi.trailingWSStart = paraBidi.trailingWSStart - start; + } else { + lineBidi.trailingWSStart = length; + } + } else { + byte[] levels = lineBidi.levels; + int i, trailingWSStart; + byte level; + + setTrailingWSStart(lineBidi); + trailingWSStart = lineBidi.trailingWSStart; + + /* recalculate lineBidiBase.direction */ + if (trailingWSStart == 0) { + /* all levels are at paraLevel */ + lineBidi.direction = (byte)(lineBidi.paraLevel & 1); + } else { + /* get the level of the first character */ + level = (byte)(levels[0] & 1); + + /* if there is anything of a different level, then the line + is mixed */ + if (trailingWSStart < length && + (lineBidi.paraLevel & 1) != level) { + /* the trailing WS is at paraLevel, which differs from + levels[0] */ + lineBidi.direction = BidiBase.MIXED; + } else { + /* see if levels[1..trailingWSStart-1] have the same + direction as levels[0] and paraLevel */ + for (i = 1; ; i++) { + if (i == trailingWSStart) { + /* the direction values match those in level */ + lineBidi.direction = level; + break; + } else if ((levels[i] & 1) != level) { + lineBidi.direction = BidiBase.MIXED; + break; + } + } + } + } + + switch(lineBidi.direction) { + case Bidi.DIRECTION_LEFT_TO_RIGHT: + /* make sure paraLevel is even */ + lineBidi.paraLevel = (byte) + ((lineBidi.paraLevel + 1) & ~1); + + /* all levels are implicitly at paraLevel (important for + getLevels()) */ + lineBidi.trailingWSStart = 0; + break; + case Bidi.DIRECTION_RIGHT_TO_LEFT: + /* make sure paraLevel is odd */ + lineBidi.paraLevel |= 1; + + /* all levels are implicitly at paraLevel (important for + getLevels()) */ + lineBidi.trailingWSStart = 0; + break; + default: + break; + } + } + + lineBidi.paraBidi = paraBidi; /* mark successful setLine */ + + return newBidi; + } + + static byte getLevelAt(BidiBase bidiBase, int charIndex) + { + /* return paraLevel if in the trailing WS run, otherwise the real level */ + if (bidiBase.direction != BidiBase.MIXED || charIndex >= bidiBase.trailingWSStart) { + return bidiBase.GetParaLevelAt(charIndex); + } else { + return bidiBase.levels[charIndex]; + } + } + + static byte[] getLevels(BidiBase bidiBase) + { + int start = bidiBase.trailingWSStart; + int length = bidiBase.length; + + if (start != length) { + /* the current levels array does not reflect the WS run */ + /* + * After the previous if(), we know that the levels array + * has an implicit trailing WS run and therefore does not fully + * reflect itself all the levels. + * This must be a Bidi object for a line, and + * we need to create a new levels array. + */ + /* bidiBase.paraLevel is ok even if contextual multiple paragraphs, + since bidiBase is a line object */ + Arrays.fill(bidiBase.levels, start, length, bidiBase.paraLevel); + + /* this new levels array is set for the line and reflects the WS run */ + bidiBase.trailingWSStart = length; + } + if (length < bidiBase.levels.length) { + byte[] levels = new byte[length]; + System.arraycopy(bidiBase.levels, 0, levels, 0, length); + return levels; + } + return bidiBase.levels; + } + + static BidiRun getVisualRun(BidiBase bidiBase, int runIndex) { + int start = bidiBase.runs[runIndex].start; + int limit; + byte level = bidiBase.runs[runIndex].level; + + if (runIndex > 0) { + limit = start + + bidiBase.runs[runIndex].limit - + bidiBase.runs[runIndex - 1].limit; + } else { + limit = start + bidiBase.runs[0].limit; + } + return new BidiRun(start, limit, level); + } + + /* in trivial cases there is only one trivial run; called by getRuns() */ + private static void getSingleRun(BidiBase bidiBase, byte level) { + /* simple, single-run case */ + bidiBase.runs = bidiBase.simpleRuns; + bidiBase.runCount = 1; + + /* fill and reorder the single run */ + bidiBase.runs[0] = new BidiRun(0, bidiBase.length, level); + } + + /* reorder the runs array (L2) ---------------------------------------------- */ + + /* + * Reorder the same-level runs in the runs array. + * Here, runCount>1 and maxLevel>=minLevel>=paraLevel. + * All the visualStart fields=logical start before reordering. + * The "odd" bits are not set yet. + * + * Reordering with this data structure lends itself to some handy shortcuts: + * + * Since each run is moved but not modified, and since at the initial maxLevel + * each sequence of same-level runs consists of only one run each, we + * don't need to do anything there and can predecrement maxLevel. + * In many simple cases, the reordering is thus done entirely in the + * index mapping. + * Also, reordering occurs only down to the lowest odd level that occurs, + * which is minLevel|1. However, if the lowest level itself is odd, then + * in the last reordering the sequence of the runs at this level or higher + * will be all runs, and we don't need the elaborate loop to search for them. + * This is covered by ++minLevel instead of minLevel|=1 followed + * by an extra reorder-all after the reorder-some loop. + * About a trailing WS run: + * Such a run would need special treatment because its level is not + * reflected in levels[] if this is not a paragraph object. + * Instead, all characters from trailingWSStart on are implicitly at + * paraLevel. + * However, for all maxLevel>paraLevel, this run will never be reordered + * and does not need to be taken into account. maxLevel==paraLevel is only reordered + * if minLevel==paraLevel is odd, which is done in the extra segment. + * This means that for the main reordering loop we don't need to consider + * this run and can --runCount. If it is later part of the all-runs + * reordering, then runCount is adjusted accordingly. + */ + private static void reorderLine(BidiBase bidiBase, byte minLevel, byte maxLevel) { + + /* nothing to do? */ + if (maxLevel<=(minLevel|1)) { + return; + } + + BidiRun[] runs; + BidiRun tempRun; + byte[] levels; + int firstRun, endRun, limitRun, runCount; + + /* + * Reorder only down to the lowest odd level + * and reorder at an odd minLevel in a separate, simpler loop. + * See comments above for why minLevel is always incremented. + */ + ++minLevel; + + runs = bidiBase.runs; + levels = bidiBase.levels; + runCount = bidiBase.runCount; + + /* do not include the WS run at paraLevel<=old minLevel except in the simple loop */ + if (bidiBase.trailingWSStart < bidiBase.length) { + --runCount; + } + + while (--maxLevel >= minLevel) { + firstRun = 0; + + /* loop for all sequences of runs */ + for ( ; ; ) { + /* look for a sequence of runs that are all at >=maxLevel */ + /* look for the first run of such a sequence */ + while (firstRun < runCount && levels[runs[firstRun].start] < maxLevel) { + ++firstRun; + } + if (firstRun >= runCount) { + break; /* no more such runs */ + } + + /* look for the limit run of such a sequence (the run behind it) */ + for (limitRun = firstRun; ++limitRun < runCount && + levels[runs[limitRun].start]>=maxLevel; ) {} + + /* Swap the entire sequence of runs from firstRun to limitRun-1. */ + endRun = limitRun - 1; + while (firstRun < endRun) { + tempRun = runs[firstRun]; + runs[firstRun] = runs[endRun]; + runs[endRun] = tempRun; + ++firstRun; + --endRun; + } + + if (limitRun == runCount) { + break; /* no more such runs */ + } else { + firstRun = limitRun + 1; + } + } + } + + /* now do maxLevel==old minLevel (==odd!), see above */ + if ((minLevel & 1) == 0) { + firstRun = 0; + + /* include the trailing WS run in this complete reordering */ + if (bidiBase.trailingWSStart == bidiBase.length) { + --runCount; + } + + /* Swap the entire sequence of all runs. (endRun==runCount) */ + while (firstRun < runCount) { + tempRun = runs[firstRun]; + runs[firstRun] = runs[runCount]; + runs[runCount] = tempRun; + ++firstRun; + --runCount; + } + } + } + + /* compute the runs array --------------------------------------------------- */ + + static int getRunFromLogicalIndex(BidiBase bidiBase, int logicalIndex) { + BidiRun[] runs = bidiBase.runs; + int runCount = bidiBase.runCount, visualStart = 0, i, length, logicalStart; + + for (i = 0; i < runCount; i++) { + length = runs[i].limit - visualStart; + logicalStart = runs[i].start; + if ((logicalIndex >= logicalStart) && (logicalIndex < (logicalStart+length))) { + return i; + } + visualStart += length; + } + /* we should never get here */ + throw new IllegalStateException("Internal ICU error in getRunFromLogicalIndex"); + } + + /* + * Compute the runs array from the levels array. + * After getRuns() returns true, runCount is guaranteed to be >0 + * and the runs are reordered. + * Odd-level runs have visualStart on their visual right edge and + * they progress visually to the left. + * If option OPTION_INSERT_MARKS is set, insertRemove will contain the + * sum of appropriate LRM/RLM_BEFORE/AFTER flags. + * If option OPTION_REMOVE_CONTROLS is set, insertRemove will contain the + * negative number of BiDi control characters within this run. + */ + static void getRuns(BidiBase bidiBase) { + /* + * This method returns immediately if the runs are already set. This + * includes the case of length==0 (handled in setPara).. + */ + if (bidiBase.runCount >= 0) { + return; + } + if (bidiBase.direction != BidiBase.MIXED) { + /* simple, single-run case - this covers length==0 */ + /* bidiBase.paraLevel is ok even for contextual multiple paragraphs */ + getSingleRun(bidiBase, bidiBase.paraLevel); + } else /* BidiBase.MIXED, length>0 */ { + /* mixed directionality */ + int length = bidiBase.length, limit; + byte[] levels = bidiBase.levels; + int i, runCount; + byte level = -1; /* initialize with no valid level */ + /* + * If there are WS characters at the end of the line + * and the run preceding them has a level different from + * paraLevel, then they will form their own run at paraLevel (L1). + * Count them separately. + * We need some special treatment for this in order to not + * modify the levels array which a line Bidi object shares + * with its paragraph parent and its other line siblings. + * In other words, for the trailing WS, it may be + * levels[]!=paraLevel but we have to treat it like it were so. + */ + limit = bidiBase.trailingWSStart; + /* count the runs, there is at least one non-WS run, and limit>0 */ + runCount = 0; + for (i = 0; i < limit; ++i) { + /* increment runCount at the start of each run */ + if (levels[i] != level) { + ++runCount; + level = levels[i]; + } + } + + /* + * We don't need to see if the last run can be merged with a trailing + * WS run because setTrailingWSStart() would have done that. + */ + if (runCount == 1 && limit == length) { + /* There is only one non-WS run and no trailing WS-run. */ + getSingleRun(bidiBase, levels[0]); + } else /* runCount>1 || limit 1 */ + bidiBase.getRunsMemory(runCount); + runs = bidiBase.runsMemory; + + /* set the runs */ + /* FOOD FOR THOUGHT: this could be optimized, e.g.: + * 464->444, 484->444, 575->555, 595->555 + * However, that would take longer. Check also how it would + * interact with BiDi control removal and inserting Marks. + */ + runIndex = 0; + + /* search for the run limits and initialize visualLimit values with the run lengths */ + i = 0; + do { + /* prepare this run */ + start = i; + level = levels[i]; + if (level < minLevel) { + minLevel = level; + } + if (level > maxLevel) { + maxLevel = level; + } + + /* look for the run limit */ + while (++i < limit && levels[i] == level) {} + + /* i is another run limit */ + runs[runIndex] = new BidiRun(start, i - start, level); + ++runIndex; + } while (i < limit); + + if (limit < length) { + /* there is a separate WS run */ + runs[runIndex] = new BidiRun(limit, length - limit, bidiBase.paraLevel); + /* For the trailing WS run, bidiBase.paraLevel is ok even + if contextual multiple paragraphs. */ + if (bidiBase.paraLevel < minLevel) { + minLevel = bidiBase.paraLevel; + } + } + + /* set the object fields */ + bidiBase.runs = runs; + bidiBase.runCount = runCount; + + reorderLine(bidiBase, minLevel, maxLevel); + + /* now add the direction flags and adjust the visualLimit's to be just that */ + /* this loop will also handle the trailing WS run */ + limit = 0; + for (i = 0; i < runCount; ++i) { + runs[i].level = levels[runs[i].start]; + limit = (runs[i].limit += limit); + } + + /* Set the embedding level for the trailing WS run. */ + /* For a RTL paragraph, it will be the *first* run in visual order. */ + /* For the trailing WS run, bidiBase.paraLevel is ok even if + contextual multiple paragraphs. */ + if (runIndex < runCount) { + int trailingRun = ((bidiBase.paraLevel & 1) != 0)? 0 : runIndex; + runs[trailingRun].level = bidiBase.paraLevel; + } + } + } + + /* handle insert LRM/RLM BEFORE/AFTER run */ + if (bidiBase.insertPoints.size > 0) { + BidiBase.Point point; + int runIndex, ip; + for (ip = 0; ip < bidiBase.insertPoints.size; ip++) { + point = bidiBase.insertPoints.points[ip]; + runIndex = getRunFromLogicalIndex(bidiBase, point.pos); + bidiBase.runs[runIndex].insertRemove |= point.flag; + } + } + + /* handle remove BiDi control characters */ + if (bidiBase.controlCount > 0) { + int runIndex, ic; + char c; + for (ic = 0; ic < bidiBase.length; ic++) { + c = bidiBase.text[ic]; + if (BidiBase.IsBidiControlChar(c)) { + runIndex = getRunFromLogicalIndex(bidiBase, ic); + bidiBase.runs[runIndex].insertRemove--; + } + } + } + } + + static int[] prepareReorder(byte[] levels, byte[] pMinLevel, byte[] pMaxLevel) + { + int start; + byte level, minLevel, maxLevel; + + if (levels == null || levels.length <= 0) { + return null; + } + + /* determine minLevel and maxLevel */ + minLevel = BidiBase.MAX_EXPLICIT_LEVEL + 1; + maxLevel = 0; + for (start = levels.length; start>0; ) { + level = levels[--start]; + if (level < 0 || level > (BidiBase.MAX_EXPLICIT_LEVEL + 1)) { + return null; + } + if (level < minLevel) { + minLevel = level; + } + if (level > maxLevel) { + maxLevel = level; + } + } + pMinLevel[0] = minLevel; + pMaxLevel[0] = maxLevel; + + /* initialize the index map */ + int[] indexMap = new int[levels.length]; + for (start = levels.length; start > 0; ) { + --start; + indexMap[start] = start; + } + + return indexMap; + } + + static int[] reorderVisual(byte[] levels) + { + byte[] aMinLevel = new byte[1]; + byte[] aMaxLevel = new byte[1]; + int start, end, limit, temp; + byte minLevel, maxLevel; + + int[] indexMap = prepareReorder(levels, aMinLevel, aMaxLevel); + if (indexMap == null) { + return null; + } + + minLevel = aMinLevel[0]; + maxLevel = aMaxLevel[0]; + + /* nothing to do? */ + if (minLevel == maxLevel && (minLevel & 1) == 0) { + return indexMap; + } + + /* reorder only down to the lowest odd level */ + minLevel |= 1; + + /* loop maxLevel..minLevel */ + do { + start = 0; + + /* loop for all sequences of levels to reorder at the current maxLevel */ + for ( ; ; ) { + /* look for a sequence of levels that are all at >=maxLevel */ + /* look for the first index of such a sequence */ + while (start < levels.length && levels[start] < maxLevel) { + ++start; + } + if (start >= levels.length) { + break; /* no more such runs */ + } + + /* look for the limit of such a sequence (the index behind it) */ + for (limit = start; ++limit < levels.length && levels[limit] >= maxLevel; ) {} + + /* + * Swap the entire interval of indexes from start to limit-1. + * We don't need to swap the levels for the purpose of this + * algorithm: the sequence of levels that we look at does not + * move anyway. + */ + end = limit - 1; + while (start < end) { + temp = indexMap[start]; + indexMap[start] = indexMap[end]; + indexMap[end] = temp; + + ++start; + --end; + } + + if (limit == levels.length) { + break; /* no more such sequences */ + } else { + start = limit + 1; + } + } + } while (--maxLevel >= minLevel); + + return indexMap; + } + + static int[] getVisualMap(BidiBase bidiBase) + { + /* fill a visual-to-logical index map using the runs[] */ + BidiRun[] runs = bidiBase.runs; + int logicalStart, visualStart, visualLimit; + int allocLength = bidiBase.length > bidiBase.resultLength ? bidiBase.length + : bidiBase.resultLength; + int[] indexMap = new int[allocLength]; + + visualStart = 0; + int idx = 0; + for (int j = 0; j < bidiBase.runCount; ++j) { + logicalStart = runs[j].start; + visualLimit = runs[j].limit; + if (runs[j].isEvenRun()) { + do { /* LTR */ + indexMap[idx++] = logicalStart++; + } while (++visualStart < visualLimit); + } else { + logicalStart += visualLimit - visualStart; /* logicalLimit */ + do { /* RTL */ + indexMap[idx++] = --logicalStart; + } while (++visualStart < visualLimit); + } + /* visualStart==visualLimit; */ + } + + if (bidiBase.insertPoints.size > 0) { + int markFound = 0, runCount = bidiBase.runCount; + int insertRemove, i, j, k; + runs = bidiBase.runs; + /* count all inserted marks */ + for (i = 0; i < runCount; i++) { + insertRemove = runs[i].insertRemove; + if ((insertRemove & (BidiBase.LRM_BEFORE|BidiBase.RLM_BEFORE)) > 0) { + markFound++; + } + if ((insertRemove & (BidiBase.LRM_AFTER|BidiBase.RLM_AFTER)) > 0) { + markFound++; + } + } + /* move back indexes by number of preceding marks */ + k = bidiBase.resultLength; + for (i = runCount - 1; i >= 0 && markFound > 0; i--) { + insertRemove = runs[i].insertRemove; + if ((insertRemove & (BidiBase.LRM_AFTER|BidiBase.RLM_AFTER)) > 0) { + indexMap[--k] = BidiBase.MAP_NOWHERE; + markFound--; + } + visualStart = i > 0 ? runs[i-1].limit : 0; + for (j = runs[i].limit - 1; j >= visualStart && markFound > 0; j--) { + indexMap[--k] = indexMap[j]; + } + if ((insertRemove & (BidiBase.LRM_BEFORE|BidiBase.RLM_BEFORE)) > 0) { + indexMap[--k] = BidiBase.MAP_NOWHERE; + markFound--; + } + } + } + else if (bidiBase.controlCount > 0) { + int runCount = bidiBase.runCount, logicalEnd; + int insertRemove, length, i, j, k, m; + char uchar; + boolean evenRun; + runs = bidiBase.runs; + visualStart = 0; + /* move forward indexes by number of preceding controls */ + k = 0; + for (i = 0; i < runCount; i++, visualStart += length) { + length = runs[i].limit - visualStart; + insertRemove = runs[i].insertRemove; + /* if no control found yet, nothing to do in this run */ + if ((insertRemove == 0) && (k == visualStart)) { + k += length; + continue; + } + /* if no control in this run */ + if (insertRemove == 0) { + visualLimit = runs[i].limit; + for (j = visualStart; j < visualLimit; j++) { + indexMap[k++] = indexMap[j]; + } + continue; + } + logicalStart = runs[i].start; + evenRun = runs[i].isEvenRun(); + logicalEnd = logicalStart + length - 1; + for (j = 0; j < length; j++) { + m = evenRun ? logicalStart + j : logicalEnd - j; + uchar = bidiBase.text[m]; + if (!BidiBase.IsBidiControlChar(uchar)) { + indexMap[k++] = m; + } + } + } + } + if (allocLength == bidiBase.resultLength) { + return indexMap; + } + int[] newMap = new int[bidiBase.resultLength]; + System.arraycopy(indexMap, 0, newMap, 0, bidiBase.resultLength); + return newMap; + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiRun.class b/tests/test_data/std/jdk/internal/icu/text/BidiRun.class new file mode 100644 index 00000000..221efc2c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiRun.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiRun.java b/tests/test_data/std/jdk/internal/icu/text/BidiRun.java new file mode 100644 index 00000000..4764e718 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/BidiRun.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + ******************************************************************************* + * (C) Copyright IBM Corp. and others, 1996-2009 - All Rights Reserved * + * * + * The original version of this source code and documentation is copyrighted * + * and owned by IBM, These materials are provided under terms of a License * + * Agreement between IBM and Sun. This technology is protected by multiple * + * US and International patents. This notice and attribution to IBM may not * + * to removed. * + ******************************************************************************* + */ +/* Written by Simon Montagu, Matitiahu Allouche + * (ported from C code written by Markus W. Scherer) + */ + +package jdk.internal.icu.text; + +/** + * A BidiRun represents a sequence of characters at the same embedding level. + * The Bidi algorithm decomposes a piece of text into sequences of characters + * at the same embedding level, each such sequence is called a "run". + * + *

A BidiRun represents such a run by storing its essential properties, + * but does not duplicate the characters which form the run. + * + *

The "limit" of the run is the position just after the + * last character, i.e., one more than that position. + * + *

This class has no public constructor, and its members cannot be + * modified by users. + * + * @see com.ibm.icu.text.Bidi + */ +class BidiRun { + + int start; /* first logical position of the run */ + int limit; /* last visual position of the run +1 */ + int insertRemove; /* if >0, flags for inserting LRM/RLM before/after run, + if <0, count of bidi controls within run */ + byte level; + + /* + * Default constructor + * + * Note that members start and limit of a run instance have different + * meanings depending whether the run is part of the runs array of a Bidi + * object, or if it is a reference returned by getVisualRun() or + * getLogicalRun(). + * For a member of the runs array of a Bidi object, + * - start is the first logical position of the run in the source text. + * - limit is one after the last visual position of the run. + * For a reference returned by getLogicalRun() or getVisualRun(), + * - start is the first logical position of the run in the source text. + * - limit is one after the last logical position of the run. + */ + BidiRun() + { + this(0, 0, (byte)0); + } + + /* + * Constructor + */ + BidiRun(int start, int limit, byte embeddingLevel) + { + this.start = start; + this.limit = limit; + this.level = embeddingLevel; + } + + /* + * Copy the content of a BidiRun instance + */ + void copyFrom(BidiRun run) + { + this.start = run.start; + this.limit = run.limit; + this.level = run.level; + this.insertRemove = run.insertRemove; + } + + /** + * Get level of run + */ + byte getEmbeddingLevel() + { + return level; + } + + /** + * Check if run level is even + * @return true if the embedding level of this run is even, i.e. it is a + * left-to-right run. + */ + boolean isEvenRun() + { + return (level & 1) == 0; + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiWriter.class b/tests/test_data/std/jdk/internal/icu/text/BidiWriter.class new file mode 100644 index 00000000..8fb05d7b Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/BidiWriter.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/BidiWriter.java b/tests/test_data/std/jdk/internal/icu/text/BidiWriter.java new file mode 100644 index 00000000..3b4c5dd4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/BidiWriter.java @@ -0,0 +1,451 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +******************************************************************************* +* Copyright (C) 2001-2010, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +*/ +/* Written by Simon Montagu, Matitiahu Allouche + * (ported from C code written by Markus W. Scherer) + */ + +package jdk.internal.icu.text; + +import jdk.internal.icu.lang.UCharacter; + +final class BidiWriter { + + /** Bidi control code points */ + static final char LRM_CHAR = 0x200e; + static final char RLM_CHAR = 0x200f; + static final int MASK_R_AL = (1 << UCharacter.RIGHT_TO_LEFT | + 1 << UCharacter.RIGHT_TO_LEFT_ARABIC); + + private static boolean IsCombining(int type) { + return ((1< 0); + break; + + case BidiBase.KEEP_BASE_COMBINING: + /* + * Here, too, the destination + * run will have the same length as the source run, + * and there is no mirroring. + * We do need to keep combining characters with their base + * characters. + */ + srcLength = src.length(); + + /* preserve character integrity */ + do { + /* i is always after the last code unit known to need to be kept + * in this segment */ + int c; + int i = srcLength; + + /* collect code units and modifier letters for one base + * character */ + do { + c = UTF16.charAt(src, srcLength - 1); + srcLength -= UTF16.getCharCount(c); + } while(srcLength > 0 && IsCombining(UCharacter.getType(c))); + + /* copy this "user character" */ + dest.append(src.substring(srcLength, i)); + } while(srcLength > 0); + break; + + default: + /* + * With several "complicated" options set, this is the most + * general and the slowest copying of an RTL run. + * We will do mirroring, remove Bidi controls, and + * keep combining characters with their base characters + * as requested. + */ + srcLength = src.length(); + + /* preserve character integrity */ + do { + /* i is always after the last code unit known to need to be kept + * in this segment */ + int i = srcLength; + + /* collect code units for one base character */ + int c = UTF16.charAt(src, srcLength - 1); + srcLength -= UTF16.getCharCount(c); + if ((options & BidiBase.KEEP_BASE_COMBINING) != 0) { + /* collect modifier letters for this base character */ + while(srcLength > 0 && IsCombining(UCharacter.getType(c))) { + c = UTF16.charAt(src, srcLength - 1); + srcLength -= UTF16.getCharCount(c); + } + } + + if ((options & BidiBase.REMOVE_BIDI_CONTROLS) != 0 && + BidiBase.IsBidiControlChar(c)) { + /* do not copy this Bidi control character */ + continue; + } + + /* copy this "user character" */ + int j = srcLength; + if((options & BidiBase.DO_MIRRORING) != 0) { + /* mirror only the base character */ + c = UCharacter.getMirror(c); + UTF16.append(dest, c); + j += UTF16.getCharCount(c); + } + dest.append(src.substring(j, i)); + } while(srcLength > 0); + break; + } /* end of switch */ + + return dest.toString(); + } + + static String doWriteReverse(char[] text, int start, int limit, int options) { + return writeReverse(new String(text, start, limit - start), options); + } + + static String writeReordered(BidiBase bidi, int options) { + int run, runCount; + StringBuilder dest; + char[] text = bidi.text; + runCount = bidi.countRuns(); + + /* + * Option "insert marks" implies BidiBase.INSERT_LRM_FOR_NUMERIC if the + * reordering mode (checked below) is appropriate. + */ + if ((bidi.reorderingOptions & BidiBase.OPTION_INSERT_MARKS) != 0) { + options |= BidiBase.INSERT_LRM_FOR_NUMERIC; + options &= ~BidiBase.REMOVE_BIDI_CONTROLS; + } + /* + * Option "remove controls" implies BidiBase.REMOVE_BIDI_CONTROLS + * and cancels BidiBase.INSERT_LRM_FOR_NUMERIC. + */ + if ((bidi.reorderingOptions & BidiBase.OPTION_REMOVE_CONTROLS) != 0) { + options |= BidiBase.REMOVE_BIDI_CONTROLS; + options &= ~BidiBase.INSERT_LRM_FOR_NUMERIC; + } + /* + * If we do not perform the "inverse Bidi" algorithm, then we + * don't need to insert any LRMs, and don't need to test for it. + */ + if ((bidi.reorderingMode != BidiBase.REORDER_INVERSE_NUMBERS_AS_L) && + (bidi.reorderingMode != BidiBase.REORDER_INVERSE_LIKE_DIRECT) && + (bidi.reorderingMode != BidiBase.REORDER_INVERSE_FOR_NUMBERS_SPECIAL) && + (bidi.reorderingMode != BidiBase.REORDER_RUNS_ONLY)) { + options &= ~BidiBase.INSERT_LRM_FOR_NUMERIC; + } + dest = new StringBuilder((options & BidiBase.INSERT_LRM_FOR_NUMERIC) != 0 ? + bidi.length * 2 : bidi.length); + /* + * Iterate through all visual runs and copy the run text segments to + * the destination, according to the options. + * + * The tests for where to insert LRMs ignore the fact that there may be + * BN codes or non-BMP code points at the beginning and end of a run; + * they may insert LRMs unnecessarily but the tests are faster this way + * (this would have to be improved for UTF-8). + */ + if ((options & BidiBase.OUTPUT_REVERSE) == 0) { + /* forward output */ + if ((options & BidiBase.INSERT_LRM_FOR_NUMERIC) == 0) { + /* do not insert Bidi controls */ + for (run = 0; run < runCount; ++run) { + BidiRun bidiRun = bidi.getVisualRun(run); + if (bidiRun.isEvenRun()) { + dest.append(doWriteForward(text, bidiRun.start, + bidiRun.limit, + options & ~BidiBase.DO_MIRRORING)); + } else { + dest.append(doWriteReverse(text, bidiRun.start, + bidiRun.limit, options)); + } + } + } else { + /* insert Bidi controls for "inverse Bidi" */ + byte[] dirProps = bidi.dirProps; + char uc; + int markFlag; + + for (run = 0; run < runCount; ++run) { + BidiRun bidiRun = bidi.getVisualRun(run); + markFlag=0; + /* check if something relevant in insertPoints */ + markFlag = bidi.runs[run].insertRemove; + if (markFlag < 0) { /* bidi controls count */ + markFlag = 0; + } + if (bidiRun.isEvenRun()) { + if (bidi.isInverse() && + dirProps[bidiRun.start] != BidiBase.L) { + markFlag |= BidiBase.LRM_BEFORE; + } + if ((markFlag & BidiBase.LRM_BEFORE) != 0) { + uc = LRM_CHAR; + } else if ((markFlag & BidiBase.RLM_BEFORE) != 0) { + uc = RLM_CHAR; + } else { + uc = 0; + } + if (uc != 0) { + dest.append(uc); + } + dest.append(doWriteForward(text, + bidiRun.start, bidiRun.limit, + options & ~BidiBase.DO_MIRRORING)); + + if (bidi.isInverse() && + dirProps[bidiRun.limit - 1] != BidiBase.L) { + markFlag |= BidiBase.LRM_AFTER; + } + if ((markFlag & BidiBase.LRM_AFTER) != 0) { + uc = LRM_CHAR; + } else if ((markFlag & BidiBase.RLM_AFTER) != 0) { + uc = RLM_CHAR; + } else { + uc = 0; + } + if (uc != 0) { + dest.append(uc); + } + } else { /* RTL run */ + if (bidi.isInverse() && + !bidi.testDirPropFlagAt(MASK_R_AL, + bidiRun.limit - 1)) { + markFlag |= BidiBase.RLM_BEFORE; + } + if ((markFlag & BidiBase.LRM_BEFORE) != 0) { + uc = LRM_CHAR; + } else if ((markFlag & BidiBase.RLM_BEFORE) != 0) { + uc = RLM_CHAR; + } else { + uc = 0; + } + if (uc != 0) { + dest.append(uc); + } + dest.append(doWriteReverse(text, bidiRun.start, + bidiRun.limit, options)); + + if(bidi.isInverse() && + (MASK_R_AL & BidiBase.DirPropFlag(dirProps[bidiRun.start])) == 0) { + markFlag |= BidiBase.RLM_AFTER; + } + if ((markFlag & BidiBase.LRM_AFTER) != 0) { + uc = LRM_CHAR; + } else if ((markFlag & BidiBase.RLM_AFTER) != 0) { + uc = RLM_CHAR; + } else { + uc = 0; + } + if (uc != 0) { + dest.append(uc); + } + } + } + } + } else { + /* reverse output */ + if((options & BidiBase.INSERT_LRM_FOR_NUMERIC) == 0) { + /* do not insert Bidi controls */ + for(run = runCount; --run >= 0; ) { + BidiRun bidiRun = bidi.getVisualRun(run); + if (bidiRun.isEvenRun()) { + dest.append(doWriteReverse(text, + bidiRun.start, bidiRun.limit, + options & ~BidiBase.DO_MIRRORING)); + } else { + dest.append(doWriteForward(text, bidiRun.start, + bidiRun.limit, options)); + } + } + } else { + /* insert Bidi controls for "inverse Bidi" */ + + byte[] dirProps = bidi.dirProps; + + for (run = runCount; --run >= 0; ) { + /* reverse output */ + BidiRun bidiRun = bidi.getVisualRun(run); + if (bidiRun.isEvenRun()) { + if (dirProps[bidiRun.limit - 1] != BidiBase.L) { + dest.append(LRM_CHAR); + } + + dest.append(doWriteReverse(text, bidiRun.start, + bidiRun.limit, options & ~BidiBase.DO_MIRRORING)); + + if (dirProps[bidiRun.start] != BidiBase.L) { + dest.append(LRM_CHAR); + } + } else { + if ((MASK_R_AL & BidiBase.DirPropFlag(dirProps[bidiRun.start])) == 0) { + dest.append(RLM_CHAR); + } + + dest.append(doWriteForward(text, bidiRun.start, + bidiRun.limit, options)); + + if ((MASK_R_AL & BidiBase.DirPropFlag(dirProps[bidiRun.limit - 1])) == 0) { + dest.append(RLM_CHAR); + } + } + } + } + } + + return dest.toString(); + } +} diff --git a/tests/test_data/std/jdk/internal/icu/text/FilteredNormalizer2.class b/tests/test_data/std/jdk/internal/icu/text/FilteredNormalizer2.class new file mode 100644 index 00000000..417369b8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/FilteredNormalizer2.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/FilteredNormalizer2.java b/tests/test_data/std/jdk/internal/icu/text/FilteredNormalizer2.java new file mode 100644 index 00000000..d17eab8c --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/FilteredNormalizer2.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* +******************************************************************************* +* Copyright (C) 2009-2014, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +*/ +package jdk.internal.icu.text; + +import java.io.IOException; + +/** + * Normalization filtered by a UnicodeSet. + * Normalizes portions of the text contained in the filter set and leaves + * portions not contained in the filter set unchanged. + * Filtering is done via UnicodeSet.span(..., UnicodeSet.SpanCondition.SIMPLE). + * Not-in-the-filter text is treated as "is normalized" and "quick check yes". + * This class implements all of (and only) the Normalizer2 API. + * An instance of this class is unmodifiable/immutable. + * @stable ICU 4.4 + * @author Markus W. Scherer + */ +class FilteredNormalizer2 extends Normalizer2 { + + /** + * Constructs a filtered normalizer wrapping any Normalizer2 instance + * and a filter set. + * Both are aliased and must not be modified or deleted while this object + * is used. + * The filter set should be frozen; otherwise the performance will suffer greatly. + * @param n2 wrapped Normalizer2 instance + * @param filterSet UnicodeSet which determines the characters to be normalized + * @stable ICU 4.4 + */ + public FilteredNormalizer2(Normalizer2 n2, UnicodeSet filterSet) { + norm2=n2; + set=filterSet; + } + + /** + * {@inheritDoc} + * @stable ICU 4.4 + */ + @Override + public StringBuilder normalize(CharSequence src, StringBuilder dest) { + if(dest==src) { + throw new IllegalArgumentException(); + } + dest.setLength(0); + normalize(src, dest, UnicodeSet.SpanCondition.SIMPLE); + return dest; + } + + /** + * {@inheritDoc} + * @stable ICU 4.6 + */ + @Override + public Appendable normalize(CharSequence src, Appendable dest) { + if(dest==src) { + throw new IllegalArgumentException(); + } + return normalize(src, dest, UnicodeSet.SpanCondition.SIMPLE); + } + + /** + * {@inheritDoc} + * @stable ICU 4.4 + */ + @Override + public StringBuilder normalizeSecondAndAppend( + StringBuilder first, CharSequence second) { + return normalizeSecondAndAppend(first, second, true); + } + + /** + * {@inheritDoc} + * @stable ICU 4.4 + */ + @Override + public StringBuilder append(StringBuilder first, CharSequence second) { + return normalizeSecondAndAppend(first, second, false); + } + + /** + * {@inheritDoc} + * @stable ICU 4.6 + */ + @Override + public String getDecomposition(int c) { + return set.contains(c) ? norm2.getDecomposition(c) : null; + } + + /** + * {@inheritDoc} + * @stable ICU 49 + */ + @Override + public int getCombiningClass(int c) { + return set.contains(c) ? norm2.getCombiningClass(c) : 0; + } + + /** + * {@inheritDoc} + * @stable ICU 4.4 + */ + @Override + public boolean isNormalized(CharSequence s) { + UnicodeSet.SpanCondition spanCondition=UnicodeSet.SpanCondition.SIMPLE; + for(int prevSpanLimit=0; prevSpanLimit + * The primary functions are to produce a normalized string and to detect whether + * a string is already normalized. + * The most commonly used normalization forms are those defined in + * Unicode Standard Annex #15: + * Unicode Normalization Forms. + * However, this API supports additional normalization forms for specialized purposes. + * For example, NFKC_Casefold is provided via getInstance("nfkc_cf", COMPOSE) + * and can be used in implementations of UTS #46. + *

+ * Not only are the standard compose and decompose modes supplied, + * but additional modes are provided as documented in the Mode enum. + *

+ * Some of the functions in this class identify normalization boundaries. + * At a normalization boundary, the portions of the string + * before it and starting from it do not interact and can be handled independently. + *

+ * The spanQuickCheckYes() stops at a normalization boundary. + * When the goal is a normalized string, then the text before the boundary + * can be copied, and the remainder can be processed with normalizeSecondAndAppend(). + *

+ * The hasBoundaryBefore(), hasBoundaryAfter() and isInert() functions test whether + * a character is guaranteed to be at a normalization boundary, + * regardless of context. + * This is used for moving from one normalization boundary to the next + * or preceding boundary, and for performing iterative normalization. + *

+ * Iterative normalization is useful when only a small portion of a + * longer string needs to be processed. + * For example, in ICU, iterative normalization is used by the NormalizationTransliterator + * (to avoid replacing already-normalized text) and ucol_nextSortKeyPart() + * (to process only the substring for which sort key bytes are computed). + *

+ * The set of normalization boundaries returned by these functions may not be + * complete: There may be more boundaries that could be returned. + * Different functions may return different boundaries. + * @stable ICU 4.4 + * @author Markus W. Scherer + */ +public abstract class Normalizer2 { + + /** + * Returns a Normalizer2 instance for Unicode NFC normalization. + * Same as getInstance(null, "nfc", Mode.COMPOSE). + * Returns an unmodifiable singleton instance. + * @return the requested Normalizer2, if successful + * @stable ICU 49 + */ + public static Normalizer2 getNFCInstance() { + return Norm2AllModes.getNFCInstance().comp; + } + + /** + * Returns a Normalizer2 instance for Unicode NFD normalization. + * Same as getInstance(null, "nfc", Mode.DECOMPOSE). + * Returns an unmodifiable singleton instance. + * @return the requested Normalizer2, if successful + * @stable ICU 49 + */ + public static Normalizer2 getNFDInstance() { + return Norm2AllModes.getNFCInstance().decomp; + } + + /** + * Returns a Normalizer2 instance for Unicode NFKC normalization. + * Same as getInstance(null, "nfkc", Mode.COMPOSE). + * Returns an unmodifiable singleton instance. + * @return the requested Normalizer2, if successful + * @stable ICU 49 + */ + public static Normalizer2 getNFKCInstance() { + return Norm2AllModes.getNFKCInstance().comp; + } + + /** + * Returns a Normalizer2 instance for Unicode NFKD normalization. + * Same as getInstance(null, "nfkc", Mode.DECOMPOSE). + * Returns an unmodifiable singleton instance. + * @return the requested Normalizer2, if successful + * @stable ICU 49 + */ + public static Normalizer2 getNFKDInstance() { + return Norm2AllModes.getNFKCInstance().decomp; + } + + /** + * Returns the normalized form of the source string. + * @param src source string + * @return normalized src + * @stable ICU 4.4 + */ + public String normalize(CharSequence src) { + if(src instanceof String) { + // Fastpath: Do not construct a new String if the src is a String + // and is already normalized. + int spanLength=spanQuickCheckYes(src); + if(spanLength==src.length()) { + return (String)src; + } + if (spanLength != 0) { + StringBuilder sb=new StringBuilder(src.length()).append(src, 0, spanLength); + return normalizeSecondAndAppend(sb, src.subSequence(spanLength, src.length())).toString(); + } + } + return normalize(src, new StringBuilder(src.length())).toString(); + } + + /** + * Writes the normalized form of the source string to the destination string + * (replacing its contents) and returns the destination string. + * The source and destination strings must be different objects. + * @param src source string + * @param dest destination string; its contents is replaced with normalized src + * @return dest + * @stable ICU 4.4 + */ + public abstract StringBuilder normalize(CharSequence src, StringBuilder dest); + + /** + * Writes the normalized form of the source string to the destination Appendable + * and returns the destination Appendable. + * The source and destination strings must be different objects. + * + *

Any {@link java.io.IOException} is wrapped into a {@link com.ibm.icu.util.ICUUncheckedIOException}. + * + * @param src source string + * @param dest destination Appendable; gets normalized src appended + * @return dest + * @stable ICU 4.6 + */ + public abstract Appendable normalize(CharSequence src, Appendable dest); + + /** + * Appends the normalized form of the second string to the first string + * (merging them at the boundary) and returns the first string. + * The result is normalized if the first string was normalized. + * The first and second strings must be different objects. + * @param first string, should be normalized + * @param second string, will be normalized + * @return first + * @stable ICU 4.4 + */ + public abstract StringBuilder normalizeSecondAndAppend( + StringBuilder first, CharSequence second); + + /** + * Appends the second string to the first string + * (merging them at the boundary) and returns the first string. + * The result is normalized if both the strings were normalized. + * The first and second strings must be different objects. + * @param first string, should be normalized + * @param second string, should be normalized + * @return first + * @stable ICU 4.4 + */ + public abstract StringBuilder append(StringBuilder first, CharSequence second); + + /** + * Gets the decomposition mapping of c. + * Roughly equivalent to normalizing the String form of c + * on a DECOMPOSE Normalizer2 instance, but much faster, and except that this function + * returns null if c does not have a decomposition mapping in this instance's data. + * This function is independent of the mode of the Normalizer2. + * @param c code point + * @return c's decomposition mapping, if any; otherwise null + * @stable ICU 4.6 + */ + public abstract String getDecomposition(int c); + + /** + * Gets the combining class of c. + * The default implementation returns 0 + * but all standard implementations return the Unicode Canonical_Combining_Class value. + * @param c code point + * @return c's combining class + * @stable ICU 49 + */ + public int getCombiningClass(int c) { return 0; } + + /** + * Tests if the string is normalized. + * Internally, in cases where the quickCheck() method would return "maybe" + * (which is only possible for the two COMPOSE modes) this method + * resolves to "yes" or "no" to provide a definitive result, + * at the cost of doing more work in those cases. + * @param s input string + * @return true if s is normalized + * @stable ICU 4.4 + */ + public abstract boolean isNormalized(CharSequence s); + + /** + * Returns the end of the normalized substring of the input string. + * In other words, with end=spanQuickCheckYes(s); + * the substring s.subSequence(0, end) + * will pass the quick check with a "yes" result. + *

+ * The returned end index is usually one or more characters before the + * "no" or "maybe" character: The end index is at a normalization boundary. + * (See the class documentation for more about normalization boundaries.) + *

+ * When the goal is a normalized string and most input strings are expected + * to be normalized already, then call this method, + * and if it returns a prefix shorter than the input string, + * copy that prefix and use normalizeSecondAndAppend() for the remainder. + * @param s input string + * @return "yes" span end index + * @stable ICU 4.4 + */ + public abstract int spanQuickCheckYes(CharSequence s); + + /** + * Tests if the character always has a normalization boundary before it, + * regardless of context. + * If true, then the character does not normalization-interact with + * preceding characters. + * In other words, a string containing this character can be normalized + * by processing portions before this character and starting from this + * character independently. + * This is used for iterative normalization. See the class documentation for details. + * @param c character to test + * @return true if c has a normalization boundary before it + * @stable ICU 4.4 + */ + public abstract boolean hasBoundaryBefore(int c); + + /** + * Sole constructor. (For invocation by subclass constructors, + * typically implicit.) + * @internal + * deprecated This API is ICU internal only. + */ + protected Normalizer2() { + } +} diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$1.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$1.class new file mode 100644 index 00000000..0f54c051 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$1.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$Mode.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$Mode.class new file mode 100644 index 00000000..8f14d956 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$Mode.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$ModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$ModeImpl.class new file mode 100644 index 00000000..d3269578 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$ModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFC32ModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFC32ModeImpl.class new file mode 100644 index 00000000..254c9a76 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFC32ModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFCMode.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFCMode.class new file mode 100644 index 00000000..6a857685 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFCMode.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFCModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFCModeImpl.class new file mode 100644 index 00000000..f47f68d5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFCModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFD32ModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFD32ModeImpl.class new file mode 100644 index 00000000..76e93f45 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFD32ModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFDMode.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFDMode.class new file mode 100644 index 00000000..f7041e71 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFDMode.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFDModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFDModeImpl.class new file mode 100644 index 00000000..3c13726b Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFDModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKC32ModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKC32ModeImpl.class new file mode 100644 index 00000000..3fa6019c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKC32ModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKCMode.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKCMode.class new file mode 100644 index 00000000..c3341b4e Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKCMode.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKCModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKCModeImpl.class new file mode 100644 index 00000000..1a990386 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKCModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKD32ModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKD32ModeImpl.class new file mode 100644 index 00000000..3cc0d4fe Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKD32ModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKDMode.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKDMode.class new file mode 100644 index 00000000..b7173616 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKDMode.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKDModeImpl.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKDModeImpl.class new file mode 100644 index 00000000..55222f8c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NFKDModeImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NONEMode.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NONEMode.class new file mode 100644 index 00000000..eca85280 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$NONEMode.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$Unicode32.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$Unicode32.class new file mode 100644 index 00000000..f0d6d0ba Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase$Unicode32.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase.class b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase.class new file mode 100644 index 00000000..a16a4fda Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/NormalizerBase.java b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase.java new file mode 100644 index 00000000..f2566d9d --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/NormalizerBase.java @@ -0,0 +1,784 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 2000-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ +package jdk.internal.icu.text; + +import jdk.internal.icu.impl.Norm2AllModes; + +import java.text.CharacterIterator; +import java.text.Normalizer; + +/** + * Unicode Normalization + * + *

Unicode normalization API

+ * + * normalize transforms Unicode text into an equivalent composed or + * decomposed form, allowing for easier sorting and searching of text. + * normalize supports the standard normalization forms described in + * + * Unicode Standard Annex #15 — Unicode Normalization Forms. + * + * Characters with accents or other adornments can be encoded in + * several different ways in Unicode. For example, take the character A-acute. + * In Unicode, this can be encoded as a single character (the + * "composed" form): + * + *
+ *      00C1    LATIN CAPITAL LETTER A WITH ACUTE
+ * 
+ * + * or as two separate characters (the "decomposed" form): + * + *
+ *      0041    LATIN CAPITAL LETTER A
+ *      0301    COMBINING ACUTE ACCENT
+ * 
+ * + * To a user of your program, however, both of these sequences should be + * treated as the same "user-level" character "A with acute accent". When you + * are searching or comparing text, you must ensure that these two sequences are + * treated equivalently. In addition, you must handle characters with more than + * one accent. Sometimes the order of a character's combining accents is + * significant, while in other cases accent sequences in different orders are + * really equivalent. + * + * Similarly, the string "ffi" can be encoded as three separate letters: + * + *
+ *      0066    LATIN SMALL LETTER F
+ *      0066    LATIN SMALL LETTER F
+ *      0069    LATIN SMALL LETTER I
+ * 
+ * + * or as the single character + * + *
+ *      FB03    LATIN SMALL LIGATURE FFI
+ * 
+ * + * The ffi ligature is not a distinct semantic character, and strictly speaking + * it shouldn't be in Unicode at all, but it was included for compatibility + * with existing character sets that already provided it. The Unicode standard + * identifies such characters by giving them "compatibility" decompositions + * into the corresponding semantic characters. When sorting and searching, you + * will often want to use these mappings. + * + * normalize helps solve these problems by transforming text into + * the canonical composed and decomposed forms as shown in the first example + * above. In addition, you can have it perform compatibility decompositions so + * that you can treat compatibility characters the same as their equivalents. + * Finally, normalize rearranges accents into the proper canonical + * order, so that you do not have to worry about accent rearrangement on your + * own. + * + * Form FCD, "Fast C or D", is also designed for collation. + * It allows to work on strings that are not necessarily normalized + * with an algorithm (like in collation) that works under "canonical closure", + * i.e., it treats precomposed characters and their decomposed equivalents the + * same. + * + * It is not a normalization form because it does not provide for uniqueness of + * representation. Multiple strings may be canonically equivalent (their NFDs + * are identical) and may all conform to FCD without being identical themselves. + * + * The form is defined such that the "raw decomposition", the recursive + * canonical decomposition of each character, results in a string that is + * canonically ordered. This means that precomposed characters are allowed for + * as long as their decompositions do not need canonical reordering. + * + * Its advantage for a process like collation is that all NFD and most NFC texts + * - and many unnormalized texts - already conform to FCD and do not need to be + * normalized (NFD) for such a process. The FCD quick check will return YES for + * most strings in practice. + * + * normalize(FCD) may be implemented with NFD. + * + * For more details on FCD see Unicode Technical Note #5 (Canonical Equivalence in Applications): + * http://www.unicode.org/notes/tn5/#FCD + * + * ICU collation performs either NFD or FCD normalization automatically if + * normalization is turned on for the collator object. Beyond collation and + * string search, normalized strings may be useful for string equivalence + * comparisons, transliteration/transcription, unique representations, etc. + * + * The W3C generally recommends to exchange texts in NFC. + * Note also that most legacy character encodings use only precomposed forms and + * often do not encode any combining marks by themselves. For conversion to such + * character encodings the Unicode text needs to be normalized to NFC. + * For more usage examples, see the Unicode Standard Annex. + * + * Note: The Normalizer class also provides API for iterative normalization. + * While the setIndex() and getIndex() refer to indices in the + * underlying Unicode input text, the next() and previous() methods + * iterate through characters in the normalized output. + * This means that there is not necessarily a one-to-one correspondence + * between characters returned by next() and previous() and the indices + * passed to and returned from setIndex() and getIndex(). + * It is for this reason that Normalizer does not implement the CharacterIterator interface. + * + * @stable ICU 2.8 + */ +// Original filename in ICU4J: Normalizer.java +public final class NormalizerBase implements Cloneable { + + // The input text and our position in it + private UCharacterIterator text; + private Normalizer2 norm2; + private Mode mode; + private int options; + + // The normalization buffer is the result of normalization + // of the source in [currentIndex..nextIndex] . + private int currentIndex; + private int nextIndex; + + // A buffer for holding intermediate results + private StringBuilder buffer; + private int bufferPos; + + // Helper classes to defer loading of normalization data. + private static final class ModeImpl { + private ModeImpl(Normalizer2 n2) { + normalizer2 = n2; + } + private final Normalizer2 normalizer2; + } + + private static final class NFDModeImpl { + private static final ModeImpl INSTANCE = new ModeImpl(Normalizer2.getNFDInstance()); + } + + private static final class NFKDModeImpl { + private static final ModeImpl INSTANCE = new ModeImpl(Normalizer2.getNFKDInstance()); + } + + private static final class NFCModeImpl { + private static final ModeImpl INSTANCE = new ModeImpl(Normalizer2.getNFCInstance()); + } + + private static final class NFKCModeImpl { + private static final ModeImpl INSTANCE = new ModeImpl(Normalizer2.getNFKCInstance()); + } + + private static final class Unicode32 { + private static final UnicodeSet INSTANCE = new UnicodeSet("[:age=3.2:]").freeze(); + } + + private static final class NFD32ModeImpl { + private static final ModeImpl INSTANCE = + new ModeImpl(new FilteredNormalizer2(Normalizer2.getNFDInstance(), + Unicode32.INSTANCE)); + } + + private static final class NFKD32ModeImpl { + private static final ModeImpl INSTANCE = + new ModeImpl(new FilteredNormalizer2(Normalizer2.getNFKDInstance(), + Unicode32.INSTANCE)); + } + + private static final class NFC32ModeImpl { + private static final ModeImpl INSTANCE = + new ModeImpl(new FilteredNormalizer2(Normalizer2.getNFCInstance(), + Unicode32.INSTANCE)); + } + + private static final class NFKC32ModeImpl { + private static final ModeImpl INSTANCE = + new ModeImpl(new FilteredNormalizer2(Normalizer2.getNFKCInstance(), + Unicode32.INSTANCE)); + } + + /** + * Options bit set value to select Unicode 3.2 normalization + * (except NormalizationCorrections). + * At most one Unicode version can be selected at a time. + * @stable ICU 2.6 + */ + public static final int UNICODE_3_2=0x20; + + public static final int UNICODE_3_2_0_ORIGINAL=UNICODE_3_2; + + /* + * Default option for the latest Unicode normalization. This option is + * provided mainly for testing. + * The value zero means that normalization is done with the fixes for + * - Corrigendum 4 (Five CJK Canonical Mapping Errors) + * - Corrigendum 5 (Normalization Idempotency) + */ + public static final int UNICODE_LATEST = 0x00; + + /** + * Constant indicating that the end of the iteration has been reached. + * This is guaranteed to have the same value as {@link UCharacterIterator#DONE}. + * @stable ICU 2.8 + */ + public static final int DONE = UCharacterIterator.DONE; + + /** + * Constants for normalization modes. + *

+ * The Mode class is not intended for public subclassing. + * Only the Mode constants provided by the Normalizer class should be used, + * and any fields or methods should not be called or overridden by users. + * @stable ICU 2.8 + */ + public abstract static class Mode { + + /** + * Sole constructor + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected Mode() { + } + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected abstract Normalizer2 getNormalizer2(int options); + } + + private static Mode toMode(Normalizer.Form form) { + switch (form) { + case NFC : + return NFC; + case NFD : + return NFD; + case NFKC : + return NFKC; + case NFKD : + return NFKD; + } + + throw new IllegalArgumentException("Unexpected normalization form: " + + form); + } + + private static final class NONEMode extends Mode { + protected Normalizer2 getNormalizer2(int options) { return Norm2AllModes.NOOP_NORMALIZER2; } + } + + private static final class NFDMode extends Mode { + protected Normalizer2 getNormalizer2(int options) { + return (options&UNICODE_3_2) != 0 ? + NFD32ModeImpl.INSTANCE.normalizer2 : + NFDModeImpl.INSTANCE.normalizer2; + } + } + + private static final class NFKDMode extends Mode { + protected Normalizer2 getNormalizer2(int options) { + return (options&UNICODE_3_2) != 0 ? + NFKD32ModeImpl.INSTANCE.normalizer2 : + NFKDModeImpl.INSTANCE.normalizer2; + } + } + + private static final class NFCMode extends Mode { + protected Normalizer2 getNormalizer2(int options) { + return (options&UNICODE_3_2) != 0 ? + NFC32ModeImpl.INSTANCE.normalizer2 : + NFCModeImpl.INSTANCE.normalizer2; + } + } + + private static final class NFKCMode extends Mode { + protected Normalizer2 getNormalizer2(int options) { + return (options&UNICODE_3_2) != 0 ? + NFKC32ModeImpl.INSTANCE.normalizer2 : + NFKCModeImpl.INSTANCE.normalizer2; + } + } + + /** + * No decomposition/composition. + * @stable ICU 2.8 + */ + public static final Mode NONE = new NONEMode(); + + /** + * Canonical decomposition. + * @stable ICU 2.8 + */ + public static final Mode NFD = new NFDMode(); + + /** + * Compatibility decomposition. + * @stable ICU 2.8 + */ + public static final Mode NFKD = new NFKDMode(); + + /** + * Canonical decomposition followed by canonical composition. + * @stable ICU 2.8 + */ + public static final Mode NFC = new NFCMode(); + + public static final Mode NFKC =new NFKCMode(); + + //------------------------------------------------------------------------- + // Iterator constructors + //------------------------------------------------------------------------- + + /** + * Creates a new {@code NormalizerBase} object for iterating over the + * normalized form of a given string. + *

+ * The {@code options} parameter specifies which optional + * {@code NormalizerBase} features are to be enabled for this object. + *

+ * @param str The string to be normalized. The normalization + * will start at the beginning of the string. + * + * @param mode The normalization mode. + * + * @param opt Any optional features to be enabled. + * Currently the only available option is {@link #UNICODE_3_2}. + * If you want the default behavior corresponding to one of the + * standard Unicode Normalization Forms, use 0 for this argument. + * @stable ICU 2.6 + */ + public NormalizerBase(String str, Mode mode, int opt) { + this.text = UCharacterIterator.getInstance(str); + this.mode = mode; + this.options=opt; + norm2 = mode.getNormalizer2(opt); + buffer = new StringBuilder(); + } + + public NormalizerBase(String str, Mode mode) { + this(str, mode, 0); + } + + + /** + * Creates a new {@code NormalizerBase} object for iterating over the + * normalized form of the given text. + *

+ * @param iter The input text to be normalized. The normalization + * will start at the beginning of the string. + * + * @param mode The normalization mode. + * + * @param opt Any optional features to be enabled. + * Currently the only available option is {@link #UNICODE_3_2}. + * If you want the default behavior corresponding to one of the + * standard Unicode Normalization Forms, use 0 for this argument. + * @stable ICU 2.6 + */ + public NormalizerBase(CharacterIterator iter, Mode mode, int opt) { + this.text = UCharacterIterator.getInstance((CharacterIterator)iter.clone()); + this.mode = mode; + this.options = opt; + norm2 = mode.getNormalizer2(opt); + buffer = new StringBuilder(); + } + + public NormalizerBase(CharacterIterator iter, Mode mode) { + this(iter, mode, 0); + } + + /** + * Clones this {@code NormalizerBase} object. All properties of this + * object are duplicated in the new object, including the cloning of any + * {@link CharacterIterator} that was passed in to the constructor + * or to {@link #setText(CharacterIterator) setText}. + * However, the text storage underlying + * the {@code CharacterIterator} is not duplicated unless the + * iterator's {@code clone} method does so. + * @stable ICU 2.8 + */ + public Object clone() { + try { + NormalizerBase copy = (NormalizerBase) super.clone(); + copy.text = (UCharacterIterator) text.clone(); + copy.mode = mode; + copy.options = options; + copy.norm2 = norm2; + copy.buffer = new StringBuilder(buffer); + copy.bufferPos = bufferPos; + copy.currentIndex = currentIndex; + copy.nextIndex = nextIndex; + return copy; + } + catch (CloneNotSupportedException e) { + throw new InternalError(e.toString(), e); + } + } + + /** + * Normalizes a {@code String} using the given normalization operation. + *

+ * The {@code options} parameter specifies which optional + * {@code NormalizerBase} features are to be enabled for this operation. + * Currently the only available option is {@link #UNICODE_3_2}. + * If you want the default behavior corresponding to one of the standard + * Unicode Normalization Forms, use 0 for this argument. + *

+ * @param str the input string to be normalized. + * @param mode the normalization mode + * @param options the optional features to be enabled. + * @return String the normalized string + * @stable ICU 2.6 + */ + public static String normalize(String str, Mode mode, int options) { + return mode.getNormalizer2(options).normalize(str); + } + + public static String normalize(String str, Normalizer.Form form) { + return NormalizerBase.normalize(str, toMode(form), UNICODE_LATEST); + } + + public static String normalize(String str, Normalizer.Form form, int options) { + return NormalizerBase.normalize(str, toMode(form), options); + } + + /** + * Test if a string is in a given normalization form. + * This is semantically equivalent to source.equals(normalize(source, mode)). + * + * Unlike quickCheck(), this function returns a definitive result, + * never a "maybe". + * For NFD, NFKD, and FCD, both functions work exactly the same. + * For NFC and NFKC where quickCheck may return "maybe", this function will + * perform further tests to arrive at a true/false result. + * @param str the input string to be checked to see if it is + * normalized + * @param mode the normalization mode + * @param options Options for use with exclusion set and tailored Normalization + * The only option that is currently recognized is UNICODE_3_2 + * @see #isNormalized + * @stable ICU 2.6 + */ + public static boolean isNormalized(String str, Mode mode, int options) { + return mode.getNormalizer2(options).isNormalized(str); + } + + public static boolean isNormalized(String str, Normalizer.Form form) { + return NormalizerBase.isNormalized(str, toMode(form), UNICODE_LATEST); + } + + public static boolean isNormalized(String str, Normalizer.Form form, int options) { + return NormalizerBase.isNormalized(str, toMode(form), options); + } + + //------------------------------------------------------------------------- + // Iteration API + //------------------------------------------------------------------------- + + /** + * Return the current character in the normalized text. + * @return The codepoint as an int + * @stable ICU 2.8 + */ + public int current() { + if(bufferPos0 || previousNormalize()) { + int c=buffer.codePointBefore(bufferPos); + bufferPos-=Character.charCount(c); + return c; + } else { + return DONE; + } + } + + /** + * Reset the index to the beginning of the text. + * This is equivalent to setIndexOnly(startIndex)). + * @stable ICU 2.8 + */ + public void reset() { + text.setIndex(0); + currentIndex=nextIndex=0; + clearBuffer(); + } + + /** + * Set the iteration position in the input text that is being normalized, + * without any immediate normalization. + * After setIndexOnly(), getIndex() will return the same index that is + * specified here. + * + * @param index the desired index in the input text. + * @stable ICU 2.8 + */ + public void setIndexOnly(int index) { + text.setIndex(index); // validates index + currentIndex=nextIndex=index; + clearBuffer(); + } + + /** + * Set the iteration position in the input text that is being normalized + * and return the first normalized character at that position. + *

+ * Note: This method sets the position in the input text, + * while {@link #next} and {@link #previous} iterate through characters + * in the normalized output. This means that there is not + * necessarily a one-to-one correspondence between characters returned + * by {@code next} and {@code previous} and the indices passed to and + * returned from {@code setIndex} and {@link #getIndex}. + *

+ * @param index the desired index in the input text. + * + * @return the first normalized character that is the result of iterating + * forward starting at the given index. + * + * @throws IllegalArgumentException if the given index is less than + * {@link #getBeginIndex} or greater than {@link #getEndIndex}. + * deprecated ICU 3.2 + * @obsolete ICU 3.2 + */ + public int setIndex(int index) { + setIndexOnly(index); + return current(); + } + + /** + * Retrieve the index of the start of the input text. This is the begin + * index of the {@code CharacterIterator} or the start (i.e. 0) of the + * {@code String} over which this {@code NormalizerBase} is iterating + * @deprecated ICU 2.2. Use startIndex() instead. + * @return The codepoint as an int + * @see #startIndex + */ + @Deprecated + public int getBeginIndex() { + return 0; + } + + /** + * Retrieve the index of the end of the input text. This is the end index + * of the {@code CharacterIterator} or the length of the {@code String} + * over which this {@code NormalizerBase} is iterating + * @deprecated ICU 2.2. Use endIndex() instead. + * @return The codepoint as an int + * @see #endIndex + */ + @Deprecated + public int getEndIndex() { + return endIndex(); + } + + /** + * Retrieve the current iteration position in the input text that is + * being normalized. This method is useful in applications such as + * searching, where you need to be able to determine the position in + * the input text that corresponds to a given normalized output character. + *

+ * Note: This method sets the position in the input, while + * {@link #next} and {@link #previous} iterate through characters in the + * output. This means that there is not necessarily a one-to-one + * correspondence between characters returned by {@code next} and + * {@code previous} and the indices passed to and returned from + * {@code setIndex} and {@link #getIndex}. + * @return The current iteration position + * @stable ICU 2.8 + */ + public int getIndex() { + if(bufferPos + * Note:If the normalization mode is changed while iterating + * over a string, calls to {@link #next} and {@link #previous} may + * return previously buffers characters in the old normalization mode + * until the iteration is able to re-sync at the next base character. + * It is safest to call {@link #setText setText()}, {@link #first}, + * {@link #last}, etc. after calling {@code setMode}. + *

+ * @param newMode the new mode for this {@code NormalizerBase}. + * The supported modes are: + *

    + *
  • {@link #NFC} - Unicode canonical decompositiion + * followed by canonical composition. + *
  • {@link #NFKC} - Unicode compatibility decompositiion + * follwed by canonical composition. + *
  • {@link #NFD} - Unicode canonical decomposition + *
  • {@link #NFKD} - Unicode compatibility decomposition. + *
  • {@link #NONE} - Do nothing but return characters + * from the underlying input text. + *
+ * + * @see #getMode + * @stable ICU 2.8 + */ + public void setMode(Mode newMode) { + mode = newMode; + norm2 = mode.getNormalizer2(options); + } + + /** + * Return the basic operation performed by this {@code NormalizerBase} + * + * @see #setMode + * @stable ICU 2.8 + */ + public Mode getMode() { + return mode; + } + + /** + * Set the input text over which this {@code NormalizerBase} will iterate. + * The iteration position is set to the beginning of the input text. + * @param newText The new string to be normalized. + * @stable ICU 2.8 + */ + public void setText(String newText) { + UCharacterIterator newIter = UCharacterIterator.getInstance(newText); + if (newIter == null) { + throw new IllegalStateException("Could not create a new UCharacterIterator"); + } + text = newIter; + reset(); + } + + /** + * Set the input text over which this {@code NormalizerBase} will iterate. + * The iteration position is set to the beginning of the input text. + * @param newText The new string to be normalized. + * @stable ICU 2.8 + */ + public void setText(CharacterIterator newText) { + UCharacterIterator newIter = UCharacterIterator.getInstance(newText); + if (newIter == null) { + throw new IllegalStateException("Could not create a new UCharacterIterator"); + } + text = newIter; + currentIndex=nextIndex=0; + clearBuffer(); + } + + private void clearBuffer() { + buffer.setLength(0); + bufferPos=0; + } + + private boolean nextNormalize() { + clearBuffer(); + currentIndex=nextIndex; + text.setIndex(nextIndex); + // Skip at least one character so we make progress. + int c=text.nextCodePoint(); + if(c<0) { + return false; + } + StringBuilder segment=new StringBuilder().appendCodePoint(c); + while((c=text.nextCodePoint())>=0) { + if(norm2.hasBoundaryBefore(c)) { + text.moveCodePointIndex(-1); + break; + } + segment.appendCodePoint(c); + } + nextIndex=text.getIndex(); + norm2.normalize(segment, buffer); + return buffer.length()!=0; + } + + private boolean previousNormalize() { + clearBuffer(); + nextIndex=currentIndex; + text.setIndex(currentIndex); + StringBuilder segment=new StringBuilder(); + int c; + while((c=text.previousCodePoint())>=0) { + if(c<=0xffff) { + segment.insert(0, (char)c); + } else { + segment.insert(0, Character.toChars(c)); + } + if(norm2.hasBoundaryBefore(c)) { + break; + } + } + currentIndex=text.getIndex(); + norm2.normalize(segment, buffer); + bufferPos=buffer.length(); + return buffer.length()!=0; + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/text/Replaceable.class b/tests/test_data/std/jdk/internal/icu/text/Replaceable.class new file mode 100644 index 00000000..52332199 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/Replaceable.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/Replaceable.java b/tests/test_data/std/jdk/internal/icu/text/Replaceable.java new file mode 100644 index 00000000..cf55270a --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/Replaceable.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * (C) Copyright IBM Corp. 1996-2005 - All Rights Reserved * + * * + * The original version of this source code and documentation is copyrighted * + * and owned by IBM, These materials are provided under terms of a License * + * Agreement between IBM and Sun. This technology is protected by multiple * + * US and International patents. This notice and attribution to IBM may not * + * to removed. * + ******************************************************************************* + */ + +package jdk.internal.icu.text; + +/** + * Replaceable is an interface representing a + * string of characters that supports the replacement of a range of + * itself with a new string of characters. It is used by APIs that + * change a piece of text while retaining metadata. Metadata is data + * other than the Unicode characters returned by char32At(). One + * example of metadata is style attributes; another is an edit + * history, marking each character with an author and revision number. + * + *

An implicit aspect of the Replaceable API is that + * during a replace operation, new characters take on the metadata of + * the old characters. For example, if the string "the bold + * font" has range (4, 8) replaced with "strong", then it becomes "the + * strong font". + * + *

Replaceable specifies ranges using a start + * offset and a limit offset. The range of characters thus specified + * includes the characters at offset start..limit-1. That is, the + * start offset is inclusive, and the limit offset is exclusive. + * + *

Replaceable also includes API to access characters + * in the string: length(), charAt(), + * char32At(), and extractBetween(). + * + *

For a subclass to support metadata, typical behavior of + * replace() is the following: + *

    + *
  • Set the metadata of the new text to the metadata of the first + * character replaced
  • + *
  • If no characters are replaced, use the metadata of the + * previous character
  • + *
  • If there is no previous character (i.e. start == 0), use the + * following character
  • + *
  • If there is no following character (i.e. the replaceable was + * empty), use default metadata
  • + *
  • If the code point U+FFFF is seen, it should be interpreted as + * a special marker having no metadata
  • + *
+ * If this is not the behavior, the subclass should document any differences. + * + *

Copyright © IBM Corporation 1999. All rights reserved. + * + * @author Alan Liu + * @stable ICU 2.0 + */ +public interface Replaceable { + /** + * Returns the number of 16-bit code units in the text. + * @return number of 16-bit code units in text + * @stable ICU 2.0 + */ + int length(); + + /** + * Returns the 16-bit code unit at the given offset into the text. + * @param offset an integer between 0 and length()-1 + * inclusive + * @return 16-bit code unit of text at given offset + * @stable ICU 2.0 + */ + char charAt(int offset); + + /** + * Copies characters from this object into the destination + * character array. The first character to be copied is at index + * srcStart; the last character to be copied is at + * index srcLimit-1 (thus the total number of + * characters to be copied is srcLimit-srcStart). The + * characters are copied into the subarray of dst + * starting at index dstStart and ending at index + * dstStart + (srcLimit-srcStart) - 1. + * + * @param srcStart the beginning index to copy, inclusive; + * {@code 0 <= start <= limit}. + * @param srcLimit the ending index to copy, exclusive; + * {@code start <= limit <= length()}. + * @param dst the destination array. + * @param dstStart the start offset in the destination array. + * @stable ICU 2.0 + */ + void getChars(int srcStart, int srcLimit, char dst[], int dstStart); +} diff --git a/tests/test_data/std/jdk/internal/icu/text/ReplaceableString.class b/tests/test_data/std/jdk/internal/icu/text/ReplaceableString.class new file mode 100644 index 00000000..ff068eb6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/ReplaceableString.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/ReplaceableString.java b/tests/test_data/std/jdk/internal/icu/text/ReplaceableString.java new file mode 100644 index 00000000..59d7144e --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/ReplaceableString.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 1996-2009, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ + +package jdk.internal.icu.text; + +/** + * ReplaceableString is an adapter class that implements the + * Replaceable API around an ordinary StringBuffer. + * + *

Note: This class does not support attributes and is not + * intended for general use. Most clients will need to implement + * {@link Replaceable} in their text representation class. + * + *

Copyright © IBM Corporation 1999. All rights reserved. + * + * @see Replaceable + * @author Alan Liu + * @stable ICU 2.0 + */ +public class ReplaceableString implements Replaceable { + + private StringBuffer buf; + + /** + * Construct a new object with the given initial contents. + * @param str initial contents + * @stable ICU 2.0 + */ + public ReplaceableString(String str) { + buf = new StringBuffer(str); + } + + /** + * Construct a new object using buf for internal + * storage. The contents of buf at the time of + * construction are used as the initial contents. Note! + * Modifications to buf will modify this object, and + * vice versa. + * @param buf object to be used as internal storage + * @stable ICU 2.0 + */ + public ReplaceableString(StringBuffer buf) { + this.buf = buf; + } + + /** + * Return the number of characters contained in this object. + * Replaceable API. + * @stable ICU 2.0 + */ + public int length() { + return buf.length(); + } + + /** + * Return the character at the given position in this object. + * Replaceable API. + * @param offset offset into the contents, from 0 to + * length() - 1 + * @stable ICU 2.0 + */ + public char charAt(int offset) { + return buf.charAt(offset); + } + + /** + * Copies characters from this object into the destination + * character array. The first character to be copied is at index + * srcStart; the last character to be copied is at + * index srcLimit-1 (thus the total number of + * characters to be copied is srcLimit-srcStart). The + * characters are copied into the subarray of dst + * starting at index dstStart and ending at index + * dstStart + (srcLimit-srcStart) - 1. + * + * @param srcStart the beginning index to copy, inclusive; + * {@code 0 <= start <= limit}. + * @param srcLimit the ending index to copy, exclusive; + * {@code start <= limit <= length()}. + * @param dst the destination array. + * @param dstStart the start offset in the destination array. + * @stable ICU 2.0 + */ + public void getChars(int srcStart, int srcLimit, char dst[], int dstStart) { + if (srcStart != srcLimit) { + buf.getChars(srcStart, srcLimit, dst, dstStart); + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/text/StringPrep$StringPrepTrieImpl.class b/tests/test_data/std/jdk/internal/icu/text/StringPrep$StringPrepTrieImpl.class new file mode 100644 index 00000000..c6edc906 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/StringPrep$StringPrepTrieImpl.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/StringPrep$Values.class b/tests/test_data/std/jdk/internal/icu/text/StringPrep$Values.class new file mode 100644 index 00000000..4c536e77 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/StringPrep$Values.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/StringPrep.class b/tests/test_data/std/jdk/internal/icu/text/StringPrep.class new file mode 100644 index 00000000..48a9a669 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/StringPrep.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/StringPrep.java b/tests/test_data/std/jdk/internal/icu/text/StringPrep.java new file mode 100644 index 00000000..f05d0403 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/StringPrep.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* +/* + ******************************************************************************* + * Copyright (C) 2003-2004, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ +// +// CHANGELOG +// 2005-05-19 Edward Wang +// - copy this file from icu4jsrc_3_2/src/com/ibm/icu/text/StringPrep.java +// - move from package com.ibm.icu.text to package sun.net.idn +// - use ParseException instead of StringPrepParseException +// - change 'Normalizer.getUnicodeVersion()' to 'NormalizerImpl.getUnicodeVersion()' +// - remove all @deprecated tag to make compiler happy +// 2007-08-14 Martin Buchholz +// - remove redundant casts +// +package jdk.internal.icu.text; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; + +import sun.text.Normalizer; +import jdk.internal.icu.impl.CharTrie; +import jdk.internal.icu.impl.StringPrepDataReader; +import jdk.internal.icu.impl.Trie; +import jdk.internal.icu.lang.UCharacter; +import jdk.internal.icu.lang.UCharacterDirection; +import jdk.internal.icu.util.VersionInfo; + +/** + * StringPrep API implements the StingPrep framework as described by + * RFC 3454. + * StringPrep prepares Unicode strings for use in network protocols. + * Profiles of StingPrep are set of rules and data according to which the + * Unicode Strings are prepared. Each profiles contains tables which describe + * how a code point should be treated. The tables are broadly classied into + *

    + *
  • Unassigned Table: Contains code points that are unassigned + * in the Unicode Version supported by StringPrep. Currently + * RFC 3454 supports Unicode 3.2.
  • + *
  • Prohibited Table: Contains code points that are prohibited from + * the output of the StringPrep processing function.
  • + *
  • Mapping Table: Contains code points that are deleted from the output or case mapped.
  • + *
+ * + * The procedure for preparing Unicode strings: + *
    + *
  1. Map: For each character in the input, check if it has a mapping + * and, if so, replace it with its mapping.
  2. + *
  3. Normalize: Possibly normalize the result of step 1 using Unicode + * normalization.
  4. + *
  5. Prohibit: Check for any characters that are not allowed in the + * output. If any are found, return an error.
  6. + *
  7. Check bidi: Possibly check for right-to-left characters, and if + * any are found, make sure that the whole string satisfies the + * requirements for bidirectional strings. If the string does not + * satisfy the requirements for bidirectional strings, return an + * error.
  8. + *
+ * @author Ram Viswanadha + * @draft ICU 2.8 + */ +public final class StringPrep { + /** + * Option to prohibit processing of unassigned code points in the input + * + * @see #prepare + * @draft ICU 2.8 + */ + public static final int DEFAULT = 0x0000; + + /** + * Option to allow processing of unassigned code points in the input + * + * @see #prepare + * @draft ICU 2.8 + */ + public static final int ALLOW_UNASSIGNED = 0x0001; + + private static final int UNASSIGNED = 0x0000; + private static final int MAP = 0x0001; + private static final int PROHIBITED = 0x0002; + private static final int DELETE = 0x0003; + private static final int TYPE_LIMIT = 0x0004; + + private static final int NORMALIZATION_ON = 0x0001; + private static final int CHECK_BIDI_ON = 0x0002; + + private static final int TYPE_THRESHOLD = 0xFFF0; + private static final int MAX_INDEX_VALUE = 0x3FBF; /*16139*/ + private static final int MAX_INDEX_TOP_LENGTH = 0x0003; + + /* indexes[] value names */ + private static final int INDEX_TRIE_SIZE = 0; /* number of bytes in normalization trie */ + private static final int INDEX_MAPPING_DATA_SIZE = 1; /* The array that contains the mapping */ + private static final int NORM_CORRECTNS_LAST_UNI_VERSION = 2; /* The index of Unicode version of last entry in NormalizationCorrections.txt */ + private static final int ONE_UCHAR_MAPPING_INDEX_START = 3; /* The starting index of 1 UChar mapping index in the mapping data array */ + private static final int TWO_UCHARS_MAPPING_INDEX_START = 4; /* The starting index of 2 UChars mapping index in the mapping data array */ + private static final int THREE_UCHARS_MAPPING_INDEX_START = 5; + private static final int FOUR_UCHARS_MAPPING_INDEX_START = 6; + private static final int OPTIONS = 7; /* Bit set of options to turn on in the profile */ + private static final int INDEX_TOP = 16; /* changing this requires a new formatVersion */ + + + /** + * Default buffer size of datafile + */ + private static final int DATA_BUFFER_SIZE = 25000; + + /* Wrappers for Trie implementations */ + private static final class StringPrepTrieImpl implements Trie.DataManipulate{ + private CharTrie sprepTrie = null; + /** + * Called by com.ibm.icu.util.Trie to extract from a lead surrogate's + * data the index array offset of the indexes for that lead surrogate. + * @param property data value for a surrogate from the trie, including + * the folding offset + * @return data offset or 0 if there is no data for the lead surrogate + */ + public int getFoldingOffset(int value){ + return value; + } + } + + // CharTrie implementation for reading the trie data + private StringPrepTrieImpl sprepTrieImpl; + // Indexes read from the data file + private int[] indexes; + // mapping data read from the data file + private char[] mappingData; + // format version of the data file + private byte[] formatVersion; + // the version of Unicode supported by the data file + private VersionInfo sprepUniVer; + // the Unicode version of last entry in the + // NormalizationCorrections.txt file if normalization + // is turned on + private VersionInfo normCorrVer; + // Option to turn on Normalization + private boolean doNFKC; + // Option to turn on checking for BiDi rules + private boolean checkBiDi; + + + private char getCodePointValue(int ch){ + return sprepTrieImpl.sprepTrie.getCodePointValue(ch); + } + + private static VersionInfo getVersionInfo(int comp){ + int micro = comp & 0xFF; + int milli =(comp >> 8) & 0xFF; + int minor =(comp >> 16) & 0xFF; + int major =(comp >> 24) & 0xFF; + return VersionInfo.getInstance(major,minor,milli,micro); + } + private static VersionInfo getVersionInfo(byte[] version){ + if(version.length != 4){ + return null; + } + return VersionInfo.getInstance((int)version[0],(int) version[1],(int) version[2],(int) version[3]); + } + /** + * Creates an StringPrep object after reading the input stream. + * The object does not hold a reference to the input steam, so the stream can be + * closed after the method returns. + * + * @param inputStream The stream for reading the StringPrep profile binarySun + * @throws IOException + * @draft ICU 2.8 + */ + public StringPrep(InputStream inputStream) throws IOException{ + + BufferedInputStream b = new BufferedInputStream(inputStream,DATA_BUFFER_SIZE); + + StringPrepDataReader reader = new StringPrepDataReader(b); + + // read the indexes + indexes = reader.readIndexes(INDEX_TOP); + + byte[] sprepBytes = new byte[indexes[INDEX_TRIE_SIZE]]; + + + //indexes[INDEX_MAPPING_DATA_SIZE] store the size of mappingData in bytes + mappingData = new char[indexes[INDEX_MAPPING_DATA_SIZE]/2]; + // load the rest of the data and initialize the data members + reader.read(sprepBytes,mappingData); + + sprepTrieImpl = new StringPrepTrieImpl(); + sprepTrieImpl.sprepTrie = new CharTrie( new ByteArrayInputStream(sprepBytes),sprepTrieImpl ); + + // get the data format version + formatVersion = reader.getDataFormatVersion(); + + // get the options + doNFKC = ((indexes[OPTIONS] & NORMALIZATION_ON) > 0); + checkBiDi = ((indexes[OPTIONS] & CHECK_BIDI_ON) > 0); + sprepUniVer = getVersionInfo(reader.getUnicodeVersion()); + normCorrVer = getVersionInfo(indexes[NORM_CORRECTNS_LAST_UNI_VERSION]); + VersionInfo normUniVer = UCharacter.getUnicodeVersion(); + if(normUniVer.compareTo(sprepUniVer) < 0 && /* the Unicode version of SPREP file must be less than the Unicode Version of the normalization data */ + normUniVer.compareTo(normCorrVer) < 0 && /* the Unicode version of the NormalizationCorrections.txt file should be less than the Unicode Version of the normalization data */ + ((indexes[OPTIONS] & NORMALIZATION_ON) > 0) /* normalization turned on*/ + ){ + throw new IOException("Normalization Correction version not supported"); + } + b.close(); + } + + private static final class Values{ + boolean isIndex; + int value; + int type; + public void reset(){ + isIndex = false; + value = 0; + type = -1; + } + } + + private static final void getValues(char trieWord,Values values){ + values.reset(); + if(trieWord == 0){ + /* + * Initial value stored in the mapping table + * just return TYPE_LIMIT .. so that + * the source codepoint is copied to the destination + */ + values.type = TYPE_LIMIT; + }else if(trieWord >= TYPE_THRESHOLD){ + values.type = (trieWord - TYPE_THRESHOLD); + }else{ + /* get the type */ + values.type = MAP; + /* ascertain if the value is index or delta */ + if((trieWord & 0x02)>0){ + values.isIndex = true; + values.value = trieWord >> 2; //mask off the lower 2 bits and shift + + }else{ + values.isIndex = false; + values.value = (trieWord<<16)>>16; + values.value = (values.value >> 2); + + } + + if((trieWord>>2) == MAX_INDEX_VALUE){ + values.type = DELETE; + values.isIndex = false; + values.value = 0; + } + } + } + + + + private StringBuffer map( UCharacterIterator iter, int options) + throws ParseException { + + Values val = new Values(); + char result = 0; + int ch = UCharacterIterator.DONE; + StringBuffer dest = new StringBuffer(); + boolean allowUnassigned = ((options & ALLOW_UNASSIGNED)>0); + + while((ch=iter.nextCodePoint())!= UCharacterIterator.DONE){ + + result = getCodePointValue(ch); + getValues(result,val); + + // check if the source codepoint is unassigned + if(val.type == UNASSIGNED && allowUnassigned == false){ + throw new ParseException("An unassigned code point was found in the input " + + iter.getText(), iter.getIndex()); + }else if((val.type == MAP)){ + int index, length; + + if(val.isIndex){ + index = val.value; + if(index >= indexes[ONE_UCHAR_MAPPING_INDEX_START] && + index < indexes[TWO_UCHARS_MAPPING_INDEX_START]){ + length = 1; + }else if(index >= indexes[TWO_UCHARS_MAPPING_INDEX_START] && + index < indexes[THREE_UCHARS_MAPPING_INDEX_START]){ + length = 2; + }else if(index >= indexes[THREE_UCHARS_MAPPING_INDEX_START] && + index < indexes[FOUR_UCHARS_MAPPING_INDEX_START]){ + length = 3; + }else{ + length = mappingData[index++]; + } + /* copy mapping to destination */ + dest.append(mappingData,index,length); + continue; + + }else{ + ch -= val.value; + } + }else if(val.type == DELETE){ + // just consume the codepoint and continue + continue; + } + //copy the source into destination + UTF16.append(dest,ch); + } + + return dest; + } + + + private StringBuffer normalize(StringBuffer src){ + /* + * Option UNORM_BEFORE_PRI_29: + * + * IDNA as interpreted by IETF members (see unicode mailing list 2004H1) + * requires strict adherence to Unicode 3.2 normalization, + * including buggy composition from before fixing Public Review Issue #29. + * Note that this results in some valid but nonsensical text to be + * either corrupted or rejected, depending on the text. + * See http://www.unicode.org/review/resolved-pri.html#pri29 + * See unorm.cpp and cnormtst.c + */ + return new StringBuffer( + Normalizer.normalize( + src.toString(), + java.text.Normalizer.Form.NFKC, + Normalizer.UNICODE_3_2)); + } + /* + boolean isLabelSeparator(int ch){ + int result = getCodePointValue(ch); + if( (result & 0x07) == LABEL_SEPARATOR){ + return true; + } + return false; + } + */ + /* + 1) Map -- For each character in the input, check if it has a mapping + and, if so, replace it with its mapping. + + 2) Normalize -- Possibly normalize the result of step 1 using Unicode + normalization. + + 3) Prohibit -- Check for any characters that are not allowed in the + output. If any are found, return an error. + + 4) Check bidi -- Possibly check for right-to-left characters, and if + any are found, make sure that the whole string satisfies the + requirements for bidirectional strings. If the string does not + satisfy the requirements for bidirectional strings, return an + error. + [Unicode3.2] defines several bidirectional categories; each character + has one bidirectional category assigned to it. For the purposes of + the requirements below, an "RandALCat character" is a character that + has Unicode bidirectional categories "R" or "AL"; an "LCat character" + is a character that has Unicode bidirectional category "L". Note + + + that there are many characters which fall in neither of the above + definitions; Latin digits ( through ) are examples of + this because they have bidirectional category "EN". + + In any profile that specifies bidirectional character handling, all + three of the following requirements MUST be met: + + 1) The characters in section 5.8 MUST be prohibited. + + 2) If a string contains any RandALCat character, the string MUST NOT + contain any LCat character. + + 3) If a string contains any RandALCat character, a RandALCat + character MUST be the first character of the string, and a + RandALCat character MUST be the last character of the string. + */ + /** + * Prepare the input buffer for use in applications with the given profile. This operation maps, normalizes(NFKC), + * checks for prohited and BiDi characters in the order defined by RFC 3454 + * depending on the options specified in the profile. + * + * @param src A UCharacterIterator object containing the source string + * @param options A bit set of options: + * + * - StringPrep.NONE Prohibit processing of unassigned code points in the input + * + * - StringPrep.ALLOW_UNASSIGNED Treat the unassigned code points are in the input + * as normal Unicode code points. + * + * @return StringBuffer A StringBuffer containing the output + * @throws ParseException + * @draft ICU 2.8 + */ + public StringBuffer prepare(UCharacterIterator src, int options) + throws ParseException{ + + // map + StringBuffer mapOut = map(src,options); + StringBuffer normOut = mapOut;// initialize + + if(doNFKC){ + // normalize + normOut = normalize(mapOut); + } + + int ch; + char result; + UCharacterIterator iter = UCharacterIterator.getInstance(normOut); + Values val = new Values(); + int direction=UCharacterDirection.CHAR_DIRECTION_COUNT, + firstCharDir=UCharacterDirection.CHAR_DIRECTION_COUNT; + int rtlPos=-1, ltrPos=-1; + boolean rightToLeft=false, leftToRight=false; + + while((ch=iter.nextCodePoint())!= UCharacterIterator.DONE){ + result = getCodePointValue(ch); + getValues(result,val); + + if(val.type == PROHIBITED ){ + throw new ParseException("A prohibited code point was found in the input" + + iter.getText(), val.value); + } + + direction = UCharacter.getDirection(ch); + if(firstCharDir == UCharacterDirection.CHAR_DIRECTION_COUNT){ + firstCharDir = direction; + } + if(direction == UCharacterDirection.LEFT_TO_RIGHT){ + leftToRight = true; + ltrPos = iter.getIndex()-1; + } + if(direction == UCharacterDirection.RIGHT_TO_LEFT || direction == UCharacterDirection.RIGHT_TO_LEFT_ARABIC){ + rightToLeft = true; + rtlPos = iter.getIndex()-1; + } + } + if(checkBiDi == true){ + // satisfy 2 + if( leftToRight == true && rightToLeft == true){ + throw new ParseException("The input does not conform to the rules for BiDi code points." + + iter.getText(), + (rtlPos>ltrPos) ? rtlPos : ltrPos); + } + + //satisfy 3 + if( rightToLeft == true && + !((firstCharDir == UCharacterDirection.RIGHT_TO_LEFT || firstCharDir == UCharacterDirection.RIGHT_TO_LEFT_ARABIC) && + (direction == UCharacterDirection.RIGHT_TO_LEFT || direction == UCharacterDirection.RIGHT_TO_LEFT_ARABIC)) + ){ + throw new ParseException("The input does not conform to the rules for BiDi code points." + + iter.getText(), + (rtlPos>ltrPos) ? rtlPos : ltrPos); + } + } + return normOut; + + } +} diff --git a/tests/test_data/std/jdk/internal/icu/text/UCharacterIterator.class b/tests/test_data/std/jdk/internal/icu/text/UCharacterIterator.class new file mode 100644 index 00000000..6777fd3c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/UCharacterIterator.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/UCharacterIterator.java b/tests/test_data/std/jdk/internal/icu/text/UCharacterIterator.java new file mode 100644 index 00000000..93978372 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/UCharacterIterator.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 1996-2014, International Business Machines Corporation and * + * others. All Rights Reserved. * + ******************************************************************************* + */ + +package jdk.internal.icu.text; + +import jdk.internal.icu.impl.CharacterIteratorWrapper; +import jdk.internal.icu.impl.ReplaceableUCharacterIterator; +import jdk.internal.icu.impl.UCharacterProperty; + +import java.text.CharacterIterator; + +/** + * Abstract class that defines an API for iteration on text objects.This is an + * interface for forward and backward iteration and random access into a text + * object. Forward iteration is done with post-increment and backward iteration + * is done with pre-decrement semantics, while the + * java.text.CharacterIterator interface methods provided forward + * iteration with "pre-increment" and backward iteration with pre-decrement + * semantics. This API is more efficient for forward iteration over code points. + * The other major difference is that this API can do both code unit and code point + * iteration, java.text.CharacterIterator can only iterate over + * code units and is limited to BMP (0 - 0xFFFF) + * @author Ram + * @stable ICU 2.4 + */ +public abstract class UCharacterIterator + implements Cloneable { + + /** + * Protected default constructor for the subclasses + * @stable ICU 2.4 + */ + protected UCharacterIterator(){ + } + + /** + * Indicator that we have reached the ends of the UTF16 text. + * Moved from UForwardCharacterIterator.java + * @stable ICU 2.4 + */ + public static final int DONE = -1; + + // static final methods ---------------------------------------------------- + + /** + * Returns a UCharacterIterator object given a + * source string. + * @param source a string + * @return UCharacterIterator object + * @exception IllegalArgumentException if the argument is null + * @stable ICU 2.4 + */ + public static final UCharacterIterator getInstance(String source){ + return new ReplaceableUCharacterIterator(source); + } + + /** + * Returns a UCharacterIterator object given a + * source StringBuffer. + * @param source an string buffer of UTF-16 code units + * @return UCharacterIterator object + * @exception IllegalArgumentException if the argument is null + * @stable ICU 2.4 + */ + public static final UCharacterIterator getInstance(StringBuffer source){ + return new ReplaceableUCharacterIterator(source); + } + + /** + * Returns a UCharacterIterator object given a + * CharacterIterator. + * @param source a valid CharacterIterator object. + * @return UCharacterIterator object + * @exception IllegalArgumentException if the argument is null + * @stable ICU 2.4 + */ + public static final UCharacterIterator getInstance(CharacterIterator source){ + return new CharacterIteratorWrapper(source); + } + + // public methods ---------------------------------------------------------- + + /** + * Returns the length of the text + * @return length of the text + * @stable ICU 2.4 + */ + public abstract int getLength(); + + /** + * Gets the current index in text. + * @return current index in text. + * @stable ICU 2.4 + */ + public abstract int getIndex(); + + /** + * Returns the UTF16 code unit at index, and increments to the next + * code unit (post-increment semantics). If index is out of + * range, DONE is returned, and the iterator is reset to the limit + * of the text. + * @return the next UTF16 code unit, or DONE if the index is at the limit + * of the text. + * @stable ICU 2.4 + */ + public abstract int next(); + + /** + * Returns the code point at index, and increments to the next code + * point (post-increment semantics). If index does not point to a + * valid surrogate pair, the behavior is the same as + * next(). Otherwise the iterator is incremented past + * the surrogate pair, and the code point represented by the pair + * is returned. + * @return the next codepoint in text, or DONE if the index is at + * the limit of the text. + * @stable ICU 2.4 + */ + public int nextCodePoint(){ + int ch1 = next(); + if(UTF16.isLeadSurrogate(ch1)){ + int ch2 = next(); + if(UTF16.isTrailSurrogate(ch2)){ + return UCharacterProperty.getRawSupplementary((char)ch1, + (char)ch2); + }else if (ch2 != DONE) { + // unmatched surrogate so back out + previous(); + } + } + return ch1; + } + + /** + * Decrement to the position of the previous code unit in the + * text, and return it (pre-decrement semantics). If the + * resulting index is less than 0, the index is reset to 0 and + * DONE is returned. + * @return the previous code unit in the text, or DONE if the new + * index is before the start of the text. + * @stable ICU 2.4 + */ + public abstract int previous(); + + + /** + * Retreat to the start of the previous code point in the text, + * and return it (pre-decrement semantics). If the index is not + * preceded by a valid surrogate pair, the behavior is the same + * as previous(). Otherwise the iterator is + * decremented to the start of the surrogate pair, and the code + * point represented by the pair is returned. + * @return the previous code point in the text, or DONE if the new + * index is before the start of the text. + * @stable ICU 2.4 + */ + public int previousCodePoint(){ + int ch1 = previous(); + if(UTF16.isTrailSurrogate(ch1)){ + int ch2 = previous(); + if(UTF16.isLeadSurrogate(ch2)){ + return UCharacterProperty.getRawSupplementary((char)ch2, + (char)ch1); + }else if (ch2 != DONE) { + //unmatched trail surrogate so back out + next(); + } + } + return ch1; + } + + /** + * Sets the index to the specified index in the text. + * @param index the index within the text. + * @exception IndexOutOfBoundsException is thrown if an invalid index is + * supplied + * @stable ICU 2.4 + */ + public abstract void setIndex(int index); + + /** + * Sets the current index to the start. + * @stable ICU 2.4 + */ + public void setToStart() { + setIndex(0); + } + + /** + * Fills the buffer with the underlying text storage of the iterator + * If the buffer capacity is not enough a exception is thrown. The capacity + * of the fill in buffer should at least be equal to length of text in the + * iterator obtained by calling getLength(). + * Usage: + * + *
{@code
+     *         UChacterIterator iter = new UCharacterIterator.getInstance(text);
+     *         char[] buf = new char[iter.getLength()];
+     *         iter.getText(buf);
+     *
+     *         OR
+     *         char[] buf= new char[1];
+     *         int len = 0;
+     *         for(;;){
+     *             try{
+     *                 len = iter.getText(buf);
+     *                 break;
+     *             }catch(IndexOutOfBoundsException e){
+     *                 buf = new char[iter.getLength()];
+     *             }
+     *         }
+     * }
+ * + * @param fillIn an array of chars to fill with the underlying UTF-16 code + * units. + * @param offset the position within the array to start putting the data. + * @return the number of code units added to fillIn, as a convenience + * @exception IndexOutOfBoundsException exception if there is not enough + * room after offset in the array, or if offset < 0. + * @stable ICU 2.4 + */ + public abstract int getText(char[] fillIn, int offset); + + /** + * Convenience override for getText(char[], int) that provides + * an offset of 0. + * @param fillIn an array of chars to fill with the underlying UTF-16 code + * units. + * @return the number of code units added to fillIn, as a convenience + * @exception IndexOutOfBoundsException exception if there is not enough + * room in the array. + * @stable ICU 2.4 + */ + public final int getText(char[] fillIn) { + return getText(fillIn, 0); + } + + /** + * Convenience method for returning the underlying text storage as a string + * @return the underlying text storage in the iterator as a string + * @stable ICU 2.4 + */ + public String getText() { + char[] text = new char[getLength()]; + getText(text); + return new String(text); + } + + /** + * Moves the current position by the number of code points + * specified, either forward or backward depending on the sign of + * delta (positive or negative respectively). If the current index + * is at a trail surrogate then the first adjustment is by code + * unit, and the remaining adjustments are by code points. If the + * resulting index would be less than zero, the index is set to + * zero, and if the resulting index would be greater than limit, + * the index is set to limit. + * @param delta the number of code units to move the current index. + * @return the new index + * @exception IndexOutOfBoundsException is thrown if an invalid delta is + * supplied + * @stable ICU 2.4 + * + */ + public int moveCodePointIndex(int delta){ + if(delta>0){ + while(delta>0 && nextCodePoint() != DONE){delta--;} + }else{ + while(delta<0 && previousCodePoint() != DONE){delta++;} + } + if(delta!=0){ + throw new IndexOutOfBoundsException(); + } + + return getIndex(); + } + + /** + * Creates a copy of this iterator, independent from other iterators. + * If it is not possible to clone the iterator, returns null. + * @return copy of this iterator + * @stable ICU 2.4 + */ + public Object clone() throws CloneNotSupportedException{ + return super.clone(); + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/text/UTF16.class b/tests/test_data/std/jdk/internal/icu/text/UTF16.class new file mode 100644 index 00000000..3cc2a284 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/UTF16.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/UTF16.java b/tests/test_data/std/jdk/internal/icu/text/UTF16.java new file mode 100644 index 00000000..e84e1d4c --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/UTF16.java @@ -0,0 +1,621 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + ******************************************************************************* + * Copyright (C) 1996-2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ + +package jdk.internal.icu.text; + +import jdk.internal.icu.impl.UCharacterProperty; + +/** + *

Standalone utility class providing UTF16 character conversions and + * indexing conversions. + *

Code that uses strings alone rarely need modification. + * By design, UTF-16 does not allow overlap, so searching for strings is a safe + * operation. Similarly, concatenation is always safe. Substringing is safe if + * the start and end are both on UTF-32 boundaries. In normal code, the values + * for start and end are on those boundaries, since they arose from operations + * like searching. If not, the nearest UTF-32 boundaries can be determined + * using bounds(). + * Examples: + *

The following examples illustrate use of some of these methods. + *

{@code
+ * // iteration forwards: Original
+ * for (int i = 0; i < s.length(); ++i) {
+ *     char ch = s.charAt(i);
+ *     doSomethingWith(ch);
+ * }
+ *
+ * // iteration forwards: Changes for UTF-32
+ * int ch;
+ * for (int i = 0; i < s.length(); i += UTF16.getCharCount(ch)) {
+ *     ch = UTF16.charAt(s, i);
+ *     doSomethingWith(ch);
+ * }
+ *
+ * // iteration backwards: Original
+ * for (int i = s.length() - 1; i >= 0; --i) {
+ *     char ch = s.charAt(i);
+ *     doSomethingWith(ch);
+ * }
+ *
+ * // iteration backwards: Changes for UTF-32
+ * int ch;
+ * for (int i = s.length() - 1; i > 0; i -= UTF16.getCharCount(ch)) {
+ *     ch = UTF16.charAt(s, i);
+ *     doSomethingWith(ch);
+ * }
+ * }
+ * Notes: + *
    + *
  • + * Naming: For clarity, High and Low surrogates are called + * Lead and Trail in the API, which gives a better + * sense of their ordering in a string. offset16 and + * offset32 are used to distinguish offsets to UTF-16 + * boundaries vs offsets to UTF-32 boundaries. int char32 is + * used to contain UTF-32 characters, as opposed to char16, + * which is a UTF-16 code unit. + *
  • + *
  • + * Roundtripping Offsets: You can always roundtrip from a + * UTF-32 offset to a UTF-16 offset and back. Because of the difference in + * structure, you can roundtrip from a UTF-16 offset to a UTF-32 offset and + * back if and only if bounds(string, offset16) != TRAIL. + *
  • + *
  • + * Exceptions: The error checking will throw an exception + * if indices are out of bounds. Other than that, all methods will + * behave reasonably, even if unmatched surrogates or out-of-bounds UTF-32 + * values are present. UCharacter.isLegal() can be used to check + * for validity if desired. + *
  • + *
  • + * Unmatched Surrogates: If the string contains unmatched + * surrogates, then these are counted as one UTF-32 value. This matches + * their iteration behavior, which is vital. It also matches common display + * practice as missing glyphs (see the Unicode Standard Section 5.4, 5.5). + *
  • + *
  • + * Optimization: The method implementations may need + * optimization if the compiler doesn't fold static final methods. Since + * surrogate pairs will form an exceeding small percentage of all the text + * in the world, the singleton case should always be optimized for. + *
  • + *
+ * @author Mark Davis, with help from Markus Scherer + * @stable ICU 2.1 + */ + +public final class UTF16 +{ + // public variables --------------------------------------------------- + + /** + * The lowest Unicode code point value. + * @stable ICU 2.1 + */ + public static final int CODEPOINT_MIN_VALUE = 0; + /** + * The highest Unicode code point value (scalar value) according to the + * Unicode Standard. + * @stable ICU 2.1 + */ + public static final int CODEPOINT_MAX_VALUE = 0x10ffff; + /** + * The minimum value for Supplementary code points + * @stable ICU 2.1 + */ + public static final int SUPPLEMENTARY_MIN_VALUE = 0x10000; + /** + * Lead surrogate minimum value + * @stable ICU 2.1 + */ + public static final int LEAD_SURROGATE_MIN_VALUE = 0xD800; + /** + * Trail surrogate minimum value + * @stable ICU 2.1 + */ + public static final int TRAIL_SURROGATE_MIN_VALUE = 0xDC00; + /** + * Lead surrogate maximum value + * @stable ICU 2.1 + */ + public static final int LEAD_SURROGATE_MAX_VALUE = 0xDBFF; + /** + * Trail surrogate maximum value + * @stable ICU 2.1 + */ + public static final int TRAIL_SURROGATE_MAX_VALUE = 0xDFFF; + /** + * Surrogate minimum value + * @stable ICU 2.1 + */ + public static final int SURROGATE_MIN_VALUE = LEAD_SURROGATE_MIN_VALUE; + /** + * Lead surrogate bitmask + */ + private static final int LEAD_SURROGATE_BITMASK = 0xFFFFFC00; + /** + * Trail surrogate bitmask + */ + private static final int TRAIL_SURROGATE_BITMASK = 0xFFFFFC00; + /** + * Surrogate bitmask + */ + private static final int SURROGATE_BITMASK = 0xFFFFF800; + /** + * Lead surrogate bits + */ + private static final int LEAD_SURROGATE_BITS = 0xD800; + /** + * Trail surrogate bits + */ + private static final int TRAIL_SURROGATE_BITS = 0xDC00; + /** + * Surrogate bits + */ + private static final int SURROGATE_BITS = 0xD800; + + // constructor -------------------------------------------------------- + + // /CLOVER:OFF + /** + * Prevent instance from being created. + */ + private UTF16() { + } + + // /CLOVER:ON + // public method ------------------------------------------------------ + + /** + * Extract a single UTF-32 value from a string. + * Used when iterating forwards or backwards (with + * UTF16.getCharCount(), as well as random access. If a + * validity check is required, use + * + * UCharacter.isLegal() on the return value. + * If the char retrieved is part of a surrogate pair, its supplementary + * character will be returned. If a complete supplementary character is + * not found the incomplete character will be returned + * @param source array of UTF-16 chars + * @param offset16 UTF-16 offset to the start of the character. + * @return UTF-32 value for the UTF-32 value that contains the char at + * offset16. The boundaries of that codepoint are the same as in + * bounds32(). + * @exception IndexOutOfBoundsException thrown if offset16 is out of + * bounds. + * @stable ICU 2.1 + */ + public static int charAt(String source, int offset16) { + char single = source.charAt(offset16); + if (single < LEAD_SURROGATE_MIN_VALUE) { + return single; + } + return _charAt(source, offset16, single); + } + + private static int _charAt(String source, int offset16, char single) { + if (single > TRAIL_SURROGATE_MAX_VALUE) { + return single; + } + + // Convert the UTF-16 surrogate pair if necessary. + // For simplicity in usage, and because the frequency of pairs is + // low, look both directions. + + if (single <= LEAD_SURROGATE_MAX_VALUE) { + ++offset16; + if (source.length() != offset16) { + char trail = source.charAt(offset16); + if (trail >= TRAIL_SURROGATE_MIN_VALUE && trail <= TRAIL_SURROGATE_MAX_VALUE) { + return UCharacterProperty.getRawSupplementary(single, trail); + } + } + } else { + --offset16; + if (offset16 >= 0) { + // single is a trail surrogate so + char lead = source.charAt(offset16); + if (lead >= LEAD_SURROGATE_MIN_VALUE && lead <= LEAD_SURROGATE_MAX_VALUE) { + return UCharacterProperty.getRawSupplementary(lead, single); + } + } + } + return single; // return unmatched surrogate + } + + /** + * Extract a single UTF-32 value from a string. + * Used when iterating forwards or backwards (with + * UTF16.getCharCount(), as well as random access. If a + * validity check is required, use + * UCharacter.isLegal() + * on the return value. + * If the char retrieved is part of a surrogate pair, its supplementary + * character will be returned. If a complete supplementary character is + * not found the incomplete character will be returned + * @param source array of UTF-16 chars + * @param offset16 UTF-16 offset to the start of the character. + * @return UTF-32 value for the UTF-32 value that contains the char at + * offset16. The boundaries of that codepoint are the same as in + * bounds32(). + * @exception IndexOutOfBoundsException thrown if offset16 is out of bounds. + * @stable ICU 2.1 + */ + public static int charAt(CharSequence source, int offset16) { + char single = source.charAt(offset16); + if (single < UTF16.LEAD_SURROGATE_MIN_VALUE) { + return single; + } + return _charAt(source, offset16, single); + } + + private static int _charAt(CharSequence source, int offset16, char single) { + if (single > UTF16.TRAIL_SURROGATE_MAX_VALUE) { + return single; + } + + // Convert the UTF-16 surrogate pair if necessary. + // For simplicity in usage, and because the frequency of pairs is + // low, look both directions. + + if (single <= UTF16.LEAD_SURROGATE_MAX_VALUE) { + ++offset16; + if (source.length() != offset16) { + char trail = source.charAt(offset16); + if (trail >= UTF16.TRAIL_SURROGATE_MIN_VALUE + && trail <= UTF16.TRAIL_SURROGATE_MAX_VALUE) { + return UCharacterProperty.getRawSupplementary(single, trail); + } + } + } else { + --offset16; + if (offset16 >= 0) { + // single is a trail surrogate so + char lead = source.charAt(offset16); + if (lead >= UTF16.LEAD_SURROGATE_MIN_VALUE + && lead <= UTF16.LEAD_SURROGATE_MAX_VALUE) { + return UCharacterProperty.getRawSupplementary(lead, single); + } + } + } + return single; // return unmatched surrogate + } + + /** + * Extract a single UTF-32 value from a substring. Used when iterating forwards or backwards + * (with UTF16.getCharCount(), as well as random access. If a validity check is + * required, use UCharacter.isLegal() + * + * on the return value. If the char retrieved is part of a surrogate pair, its supplementary + * character will be returned. If a complete supplementary character is not found the incomplete + * character will be returned + * + * @param source Array of UTF-16 chars + * @param start Offset to substring in the source array for analyzing + * @param limit Offset to substring in the source array for analyzing + * @param offset16 UTF-16 offset relative to start + * @return UTF-32 value for the UTF-32 value that contains the char at offset16. The boundaries + * of that codepoint are the same as in bounds32(). + * @exception IndexOutOfBoundsException Thrown if offset16 is not within the range of start and limit. + * @stable ICU 2.1 + */ + public static int charAt(char source[], int start, int limit, int offset16) { + offset16 += start; + if (offset16 < start || offset16 >= limit) { + throw new ArrayIndexOutOfBoundsException(offset16); + } + + char single = source[offset16]; + if (!isSurrogate(single)) { + return single; + } + + // Convert the UTF-16 surrogate pair if necessary. + // For simplicity in usage, and because the frequency of pairs is + // low, look both directions. + if (single <= LEAD_SURROGATE_MAX_VALUE) { + offset16++; + if (offset16 >= limit) { + return single; + } + char trail = source[offset16]; + if (isTrailSurrogate(trail)) { + return UCharacterProperty.getRawSupplementary(single, trail); + } + } + else { // isTrailSurrogate(single), so + if (offset16 == start) { + return single; + } + offset16--; + char lead = source[offset16]; + if (isLeadSurrogate(lead)) + return UCharacterProperty.getRawSupplementary(lead, single); + } + return single; // return unmatched surrogate + } + + /** + * Determines how many chars this char32 requires. + * If a validity check is required, use + * isLegal() on + * char32 before calling. + * @param char32 the input codepoint. + * @return 2 if is in supplementary space, otherwise 1. + * @stable ICU 2.1 + */ + public static int getCharCount(int char32) + { + if (char32 < SUPPLEMENTARY_MIN_VALUE) { + return 1; + } + return 2; + } + + /** + * Determines whether the code point is a surrogate. + * + * @param codePoint The input character. + * (In ICU 2.1-69 the type of this parameter was char.) + * @return true If the input code point is a surrogate. + * @stable ICU 70 + */ + public static boolean isSurrogate(int codePoint) { + return (codePoint & SURROGATE_BITMASK) == SURROGATE_BITS; + } + + /** + * Determines whether the code point is a trail surrogate. + * + * @param codePoint The input character. + * (In ICU 2.1-69 the type of this parameter was char.) + * @return true If the input code point is a trail surrogate. + * @stable ICU 70 + */ + public static boolean isTrailSurrogate(int codePoint) { + return (codePoint & TRAIL_SURROGATE_BITMASK) == TRAIL_SURROGATE_BITS; + } + + /** + * Determines whether the code point is a lead surrogate. + * + * @param codePoint The input character. + * (In ICU 2.1-69 the type of this parameter was char.) + * @return true If the input code point is a lead surrogate + * @stable ICU 70 + */ + public static boolean isLeadSurrogate(int codePoint) { + return (codePoint & LEAD_SURROGATE_BITMASK) == LEAD_SURROGATE_BITS; + } + + /** + * Returns the lead surrogate. + * If a validity check is required, use + * isLegal() + * on char32 before calling. + * @param char32 the input character. + * @return lead surrogate if the getCharCount(ch) is 2;
+ * and 0 otherwise (note: 0 is not a valid lead surrogate). + * @stable ICU 2.1 + */ + public static char getLeadSurrogate(int char32) + { + if (char32 >= SUPPLEMENTARY_MIN_VALUE) { + return (char)(LEAD_SURROGATE_OFFSET_ + + (char32 >> LEAD_SURROGATE_SHIFT_)); + } + + return 0; + } + + /** + * Returns the trail surrogate. + * If a validity check is required, use + * isLegal() + * on char32 before calling. + * @param char32 the input character. + * @return the trail surrogate if the getCharCount(ch) is 2;
otherwise + * the character itself + * @stable ICU 2.1 + */ + public static char getTrailSurrogate(int char32) + { + if (char32 >= SUPPLEMENTARY_MIN_VALUE) { + return (char)(TRAIL_SURROGATE_MIN_VALUE + + (char32 & TRAIL_SURROGATE_MASK_)); + } + + return (char) char32; + } + + /** + * Convenience method corresponding to String.valueOf(char). Returns a one + * or two char string containing the UTF-32 value in UTF16 format. If a + * validity check is required, use + * isLegal() + * on char32 before calling. + * @param char32 the input character. + * @return string value of char32 in UTF16 format + * @exception IllegalArgumentException thrown if char32 is a invalid + * codepoint. + * @stable ICU 2.1 + */ + public static String valueOf(int char32) + { + if (char32 < CODEPOINT_MIN_VALUE || char32 > CODEPOINT_MAX_VALUE) { + throw new IllegalArgumentException("Illegal codepoint"); + } + return toString(char32); + } + + /** + * Append a single UTF-32 value to the end of a StringBuffer. + * If a validity check is required, use + * isLegal() + * on char32 before calling. + * @param target the buffer to append to + * @param char32 value to append. + * @return the updated StringBuffer + * @exception IllegalArgumentException thrown when char32 does not lie + * within the range of the Unicode codepoints + * @stable ICU 2.1 + */ + public static StringBuffer append(StringBuffer target, int char32) + { + // Check for irregular values + if (char32 < CODEPOINT_MIN_VALUE || char32 > CODEPOINT_MAX_VALUE) { + throw new IllegalArgumentException("Illegal codepoint: " + Integer.toHexString(char32)); + } + + // Write the UTF-16 values + if (char32 >= SUPPLEMENTARY_MIN_VALUE) + { + target.append(getLeadSurrogate(char32)); + target.append(getTrailSurrogate(char32)); + } + else { + target.append((char) char32); + } + return target; + } + + /** + * Shifts offset16 by the argument number of codepoints within a subarray. + * @param source char array + * @param start position of the subarray to be performed on + * @param limit position of the subarray to be performed on + * @param offset16 UTF16 position to shift relative to start + * @param shift32 number of codepoints to shift + * @return new shifted offset16 relative to start + * @exception IndexOutOfBoundsException if the new offset16 is out of + * bounds with respect to the subarray or the subarray bounds + * are out of range. + * @stable ICU 2.1 + */ + public static int moveCodePointOffset(char source[], int start, int limit, + int offset16, int shift32) + { + int size = source.length; + int count; + char ch; + int result = offset16 + start; + if (start < 0 || limit < start) { + throw new StringIndexOutOfBoundsException(start); + } + if (limit > size) { + throw new StringIndexOutOfBoundsException(limit); + } + if (offset16 < 0 || result > limit) { + throw new StringIndexOutOfBoundsException(offset16); + } + if (shift32 > 0) { + if (shift32 + result > size) { + throw new StringIndexOutOfBoundsException(result); + } + count = shift32; + while (result < limit && count > 0) + { + ch = source[result]; + if (isLeadSurrogate(ch) && (result + 1 < limit) && + isTrailSurrogate(source[result + 1])) { + result++; + } + count--; + result++; + } + } else { + if (result + shift32 < start) { + throw new StringIndexOutOfBoundsException(result); + } + for (count = -shift32; count > 0; count--) { + result--; + if (result < start) { + break; + } + ch = source[result]; + if (isTrailSurrogate(ch) && result > start && isLeadSurrogate(source[result - 1])) { + result--; + } + } + } + if (count != 0) { + throw new StringIndexOutOfBoundsException(shift32); + } + result -= start; + return result; + } + + // private data members ------------------------------------------------- + + /** + * Shift value for lead surrogate to form a supplementary character. + */ + private static final int LEAD_SURROGATE_SHIFT_ = 10; + + /** + * Mask to retrieve the significant value from a trail surrogate. + */ + private static final int TRAIL_SURROGATE_MASK_ = 0x3FF; + + /** + * Value that all lead surrogate starts with + */ + private static final int LEAD_SURROGATE_OFFSET_ = + LEAD_SURROGATE_MIN_VALUE - + (SUPPLEMENTARY_MIN_VALUE + >> LEAD_SURROGATE_SHIFT_); + + // private methods ------------------------------------------------------ + + /** + *

Converts argument code point and returns a String object representing + * the code point's value in UTF16 format. + *

This method does not check for the validity of the codepoint, the + * results are not guaranteed if a invalid codepoint is passed as + * argument. + *

The result is a string whose length is 1 for non-supplementary code + * points, 2 otherwise. + * @param ch code point + * @return string representation of the code point + */ + private static String toString(int ch) + { + if (ch < SUPPLEMENTARY_MIN_VALUE) { + return String.valueOf((char) ch); + } + + StringBuilder result = new StringBuilder(); + result.append(getLeadSurrogate(ch)); + result.append(getTrailSurrogate(ch)); + return result.toString(); + } +} diff --git a/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$Filter.class b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$Filter.class new file mode 100644 index 00000000..a420eda4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$Filter.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$SpanCondition.class b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$SpanCondition.class new file mode 100644 index 00000000..601bf4c0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$SpanCondition.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$VersionFilter.class b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$VersionFilter.class new file mode 100644 index 00000000..0e1919f2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet$VersionFilter.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/UnicodeSet.class b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet.class new file mode 100644 index 00000000..c6f34d15 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet.class differ diff --git a/tests/test_data/std/jdk/internal/icu/text/UnicodeSet.java b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet.java new file mode 100644 index 00000000..6f5919e0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/text/UnicodeSet.java @@ -0,0 +1,1412 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 1996-2015, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ +package jdk.internal.icu.text; + +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.TreeSet; + +import jdk.internal.icu.impl.BMPSet; +import jdk.internal.icu.impl.UCharacterProperty; +import jdk.internal.icu.impl.UnicodeSetStringSpan; +import jdk.internal.icu.impl.Utility; +import jdk.internal.icu.lang.UCharacter; +import jdk.internal.icu.util.OutputInt; +import jdk.internal.icu.util.VersionInfo; + +/** + * A mutable set of Unicode characters and multicharacter strings. + * Objects of this class represent character classes used + * in regular expressions. A character specifies a subset of Unicode + * code points. Legal code points are U+0000 to U+10FFFF, inclusive. + * + * Note: method freeze() will not only make the set immutable, but + * also makes important methods much higher performance: + * contains(c), containsNone(...), span(...), spanBack(...) etc. + * After the object is frozen, any subsequent call that wants to change + * the object will throw UnsupportedOperationException. + * + *

The UnicodeSet class is not designed to be subclassed. + * + *

UnicodeSet supports two APIs. The first is the + * operand API that allows the caller to modify the value of + * a UnicodeSet object. It conforms to Java 2's + * java.util.Set interface, although + * UnicodeSet does not actually implement that + * interface. All methods of Set are supported, with the + * modification that they take a character range or single character + * instead of an Object, and they take a + * UnicodeSet instead of a Collection. The + * operand API may be thought of in terms of boolean logic: a boolean + * OR is implemented by add, a boolean AND is implemented + * by retain, a boolean XOR is implemented by + * complement taking an argument, and a boolean NOT is + * implemented by complement with no argument. In terms + * of traditional set theory function names, add is a + * union, retain is an intersection, remove + * is an asymmetric difference, and complement with no + * argument is a set complement with respect to the superset range + * MIN_VALUE-MAX_VALUE + * + *

The second API is the + * applyPattern()/toPattern() API from the + * java.text.Format-derived classes. Unlike the + * methods that add characters, add categories, and control the logic + * of the set, the method applyPattern() sets all + * attributes of a UnicodeSet at once, based on a + * string pattern. + * + *

Pattern syntax

+ * + * Patterns are accepted by the constructors and the + * applyPattern() methods and returned by the + * toPattern() method. These patterns follow a syntax + * similar to that employed by version 8 regular expression character + * classes. Here are some simple examples: + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
[]No characters
[a]The character 'a'
[ae]The characters 'a' and 'e'
[a-e]The characters 'a' through 'e' inclusive, in Unicode code + * point order
[\\u4E01]The character U+4E01
[a{ab}{ac}]The character 'a' and the multicharacter strings "ab" and + * "ac"
[\p{Lu}]All characters in the general category Uppercase Letter
+ *
+ * + * Any character may be preceded by a backslash in order to remove any special + * meaning. White space characters, as defined by the Unicode Pattern_White_Space property, are + * ignored, unless they are escaped. + * + *

Property patterns specify a set of characters having a certain + * property as defined by the Unicode standard. Both the POSIX-like + * "[:Lu:]" and the Perl-like syntax "\p{Lu}" are recognized. For a + * complete list of supported property patterns, see the User's Guide + * for UnicodeSet at + * + * https://unicode-org.github.io/icu/userguide/strings/unicodeset. + * Actual determination of property data is defined by the underlying + * Unicode database as implemented by UCharacter. + * + *

Patterns specify individual characters, ranges of characters, and + * Unicode property sets. When elements are concatenated, they + * specify their union. To complement a set, place a '^' immediately + * after the opening '['. Property patterns are inverted by modifying + * their delimiters; "[:^foo]" and "\P{foo}". In any other location, + * '^' has no special meaning. + * + *

Since ICU 70, "[^...]", "[:^foo]", "\P{foo}", and "[:binaryProperty=No:]" + * perform a "code point complement" (all code points minus the original set), + * removing all multicharacter strings, + * equivalent to .{@link #complement()}.{@link #removeAllStrings()} . + * The {@link #complement()} API function continues to perform a + * symmetric difference with all code points and thus retains all multicharacter strings. + * + *

Ranges are indicated by placing two a '-' between two + * characters, as in "a-z". This specifies the range of all + * characters from the left to the right, in Unicode order. If the + * left character is greater than or equal to the + * right character it is a syntax error. If a '-' occurs as the first + * character after the opening '[' or '[^', or if it occurs as the + * last character before the closing ']', then it is taken as a + * literal. Thus "[a\\-b]", "[-ab]", and "[ab-]" all indicate the same + * set of three characters, 'a', 'b', and '-'. + * + *

Sets may be intersected using the {@literal '&'} operator or the asymmetric + * set difference may be taken using the '-' operator, for example, + * "{@code [[:L:]&[\\u0000-\\u0FFF]]}" indicates the set of all Unicode letters + * with values less than 4096. Operators ({@literal '&'} and '|') have equal + * precedence and bind left-to-right. Thus + * "[[:L:]-[a-z]-[\\u0100-\\u01FF]]" is equivalent to + * "[[[:L:]-[a-z]]-[\\u0100-\\u01FF]]". This only really matters for + * difference; intersection is commutative. + * + * + *
[a]The set containing 'a' + *
[a-z]The set containing 'a' + * through 'z' and all letters in between, in Unicode order + *
[^a-z]The set containing + * all characters but 'a' through 'z', + * that is, U+0000 through 'a'-1 and 'z'+1 through U+10FFFF + *
[[pat1][pat2]] + * The union of sets specified by pat1 and pat2 + *
[[pat1]&[pat2]] + * The intersection of sets specified by pat1 and pat2 + *
[[pat1]-[pat2]] + * The asymmetric difference of sets specified by pat1 and + * pat2 + *
[:Lu:] or \p{Lu} + * The set of characters having the specified + * Unicode property; in + * this case, Unicode uppercase letters + *
[:^Lu:] or \P{Lu} + * The set of characters not having the given + * Unicode property + *
+ * + *

Formal syntax

+ * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
pattern :=  ('[' '^'? item* ']') | + * property
item :=  char | (char '-' char) | pattern-expr
+ *
pattern-expr :=  pattern | pattern-expr pattern | + * pattern-expr op pattern
+ *
op :=  '&' | '-'
+ *
special :=  '[' | ']' | '-'
+ *
char :=  any character that is not special
+ * | ('\\'
any character)
+ * | ('\u' hex hex hex hex)
+ *
hex :=  '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
+ *     'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
property :=  a Unicode property set pattern
+ *
+ * + * + * + * + *
Legend: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
a := b  a may be replaced by b
a?zero or one instance of a
+ *
a*one or more instances of a
+ *
a | beither a or b
+ *
'a'the literal string between the quotes
+ *
+ *
+ *

To iterate over contents of UnicodeSet, the following are available: + *

  • {@link #ranges()} to iterate through the ranges
  • + *
  • {@link #strings()} to iterate through the strings
  • + *
  • {@link #iterator()} to iterate through the entire contents in a single loop. + * That method is, however, not particularly efficient, since it "boxes" each code point into a String. + *
+ * All of the above can be used in for loops. + * The {@link com.ibm.icu.text.UnicodeSetIterator UnicodeSetIterator} can also be used, but not in for loops. + *

To replace, count elements, or delete spans, see {@link com.ibm.icu.text.UnicodeSetSpanner UnicodeSetSpanner}. + * + * @author Alan Liu + * @stable ICU 2.0 + */ +public class UnicodeSet { + + private static final int LOW = 0x000000; // LOW <= all valid values. ZERO for codepoints + private static final int HIGH = 0x110000; // HIGH > all valid values. 10000 for code units. + // 110000 for codepoints + + /** + * Minimum value that can be stored in a UnicodeSet. + * @stable ICU 2.0 + */ + public static final int MIN_VALUE = LOW; + + /** + * Maximum value that can be stored in a UnicodeSet. + * @stable ICU 2.0 + */ + public static final int MAX_VALUE = HIGH - 1; + + private int len; // length used; list may be longer to minimize reallocs + private int[] list; // MUST be terminated with HIGH + private int[] rangeList; // internal buffer + private int[] buffer; // internal buffer + + // NOTE: normally the field should be of type SortedSet; but that is missing a public clone!! + // is not private so that UnicodeSetIterator can get access + TreeSet strings = new TreeSet(); + + /** + * The pattern representation of this set. This may not be the + * most economical pattern. It is the pattern supplied to + * applyPattern(), with variables substituted and whitespace + * removed. For sets constructed without applyPattern(), or + * modified using the non-pattern API, this string will be null, + * indicating that toPattern() must generate a pattern + * representation from the inversion list. + */ + + private static final int START_EXTRA = 16; // initial storage. Must be >= 0 + private static final int GROW_EXTRA = START_EXTRA; // extra amount for growth. Must be >= 0 + + private static UnicodeSet INCLUSION = null; + + private volatile BMPSet bmpSet; // The set is frozen if bmpSet or stringSpan is not null. + private volatile UnicodeSetStringSpan stringSpan; + + //---------------------------------------------------------------- + // Public API + //---------------------------------------------------------------- + + /** + * Constructs an empty set. + * @stable ICU 2.0 + */ + private UnicodeSet() { + list = new int[1 + START_EXTRA]; + list[len++] = HIGH; + } + + /** + * Constructs a copy of an existing set. + * @stable ICU 2.0 + */ + private UnicodeSet(UnicodeSet other) { + set(other); + } + + /** + * Constructs a set containing the given range. If end > + * start then an empty set is created. + * + * @param start first character, inclusive, of range + * @param end last character, inclusive, of range + * @stable ICU 2.0 + */ + public UnicodeSet(int start, int end) { + this(); + complement(start, end); + } + + /** + * Constructs a set from the given pattern. See the class description + * for the syntax of the pattern language. Whitespace is ignored. + * @param pattern a string specifying what characters are in the set + * @exception java.lang.IllegalArgumentException if the pattern contains + * a syntax error. + * @stable ICU 2.0 + */ + public UnicodeSet(String pattern) { + this(); + applyPattern(pattern, null); + } + + /** + * Make this object represent the same set as other. + * @param other a UnicodeSet whose value will be + * copied to this object + * @stable ICU 2.0 + */ + public UnicodeSet set(UnicodeSet other) { + checkFrozen(); + list = other.list.clone(); + len = other.len; + strings = new TreeSet(other.strings); + return this; + } + + /** + * Returns the number of elements in this set (its cardinality) + * Note than the elements of a set may include both individual + * codepoints and strings. + * + * @return the number of elements in this set (its cardinality). + * @stable ICU 2.0 + */ + public int size() { + int n = 0; + int count = getRangeCount(); + for (int i = 0; i < count; ++i) { + n += getRangeEnd(i) - getRangeStart(i) + 1; + } + return n + strings.size(); + } + + // for internal use, after checkFrozen has been called + private UnicodeSet add_unchecked(int start, int end) { + if (start < MIN_VALUE || start > MAX_VALUE) { + throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6)); + } + if (end < MIN_VALUE || end > MAX_VALUE) { + throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6)); + } + if (start < end) { + add(range(start, end), 2, 0); + } else if (start == end) { + add(start); + } + return this; + } + + /** + * Adds the specified character to this set if it is not already + * present. If this set already contains the specified character, + * the call leaves this set unchanged. + * @stable ICU 2.0 + */ + public final UnicodeSet add(int c) { + checkFrozen(); + return add_unchecked(c); + } + + // for internal use only, after checkFrozen has been called + private final UnicodeSet add_unchecked(int c) { + if (c < MIN_VALUE || c > MAX_VALUE) { + throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(c, 6)); + } + + // find smallest i such that c < list[i] + // if odd, then it is IN the set + // if even, then it is OUT of the set + int i = findCodePoint(c); + + // already in set? + if ((i & 1) != 0) return this; + + // HIGH is 0x110000 + // assert(list[len-1] == HIGH); + + // empty = [HIGH] + // [start_0, limit_0, start_1, limit_1, HIGH] + + // [..., start_k-1, limit_k-1, start_k, limit_k, ..., HIGH] + // ^ + // list[i] + + // i == 0 means c is before the first range + + if (c == list[i]-1) { + // c is before start of next range + list[i] = c; + // if we touched the HIGH mark, then add a new one + if (c == MAX_VALUE) { + ensureCapacity(len+1); + list[len++] = HIGH; + } + if (i > 0 && c == list[i-1]) { + // collapse adjacent ranges + + // [..., start_k-1, c, c, limit_k, ..., HIGH] + // ^ + // list[i] + System.arraycopy(list, i+1, list, i-1, len-i-1); + len -= 2; + } + } + + else if (i > 0 && c == list[i-1]) { + // c is after end of prior range + list[i-1]++; + // no need to check for collapse here + } + + else { + // At this point we know the new char is not adjacent to + // any existing ranges, and it is not 10FFFF. + + + // [..., start_k-1, limit_k-1, start_k, limit_k, ..., HIGH] + // ^ + // list[i] + + // [..., start_k-1, limit_k-1, c, c+1, start_k, limit_k, ..., HIGH] + // ^ + // list[i] + + // Don't use ensureCapacity() to save on copying. + // NOTE: This has no measurable impact on performance, + // but it might help in some usage patterns. + if (len+2 > list.length) { + int[] temp = new int[len + 2 + GROW_EXTRA]; + if (i != 0) System.arraycopy(list, 0, temp, 0, i); + System.arraycopy(list, i, temp, i+2, len-i); + list = temp; + } else { + System.arraycopy(list, i, list, i+2, len-i); + } + + list[i] = c; + list[i+1] = c+1; + len += 2; + } + + return this; + } + + /** + * Adds the specified multicharacter to this set if it is not already + * present. If this set already contains the multicharacter, + * the call leaves this set unchanged. + * Thus {@code "ch" => {"ch"}} + * @param s the source string + * @return this object, for chaining + * @stable ICU 2.0 + */ + public final UnicodeSet add(CharSequence s) { + checkFrozen(); + int cp = getSingleCP(s); + if (cp < 0) { + strings.add(s.toString()); + } else { + add_unchecked(cp, cp); + } + return this; + } + + /** + * Utility for getting code point from single code point CharSequence. + * See the public UTF16.getSingleCodePoint() (which returns -1 for null rather than throwing NPE). + * + * @return a code point IF the string consists of a single one. + * otherwise returns -1. + * @param s to test + */ + private static int getSingleCP(CharSequence s) { + if (s.length() == 1) return s.charAt(0); + if (s.length() == 2) { + int cp = Character.codePointAt(s, 0); + if (cp > 0xFFFF) { // is surrogate pair + return cp; + } + } + return -1; + } + + /** + * Complements the specified range in this set. Any character in + * the range will be removed if it is in this set, or will be + * added if it is not in this set. If start > end + * then an empty range is complemented, leaving the set unchanged. + * + * @param start first character, inclusive, of range + * @param end last character, inclusive, of range + * @stable ICU 2.0 + */ + public UnicodeSet complement(int start, int end) { + checkFrozen(); + if (start < MIN_VALUE || start > MAX_VALUE) { + throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(start, 6)); + } + if (end < MIN_VALUE || end > MAX_VALUE) { + throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(end, 6)); + } + if (start <= end) { + xor(range(start, end), 2, 0); + } + return this; + } + + /** + * Returns true if this set contains the given character. + * @param c character to be checked for containment + * @return true if the test condition is met + * @stable ICU 2.0 + */ + public boolean contains(int c) { + if (c < MIN_VALUE || c > MAX_VALUE) { + throw new IllegalArgumentException("Invalid code point U+" + Utility.hex(c, 6)); + } + if (bmpSet != null) { + return bmpSet.contains(c); + } + if (stringSpan != null) { + return stringSpan.contains(c); + } + + /* + // Set i to the index of the start item greater than ch + // We know we will terminate without length test! + int i = -1; + while (true) { + if (c < list[++i]) break; + } + */ + + int i = findCodePoint(c); + + return ((i & 1) != 0); // return true if odd + } + + /** + * Returns the smallest value i such that c < list[i]. Caller + * must ensure that c is a legal value or this method will enter + * an infinite loop. This method performs a binary search. + * @param c a character in the range MIN_VALUE..MAX_VALUE + * inclusive + * @return the smallest integer i in the range 0..len-1, + * inclusive, such that c < list[i] + */ + private final int findCodePoint(int c) { + /* Examples: + findCodePoint(c) + set list[] c=0 1 3 4 7 8 + === ============== =========== + [] [110000] 0 0 0 0 0 0 + [\u0000-\u0003] [0, 4, 110000] 1 1 1 2 2 2 + [\u0004-\u0007] [4, 8, 110000] 0 0 0 1 1 2 + [:all:] [0, 110000] 1 1 1 1 1 1 + */ + + // Return the smallest i such that c < list[i]. Assume + // list[len - 1] == HIGH and that c is legal (0..HIGH-1). + if (c < list[0]) return 0; + // High runner test. c is often after the last range, so an + // initial check for this condition pays off. + if (len >= 2 && c >= list[len-2]) return len-1; + int lo = 0; + int hi = len - 1; + // invariant: c >= list[lo] + // invariant: c < list[hi] + for (;;) { + int i = (lo + hi) >>> 1; + if (i == lo) return hi; + if (c < list[i]) { + hi = i; + } else { + lo = i; + } + } + } + + /** + * Retains only the elements in this set that are contained in the + * specified set. In other words, removes from this set all of + * its elements that are not contained in the specified set. This + * operation effectively modifies this set so that its value is + * the intersection of the two sets. + * + * @param c set that defines which elements this set will retain. + * @stable ICU 2.0 + */ + public UnicodeSet retainAll(UnicodeSet c) { + checkFrozen(); + retain(c.list, c.len, 0); + strings.retainAll(c.strings); + return this; + } + + /** + * Removes all of the elements from this set. This set will be + * empty after this call returns. + * @stable ICU 2.0 + */ + public UnicodeSet clear() { + checkFrozen(); + list[0] = HIGH; + len = 1; + strings.clear(); + return this; + } + + /** + * Iteration method that returns the number of ranges contained in + * this set. + * @see #getRangeStart + * @see #getRangeEnd + * @stable ICU 2.0 + */ + public int getRangeCount() { + return len/2; + } + + /** + * Iteration method that returns the first character in the + * specified range of this set. + * @exception ArrayIndexOutOfBoundsException if index is outside + * the range 0..getRangeCount()-1 + * @see #getRangeCount + * @see #getRangeEnd + * @stable ICU 2.0 + */ + public int getRangeStart(int index) { + return list[index*2]; + } + + /** + * Iteration method that returns the last character in the + * specified range of this set. + * @exception ArrayIndexOutOfBoundsException if index is outside + * the range 0..getRangeCount()-1 + * @see #getRangeStart + * @see #getRangeEnd + * @stable ICU 2.0 + */ + public int getRangeEnd(int index) { + return (list[index*2 + 1] - 1); + } + + //---------------------------------------------------------------- + // Implementation: Pattern parsing + //---------------------------------------------------------------- + + /** + * Parses the given pattern, starting at the given position. The character + * at pattern.charAt(pos.getIndex()) must be '[', or the parse fails. + * Parsing continues until the corresponding closing ']'. If a syntax error + * is encountered between the opening and closing brace, the parse fails. + * Upon return from a successful parse, the ParsePosition is updated to + * point to the character following the closing ']', and an inversion + * list for the parsed pattern is returned. This method + * calls itself recursively to parse embedded subpatterns. + * + * @param pattern the string containing the pattern to be parsed. The + * portion of the string from pos.getIndex(), which must be a '[', to the + * corresponding closing ']', is parsed. + * @param pos upon entry, the position at which to being parsing. The + * character at pattern.charAt(pos.getIndex()) must be a '['. Upon return + * from a successful parse, pos.getIndex() is either the character after the + * closing ']' of the parsed pattern, or pattern.length() if the closing ']' + * is the last character of the pattern string. + * @return an inversion list for the parsed substring + * of pattern + * @exception java.lang.IllegalArgumentException if the parse fails. + */ + private UnicodeSet applyPattern(String pattern, + ParsePosition pos) { + if ("[:age=3.2:]".equals(pattern)) { + checkFrozen(); + VersionInfo version = VersionInfo.getInstance("3.2"); + applyFilter(new VersionFilter(version), UCharacterProperty.SRC_PROPSVEC); + } else { + throw new IllegalStateException("UnicodeSet.applyPattern(unexpected pattern " + + pattern + ")"); + } + + return this; + } + + //---------------------------------------------------------------- + // Implementation: Utility methods + //---------------------------------------------------------------- + + private void ensureCapacity(int newLen) { + if (newLen <= list.length) return; + int[] temp = new int[newLen + GROW_EXTRA]; + System.arraycopy(list, 0, temp, 0, len); + list = temp; + } + + private void ensureBufferCapacity(int newLen) { + if (buffer != null && newLen <= buffer.length) return; + buffer = new int[newLen + GROW_EXTRA]; + } + + /** + * Assumes start <= end. + */ + private int[] range(int start, int end) { + if (rangeList == null) { + rangeList = new int[] { start, end+1, HIGH }; + } else { + rangeList[0] = start; + rangeList[1] = end+1; + } + return rangeList; + } + + //---------------------------------------------------------------- + // Implementation: Fundamental operations + //---------------------------------------------------------------- + + // polarity = 0, 3 is normal: x xor y + // polarity = 1, 2: x xor ~y == x === y + + private UnicodeSet xor(int[] other, int otherLen, int polarity) { + ensureBufferCapacity(len + otherLen); + int i = 0, j = 0, k = 0; + int a = list[i++]; + int b; + if (polarity == 1 || polarity == 2) { + b = LOW; + if (other[j] == LOW) { // skip base if already LOW + ++j; + b = other[j]; + } + } else { + b = other[j++]; + } + // simplest of all the routines + // sort the values, discarding identicals! + while (true) { + if (a < b) { + buffer[k++] = a; + a = list[i++]; + } else if (b < a) { + buffer[k++] = b; + b = other[j++]; + } else if (a != HIGH) { // at this point, a == b + // discard both values! + a = list[i++]; + b = other[j++]; + } else { // DONE! + buffer[k++] = HIGH; + len = k; + break; + } + } + // swap list and buffer + int[] temp = list; + list = buffer; + buffer = temp; + return this; + } + + // polarity = 0 is normal: x union y + // polarity = 2: x union ~y + // polarity = 1: ~x union y + // polarity = 3: ~x union ~y + + private UnicodeSet add(int[] other, int otherLen, int polarity) { + ensureBufferCapacity(len + otherLen); + int i = 0, j = 0, k = 0; + int a = list[i++]; + int b = other[j++]; + // change from xor is that we have to check overlapping pairs + // polarity bit 1 means a is second, bit 2 means b is. + main: + while (true) { + switch (polarity) { + case 0: // both first; take lower if unequal + if (a < b) { // take a + // Back up over overlapping ranges in buffer[] + if (k > 0 && a <= buffer[k-1]) { + // Pick latter end value in buffer[] vs. list[] + a = max(list[i], buffer[--k]); + } else { + // No overlap + buffer[k++] = a; + a = list[i]; + } + i++; // Common if/else code factored out + polarity ^= 1; + } else if (b < a) { // take b + if (k > 0 && b <= buffer[k-1]) { + b = max(other[j], buffer[--k]); + } else { + buffer[k++] = b; + b = other[j]; + } + j++; + polarity ^= 2; + } else { // a == b, take a, drop b + if (a == HIGH) break main; + // This is symmetrical; it doesn't matter if + // we backtrack with a or b. - liu + if (k > 0 && a <= buffer[k-1]) { + a = max(list[i], buffer[--k]); + } else { + // No overlap + buffer[k++] = a; + a = list[i]; + } + i++; + polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + case 3: // both second; take higher if unequal, and drop other + if (b <= a) { // take a + if (a == HIGH) break main; + buffer[k++] = a; + } else { // take b + if (b == HIGH) break main; + buffer[k++] = b; + } + a = list[i++]; polarity ^= 1; // factored common code + b = other[j++]; polarity ^= 2; + break; + case 1: // a second, b first; if b < a, overlap + if (a < b) { // no overlap, take a + buffer[k++] = a; a = list[i++]; polarity ^= 1; + } else if (b < a) { // OVERLAP, drop b + b = other[j++]; polarity ^= 2; + } else { // a == b, drop both! + if (a == HIGH) break main; + a = list[i++]; polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + case 2: // a first, b second; if a < b, overlap + if (b < a) { // no overlap, take b + buffer[k++] = b; b = other[j++]; polarity ^= 2; + } else if (a < b) { // OVERLAP, drop a + a = list[i++]; polarity ^= 1; + } else { // a == b, drop both! + if (a == HIGH) break main; + a = list[i++]; polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + } + } + buffer[k++] = HIGH; // terminate + len = k; + // swap list and buffer + int[] temp = list; + list = buffer; + buffer = temp; + return this; + } + + // polarity = 0 is normal: x intersect y + // polarity = 2: x intersect ~y == set-minus + // polarity = 1: ~x intersect y + // polarity = 3: ~x intersect ~y + + private UnicodeSet retain(int[] other, int otherLen, int polarity) { + ensureBufferCapacity(len + otherLen); + int i = 0, j = 0, k = 0; + int a = list[i++]; + int b = other[j++]; + // change from xor is that we have to check overlapping pairs + // polarity bit 1 means a is second, bit 2 means b is. + main: + while (true) { + switch (polarity) { + case 0: // both first; drop the smaller + if (a < b) { // drop a + a = list[i++]; polarity ^= 1; + } else if (b < a) { // drop b + b = other[j++]; polarity ^= 2; + } else { // a == b, take one, drop other + if (a == HIGH) break main; + buffer[k++] = a; a = list[i++]; polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + case 3: // both second; take lower if unequal + if (a < b) { // take a + buffer[k++] = a; a = list[i++]; polarity ^= 1; + } else if (b < a) { // take b + buffer[k++] = b; b = other[j++]; polarity ^= 2; + } else { // a == b, take one, drop other + if (a == HIGH) break main; + buffer[k++] = a; a = list[i++]; polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + case 1: // a second, b first; + if (a < b) { // NO OVERLAP, drop a + a = list[i++]; polarity ^= 1; + } else if (b < a) { // OVERLAP, take b + buffer[k++] = b; b = other[j++]; polarity ^= 2; + } else { // a == b, drop both! + if (a == HIGH) break main; + a = list[i++]; polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + case 2: // a first, b second; if a < b, overlap + if (b < a) { // no overlap, drop b + b = other[j++]; polarity ^= 2; + } else if (a < b) { // OVERLAP, take a + buffer[k++] = a; a = list[i++]; polarity ^= 1; + } else { // a == b, drop both! + if (a == HIGH) break main; + a = list[i++]; polarity ^= 1; + b = other[j++]; polarity ^= 2; + } + break; + } + } + buffer[k++] = HIGH; // terminate + len = k; + // swap list and buffer + int[] temp = list; + list = buffer; + buffer = temp; + return this; + } + + private static final int max(int a, int b) { + return (a > b) ? a : b; + } + + //---------------------------------------------------------------- + // Generic filter-based scanning code + //---------------------------------------------------------------- + + private static interface Filter { + boolean contains(int codePoint); + } + + private static final VersionInfo NO_VERSION = VersionInfo.getInstance(0, 0, 0, 0); + + private static class VersionFilter implements Filter { + VersionInfo version; + VersionFilter(VersionInfo version) { this.version = version; } + public boolean contains(int ch) { + VersionInfo v = UCharacter.getAge(ch); + // Reference comparison ok; VersionInfo caches and reuses + // unique objects. + return v != NO_VERSION && + v.compareTo(version) <= 0; + } + } + + private static synchronized UnicodeSet getInclusions(int src) { + if (src != UCharacterProperty.SRC_PROPSVEC) { + throw new IllegalStateException("UnicodeSet.getInclusions(unknown src "+src+")"); + } + + if (INCLUSION == null) { + UnicodeSet incl = new UnicodeSet(); + UCharacterProperty.INSTANCE.upropsvec_addPropertyStarts(incl); + INCLUSION = incl; + } + return INCLUSION; + } + + /** + * Generic filter-based scanning code for UCD property UnicodeSets. + */ + private UnicodeSet applyFilter(Filter filter, int src) { + // Logically, walk through all Unicode characters, noting the start + // and end of each range for which filter.contain(c) is + // true. Add each range to a set. + // + // To improve performance, use an inclusions set which + // encodes information about character ranges that are known + // to have identical properties. + // getInclusions(src) contains exactly the first characters of + // same-value ranges for the given properties "source". + + clear(); + + int startHasProperty = -1; + UnicodeSet inclusions = getInclusions(src); + int limitRange = inclusions.getRangeCount(); + + for (int j=0; j= 0) { + add_unchecked(startHasProperty, ch-1); + startHasProperty = -1; + } + } + } + if (startHasProperty >= 0) { + add_unchecked(startHasProperty, 0x10FFFF); + } + + return this; + } + + /** + * Is this frozen, according to the Freezable interface? + * + * @return value + * @stable ICU 3.8 + */ + public boolean isFrozen() { + return (bmpSet != null || stringSpan != null); + } + + /** + * Freeze this class, according to the Freezable interface. + * + * @return this + * @stable ICU 4.4 + */ + public UnicodeSet freeze() { + if (!isFrozen()) { + // Do most of what compact() does before freezing because + // compact() will not work when the set is frozen. + // Small modification: Don't shrink if the savings would be tiny (<=GROW_EXTRA). + + // Delete buffer first to defragment memory less. + buffer = null; + if (list.length > (len + GROW_EXTRA)) { + // Make the capacity equal to len or 1. + // We don't want to realloc of 0 size. + int capacity = (len == 0) ? 1 : len; + int[] oldList = list; + list = new int[capacity]; + for (int i = capacity; i-- > 0;) { + list[i] = oldList[i]; + } + } + + // Optimize contains() and span() and similar functions. + if (!strings.isEmpty()) { + stringSpan = new UnicodeSetStringSpan(this, new ArrayList(strings), UnicodeSetStringSpan.ALL); + } + if (stringSpan == null || !stringSpan.needsStringSpanUTF16()) { + // Optimize for code point spans. + // There are no strings, or + // all strings are irrelevant for span() etc. because + // all of each string's code points are contained in this set. + // However, fully contained strings are relevant for spanAndCount(), + // so we create both objects. + bmpSet = new BMPSet(list, len); + } + } + return this; + } + + /** + * Span a string using this UnicodeSet. + *

To replace, count elements, or delete spans, see {@link com.ibm.icu.text.UnicodeSetSpanner UnicodeSetSpanner}. + * @param s The string to be spanned + * @param spanCondition The span condition + * @return the length of the span + * @stable ICU 4.4 + */ + public int span(CharSequence s, SpanCondition spanCondition) { + return span(s, 0, spanCondition); + } + + /** + * Span a string using this UnicodeSet. + * If the start index is less than 0, span will start from 0. + * If the start index is greater than the string length, span returns the string length. + *

To replace, count elements, or delete spans, see {@link com.ibm.icu.text.UnicodeSetSpanner UnicodeSetSpanner}. + * @param s The string to be spanned + * @param start The start index that the span begins + * @param spanCondition The span condition + * @return the string index which ends the span (i.e. exclusive) + * @stable ICU 4.4 + */ + public int span(CharSequence s, int start, SpanCondition spanCondition) { + int end = s.length(); + if (start < 0) { + start = 0; + } else if (start >= end) { + return end; + } + if (bmpSet != null) { + // Frozen set without strings, or no string is relevant for span(). + return bmpSet.span(s, start, spanCondition, null); + } + if (stringSpan != null) { + return stringSpan.span(s, start, spanCondition); + } else if (!strings.isEmpty()) { + int which = spanCondition == SpanCondition.NOT_CONTAINED ? UnicodeSetStringSpan.FWD_UTF16_NOT_CONTAINED + : UnicodeSetStringSpan.FWD_UTF16_CONTAINED; + UnicodeSetStringSpan strSpan = new UnicodeSetStringSpan(this, new ArrayList(strings), which); + if (strSpan.needsStringSpanUTF16()) { + return strSpan.span(s, start, spanCondition); + } + } + + return spanCodePointsAndCount(s, start, spanCondition, null); + } + + /** + * Same as span() but also counts the smallest number of set elements on any path across the span. + *

To replace, count elements, or delete spans, see {@link com.ibm.icu.text.UnicodeSetSpanner UnicodeSetSpanner}. + * @param outCount An output-only object (must not be null) for returning the count. + * @return the limit (exclusive end) of the span + */ + public int spanAndCount(CharSequence s, int start, SpanCondition spanCondition, OutputInt outCount) { + if (outCount == null) { + throw new IllegalArgumentException("outCount must not be null"); + } + int end = s.length(); + if (start < 0) { + start = 0; + } else if (start >= end) { + return end; + } + if (stringSpan != null) { + // We might also have bmpSet != null, + // but fully-contained strings are relevant for counting elements. + return stringSpan.spanAndCount(s, start, spanCondition, outCount); + } else if (bmpSet != null) { + return bmpSet.span(s, start, spanCondition, outCount); + } else if (!strings.isEmpty()) { + int which = spanCondition == SpanCondition.NOT_CONTAINED ? UnicodeSetStringSpan.FWD_UTF16_NOT_CONTAINED + : UnicodeSetStringSpan.FWD_UTF16_CONTAINED; + which |= UnicodeSetStringSpan.WITH_COUNT; + UnicodeSetStringSpan strSpan = new UnicodeSetStringSpan(this, new ArrayList(strings), which); + return strSpan.spanAndCount(s, start, spanCondition, outCount); + } + + return spanCodePointsAndCount(s, start, spanCondition, outCount); + } + + private int spanCodePointsAndCount(CharSequence s, int start, + SpanCondition spanCondition, OutputInt outCount) { + // Pin to 0/1 values. + boolean spanContained = (spanCondition != SpanCondition.NOT_CONTAINED); + + int c; + int next = start; + int length = s.length(); + int count = 0; + do { + c = Character.codePointAt(s, next); + if (spanContained != contains(c)) { + break; + } + ++count; + next += Character.charCount(c); + } while (next < length); + if (outCount != null) { outCount.value = count; } + return next; + } + + /** + * Span a string backwards (from the fromIndex) using this UnicodeSet. + * If the fromIndex is less than 0, spanBack will return 0. + * If fromIndex is greater than the string length, spanBack will start from the string length. + *

To replace, count elements, or delete spans, see {@link com.ibm.icu.text.UnicodeSetSpanner UnicodeSetSpanner}. + * @param s The string to be spanned + * @param fromIndex The index of the char (exclusive) that the string should be spanned backwards + * @param spanCondition The span condition + * @return The string index which starts the span (i.e. inclusive). + * @stable ICU 4.4 + */ + public int spanBack(CharSequence s, int fromIndex, SpanCondition spanCondition) { + if (fromIndex <= 0) { + return 0; + } + if (fromIndex > s.length()) { + fromIndex = s.length(); + } + if (bmpSet != null) { + // Frozen set without strings, or no string is relevant for spanBack(). + return bmpSet.spanBack(s, fromIndex, spanCondition); + } + if (stringSpan != null) { + return stringSpan.spanBack(s, fromIndex, spanCondition); + } else if (!strings.isEmpty()) { + int which = (spanCondition == SpanCondition.NOT_CONTAINED) + ? UnicodeSetStringSpan.BACK_UTF16_NOT_CONTAINED + : UnicodeSetStringSpan.BACK_UTF16_CONTAINED; + UnicodeSetStringSpan strSpan = new UnicodeSetStringSpan(this, new ArrayList(strings), which); + if (strSpan.needsStringSpanUTF16()) { + return strSpan.spanBack(s, fromIndex, spanCondition); + } + } + + // Pin to 0/1 values. + boolean spanContained = (spanCondition != SpanCondition.NOT_CONTAINED); + + int c; + int prev = fromIndex; + do { + c = Character.codePointBefore(s, prev); + if (spanContained != contains(c)) { + break; + } + prev -= Character.charCount(c); + } while (prev > 0); + return prev; + } + + /** + * Clone a thawed version of this class, according to the Freezable interface. + * @return the clone, not frozen + * @stable ICU 4.4 + */ + public UnicodeSet cloneAsThawed() { + UnicodeSet result = new UnicodeSet(this); + assert !result.isFrozen(); + return result; + } + + // internal function + private void checkFrozen() { + if (isFrozen()) { + throw new UnsupportedOperationException("Attempt to modify frozen object"); + } + } + + /** + * Argument values for whether span() and similar functions continue while the current character is contained vs. + * not contained in the set. + *

+ * The functionality is straightforward for sets with only single code points, without strings (which is the common + * case): + *

    + *
  • CONTAINED and SIMPLE work the same. + *
  • CONTAINED and SIMPLE are inverses of NOT_CONTAINED. + *
  • span() and spanBack() partition any string the + * same way when alternating between span(NOT_CONTAINED) and span(either "contained" condition). + *
  • Using a + * complemented (inverted) set and the opposite span conditions yields the same results. + *
+ * When a set contains multi-code point strings, then these statements may not be true, depending on the strings in + * the set (for example, whether they overlap with each other) and the string that is processed. For a set with + * strings: + *
    + *
  • The complement of the set contains the opposite set of code points, but the same set of strings. + * Therefore, complementing both the set and the span conditions may yield different results. + *
  • When starting spans + * at different positions in a string (span(s, ...) vs. span(s+1, ...)) the ends of the spans may be different + * because a set string may start before the later position. + *
  • span(SIMPLE) may be shorter than + * span(CONTAINED) because it will not recursively try all possible paths. For example, with a set which + * contains the three strings "xy", "xya" and "ax", span("xyax", CONTAINED) will return 4 but span("xyax", + * SIMPLE) will return 3. span(SIMPLE) will never be longer than span(CONTAINED). + *
  • With either "contained" condition, span() and spanBack() may partition a string in different ways. For example, + * with a set which contains the two strings "ab" and "ba", and when processing the string "aba", span() will yield + * contained/not-contained boundaries of { 0, 2, 3 } while spanBack() will yield boundaries of { 0, 1, 3 }. + *
+ * Note: If it is important to get the same boundaries whether iterating forward or backward through a string, then + * either only span() should be used and the boundaries cached for backward operation, or an ICU BreakIterator could + * be used. + *

+ * Note: Unpaired surrogates are treated like surrogate code points. Similarly, set strings match only on code point + * boundaries, never in the middle of a surrogate pair. + * + * @stable ICU 4.4 + */ + public enum SpanCondition { + /** + * Continues a span() while there is no set element at the current position. + * Increments by one code point at a time. + * Stops before the first set element (character or string). + * (For code points only, this is like while contains(current)==false). + *

+ * When span() returns, the substring between where it started and the position it returned consists only of + * characters that are not in the set, and none of its strings overlap with the span. + * + * @stable ICU 4.4 + */ + NOT_CONTAINED, + + /** + * Spans the longest substring that is a concatenation of set elements (characters or strings). + * (For characters only, this is like while contains(current)==true). + *

+ * When span() returns, the substring between where it started and the position it returned consists only of set + * elements (characters or strings) that are in the set. + *

+ * If a set contains strings, then the span will be the longest substring for which there + * exists at least one non-overlapping concatenation of set elements (characters or strings). + * This is equivalent to a POSIX regular expression for (OR of each set element)*. + * (Java/ICU/Perl regex stops at the first match of an OR.) + * + * @stable ICU 4.4 + */ + CONTAINED, + + /** + * Continues a span() while there is a set element at the current position. + * Increments by the longest matching element at each position. + * (For characters only, this is like while contains(current)==true). + *

+ * When span() returns, the substring between where it started and the position it returned consists only of set + * elements (characters or strings) that are in the set. + *

+ * If a set only contains single characters, then this is the same as CONTAINED. + *

+ * If a set contains strings, then the span will be the longest substring with a match at each position with the + * longest single set element (character or string). + *

+ * Use this span condition together with other longest-match algorithms, such as ICU converters + * (ucnv_getUnicodeSet()). + * + * @stable ICU 4.4 + */ + SIMPLE, + } + +} diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap$Range.class b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$Range.class new file mode 100644 index 00000000..12d55fd4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$Range.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap$RangeIterator.class b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$RangeIterator.class new file mode 100644 index 00000000..0aba5554 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$RangeIterator.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap$RangeOption.class b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$RangeOption.class new file mode 100644 index 00000000..507270d1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$RangeOption.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap$StringIterator.class b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$StringIterator.class new file mode 100644 index 00000000..607b0c60 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$StringIterator.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap$ValueFilter.class b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$ValueFilter.class new file mode 100644 index 00000000..6d7e8429 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointMap$ValueFilter.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap.class b/tests/test_data/std/jdk/internal/icu/util/CodePointMap.class new file mode 100644 index 00000000..bdec6e7b Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointMap.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointMap.java b/tests/test_data/std/jdk/internal/icu/util/CodePointMap.java new file mode 100644 index 00000000..d52ecd46 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/util/CodePointMap.java @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +// (c) 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License + +// created: 2018may10 Markus W. Scherer + +package jdk.internal.icu.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Abstract map from Unicode code points (U+0000..U+10FFFF) to integer values. + * This does not implement java.util.Map. + * + * @stable ICU 63 + */ +public abstract class CodePointMap implements Iterable { + /** + * Selectors for how getRange() should report value ranges overlapping with surrogates. + * Most users should use NORMAL. + * + * @see #getRange + * @stable ICU 63 + */ + public enum RangeOption { + /** + * getRange() enumerates all same-value ranges as stored in the map. + * Most users should use this option. + * + * @stable ICU 63 + */ + NORMAL, + /** + * getRange() enumerates all same-value ranges as stored in the map, + * except that lead surrogates (U+D800..U+DBFF) are treated as having the + * surrogateValue, which is passed to getRange() as a separate parameter. + * The surrogateValue is not transformed via filter(). + * See {@link Character#isHighSurrogate}. + * + *

Most users should use NORMAL instead. + * + *

This option is useful for maps that map surrogate code units to + * special values optimized for UTF-16 string processing + * or for special error behavior for unpaired surrogates, + * but those values are not to be associated with the lead surrogate code points. + * + * @stable ICU 63 + */ + FIXED_LEAD_SURROGATES, + /** + * getRange() enumerates all same-value ranges as stored in the map, + * except that all surrogates (U+D800..U+DFFF) are treated as having the + * surrogateValue, which is passed to getRange() as a separate parameter. + * The surrogateValue is not transformed via filter(). + * See {@link Character#isSurrogate}. + * + *

Most users should use NORMAL instead. + * + *

This option is useful for maps that map surrogate code units to + * special values optimized for UTF-16 string processing + * or for special error behavior for unpaired surrogates, + * but those values are not to be associated with the lead surrogate code points. + * + * @stable ICU 63 + */ + FIXED_ALL_SURROGATES + } + + /** + * Callback function interface: Modifies a map value. + * Optionally called by getRange(). + * The modified value will be returned by the getRange() function. + * + *

Can be used to ignore some of the value bits, + * make a filter for one of several values, + * return a value index computed from the map value, etc. + * + * @see #getRange + * @see #iterator + * @stable ICU 63 + */ + public interface ValueFilter { + /** + * Modifies the map value. + * + * @param value map value + * @return modified value + * @stable ICU 63 + */ + public int apply(int value); + } + + /** + * Range iteration result data. + * Code points from start to end map to the same value. + * The value may have been modified by {@link ValueFilter#apply(int)}, + * or it may be the surrogateValue if a RangeOption other than "normal" was used. + * + * @see #getRange + * @see #iterator + * @stable ICU 63 + */ + public static final class Range { + private int start; + private int end; + private int value; + + /** + * Constructor. Sets start and end to -1 and value to 0. + * + * @stable ICU 63 + */ + public Range() { + start = end = -1; + value = 0; + } + + /** + * @return the start code point + * @stable ICU 63 + */ + public int getStart() { return start; } + /** + * @return the (inclusive) end code point + * @stable ICU 63 + */ + public int getEnd() { return end; } + /** + * @return the range value + * @stable ICU 63 + */ + public int getValue() { return value; } + /** + * Sets the range. When using {@link #iterator()}, + * iteration will resume after the newly set end. + * + * @param start new start code point + * @param end new end code point + * @param value new value + * @stable ICU 63 + */ + public void set(int start, int end, int value) { + this.start = start; + this.end = end; + this.value = value; + } + } + + private final class RangeIterator implements Iterator { + private Range range = new Range(); + + @Override + public boolean hasNext() { + return -1 <= range.end && range.end < 0x10ffff; + } + + @Override + public Range next() { + if (getRange(range.end + 1, null, range)) { + return range; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public final void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * Iterates over code points of a string and fetches map values. + * This does not implement java.util.Iterator. + * + *

+     * void onString(CodePointMap map, CharSequence s, int start) {
+     *     CodePointMap.StringIterator iter = map.stringIterator(s, start);
+     *     while (iter.next()) {
+     *         int end = iter.getIndex();  // code point from between start and end
+     *         useValue(s, start, end, iter.getCodePoint(), iter.getValue());
+     *         start = end;
+     *     }
+     * }
+     * 
+ * + *

This class is not intended for public subclassing. + * + * @stable ICU 63 + */ + public class StringIterator { + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected CharSequence s; + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected int sIndex; + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected int c; + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected int value; + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected StringIterator(CharSequence s, int sIndex) { + this.s = s; + this.sIndex = sIndex; + c = -1; + value = 0; + } + + /** + * Resets the iterator to a new string and/or a new string index. + * + * @param s string to iterate over + * @param sIndex string index where the iteration will start + * @stable ICU 63 + */ + public void reset(CharSequence s, int sIndex) { + this.s = s; + this.sIndex = sIndex; + c = -1; + value = 0; + } + + /** + * Reads the next code point, post-increments the string index, + * and gets a value from the map. + * Sets an implementation-defined error value if the code point is an unpaired surrogate. + * + * @return true if the string index was not yet at the end of the string; + * otherwise the iterator did not advance + * @stable ICU 63 + */ + public boolean next() { + if (sIndex >= s.length()) { + return false; + } + c = Character.codePointAt(s, sIndex); + sIndex += Character.charCount(c); + value = get(c); + return true; + } + + /** + * Reads the previous code point, pre-decrements the string index, + * and gets a value from the map. + * Sets an implementation-defined error value if the code point is an unpaired surrogate. + * + * @return true if the string index was not yet at the start of the string; + * otherwise the iterator did not advance + * @stable ICU 63 + */ + public boolean previous() { + if (sIndex <= 0) { + return false; + } + c = Character.codePointBefore(s, sIndex); + sIndex -= Character.charCount(c); + value = get(c); + return true; + } + /** + * @return the string index + * @stable ICU 63 + */ + public final int getIndex() { return sIndex; } + /** + * @return the code point + * @stable ICU 63 + */ + public final int getCodePoint() { return c; } + /** + * @return the map value, + * or an implementation-defined error value if + * the code point is an unpaired surrogate + * @stable ICU 63 + */ + public final int getValue() { return value; } + } + + /** + * Protected no-args constructor. + * + * @stable ICU 63 + */ + protected CodePointMap() { + } + + /** + * Returns the value for a code point as stored in the map, with range checking. + * Returns an implementation-defined error value if c is not in the range 0..U+10FFFF. + * + * @param c the code point + * @return the map value, + * or an implementation-defined error value if + * the code point is not in the range 0..U+10FFFF + * @stable ICU 63 + */ + public abstract int get(int c); + + /** + * Sets the range object to a range of code points beginning with the start parameter. + * The range start is the same as the start input parameter + * (even if there are preceding code points that have the same value). + * The range end is the last code point such that + * all those from start to there have the same value. + * Returns false if start is not 0..U+10FFFF. + * Can be used to efficiently iterate over all same-value ranges in a map. + * (This is normally faster than iterating over code points and get()ting each value, + * but may be much slower than a data structure that stores ranges directly.) + * + *

If the {@link ValueFilter} parameter is not null, then + * the value to be delivered is passed through that filter, and the return value is the end + * of the range where all values are modified to the same actual value. + * The value is unchanged if that parameter is null. + * + *

Example: + *

+     * int start = 0;
+     * CodePointMap.Range range = new CodePointMap.Range();
+     * while (map.getRange(start, null, range)) {
+     *     int end = range.getEnd();
+     *     int value = range.getValue();
+     *     // Work with the range start..end and its value.
+     *     start = end + 1;
+     * }
+     * 
+ * + * @param start range start + * @param filter an object that may modify the map data value, + * or null if the values from the map are to be used unmodified + * @param range the range object that will be set to the code point range and value + * @return true if start is 0..U+10FFFF; otherwise no new range is fetched + * @stable ICU 63 + */ + public abstract boolean getRange(int start, ValueFilter filter, Range range); + + /** + * Sets the range object to a range of code points beginning with the start parameter. + * The range start is the same as the start input parameter + * (even if there are preceding code points that have the same value). + * The range end is the last code point such that + * all those from start to there have the same value. + * Returns false if start is not 0..U+10FFFF. + * + *

Same as the simpler {@link #getRange(int, ValueFilter, Range)} but optionally + * modifies the range if it overlaps with surrogate code points. + * + * @param start range start + * @param option defines whether surrogates are treated normally, + * or as having the surrogateValue; usually {@link RangeOption#NORMAL} + * @param surrogateValue value for surrogates; ignored if option=={@link RangeOption#NORMAL} + * @param filter an object that may modify the map data value, + * or null if the values from the map are to be used unmodified + * @param range the range object that will be set to the code point range and value + * @return true if start is 0..U+10FFFF; otherwise no new range is fetched + * @stable ICU 63 + */ + public boolean getRange(int start, RangeOption option, int surrogateValue, + ValueFilter filter, Range range) { + assert option != null; + if (!getRange(start, filter, range)) { + return false; + } + if (option == RangeOption.NORMAL) { + return true; + } + int surrEnd = option == RangeOption.FIXED_ALL_SURROGATES ? 0xdfff : 0xdbff; + int end = range.end; + if (end < 0xd7ff || start > surrEnd) { + return true; + } + // The range overlaps with surrogates, or ends just before the first one. + if (range.value == surrogateValue) { + if (end >= surrEnd) { + // Surrogates followed by a non-surrValue range, + // or surrogates are part of a larger surrValue range. + return true; + } + } else { + if (start <= 0xd7ff) { + range.end = 0xd7ff; // Non-surrValue range ends before surrValue surrogates. + return true; + } + // Start is a surrogate with a non-surrValue code *unit* value. + // Return a surrValue code *point* range. + range.value = surrogateValue; + if (end > surrEnd) { + range.end = surrEnd; // Surrogate range ends before non-surrValue rest of range. + return true; + } + } + // See if the surrValue surrogate range can be merged with + // an immediately following range. + if (getRange(surrEnd + 1, filter, range) && range.value == surrogateValue) { + range.start = start; + return true; + } + range.start = start; + range.end = surrEnd; + range.value = surrogateValue; + return true; + } + + /** + * Convenience iterator over same-map-value code point ranges. + * Same as looping over all ranges with {@link #getRange(int, ValueFilter, Range)} + * without filtering. + * Adjacent ranges have different map values. + * + *

The iterator always returns the same Range object. + * + * @return a Range iterator + * @stable ICU 63 + */ + @Override + public Iterator iterator() { + return new RangeIterator(); + } + + /** + * Returns an iterator (not a java.util.Iterator) over code points of a string + * for fetching map values. + * + * @param s string to iterate over + * @param sIndex string index where the iteration will start + * @return the iterator + * @stable ICU 63 + */ + public StringIterator stringIterator(CharSequence s, int sIndex) { + return new StringIterator(s, sIndex); + } +} diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data.class new file mode 100644 index 00000000..78c8c9b9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data16.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data16.class new file mode 100644 index 00000000..3ef75482 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data16.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data32.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data32.class new file mode 100644 index 00000000..6f735a58 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data32.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data8.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data8.class new file mode 100644 index 00000000..793b8187 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Data8.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast$FastStringIterator.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast$FastStringIterator.class new file mode 100644 index 00000000..d3c701b7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast$FastStringIterator.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast.class new file mode 100644 index 00000000..1237414c Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast16.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast16.class new file mode 100644 index 00000000..25bd5937 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast16.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast32.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast32.class new file mode 100644 index 00000000..fc703d08 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast32.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast8.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast8.class new file mode 100644 index 00000000..d53887d2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Fast8.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small$SmallStringIterator.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small$SmallStringIterator.class new file mode 100644 index 00000000..b8655a18 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small$SmallStringIterator.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small.class new file mode 100644 index 00000000..3c762a19 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small16.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small16.class new file mode 100644 index 00000000..1e4f790d Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small16.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small32.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small32.class new file mode 100644 index 00000000..86f358e4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small32.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small8.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small8.class new file mode 100644 index 00000000..f11dde8a Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Small8.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Type.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Type.class new file mode 100644 index 00000000..ed5825db Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$Type.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$ValueWidth.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$ValueWidth.class new file mode 100644 index 00000000..89a54814 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie$ValueWidth.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie.class b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie.class new file mode 100644 index 00000000..6743fdee Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/CodePointTrie.java b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie.java new file mode 100644 index 00000000..7aaa0be6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/util/CodePointTrie.java @@ -0,0 +1,1266 @@ +/* + * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +// (c) 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html#License + +// created: 2018may04 Markus W. Scherer + +package jdk.internal.icu.util; + +import jdk.internal.icu.impl.ICUBinary; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static jdk.internal.icu.impl.NormalizerImpl.UTF16Plus; + +/** + * Immutable Unicode code point trie. + * Fast, reasonably compact, map from Unicode code points (U+0000..U+10FFFF) to integer values. + * For details see https://icu.unicode.org/design/struct/utrie + * + *

This class is not intended for public subclassing. + * + * @see MutableCodePointTrie + * @stable ICU 63 + */ +@SuppressWarnings("deprecation") +public abstract class CodePointTrie extends CodePointMap { + /** + * Selectors for the type of a CodePointTrie. + * Different trade-offs for size vs. speed. + * + *

Use null for {@link #fromBinary} to accept any type; + * {@link #getType} will return the actual type. + * + * @see MutableCodePointTrie#buildImmutable(CodePointTrie.Type, CodePointTrie.ValueWidth) + * @see #fromBinary + * @see #getType + * @stable ICU 63 + */ + public enum Type { + /** + * Fast/simple/larger BMP data structure. + * The {@link Fast} subclasses have additional functions for lookup for BMP and supplementary code points. + * + * @see Fast + * @stable ICU 63 + */ + FAST, + /** + * Small/slower BMP data structure. + * + * @see Small + * @stable ICU 63 + */ + SMALL + } + + /** + * Selectors for the number of bits in a CodePointTrie data value. + * + *

Use null for {@link #fromBinary} to accept any data value width; + * {@link #getValueWidth} will return the actual data value width. + * + * @stable ICU 63 + */ + public enum ValueWidth { + /** + * The trie stores 16 bits per data value. + * It returns them as unsigned values 0..0xffff=65535. + * + * @stable ICU 63 + */ + BITS_16, + /** + * The trie stores 32 bits per data value. + * + * @stable ICU 63 + */ + BITS_32, + /** + * The trie stores 8 bits per data value. + * It returns them as unsigned values 0..0xff=255. + * + * @stable ICU 63 + */ + BITS_8 + } + + private CodePointTrie(char[] index, Data data, int highStart, + int index3NullOffset, int dataNullOffset) { + this.ascii = new int[ASCII_LIMIT]; + this.index = index; + this.data = data; + this.dataLength = data.getDataLength(); + this.highStart = highStart; + this.index3NullOffset = index3NullOffset; + this.dataNullOffset = dataNullOffset; + + for (int c = 0; c < ASCII_LIMIT; ++c) { + ascii[c] = data.getFromIndex(c); + } + + int nullValueOffset = dataNullOffset; + if (nullValueOffset >= dataLength) { + nullValueOffset = dataLength - HIGH_VALUE_NEG_DATA_OFFSET; + } + nullValue = data.getFromIndex(nullValueOffset); + } + + /** + * Creates a trie from its binary form, + * stored in the ByteBuffer starting at the current position. + * Advances the buffer position to just after the trie data. + * Inverse of {@link #toBinary(OutputStream)}. + * + *

The data is copied from the buffer; + * later modification of the buffer will not affect the trie. + * + * @param type selects the trie type; this method throws an exception + * if the type does not match the binary data; + * use null to accept any type + * @param valueWidth selects the number of bits in a data value; this method throws an exception + * if the valueWidth does not match the binary data; + * use null to accept any data value width + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @see MutableCodePointTrie#MutableCodePointTrie(int, int) + * @see MutableCodePointTrie#buildImmutable(CodePointTrie.Type, CodePointTrie.ValueWidth) + * @see #toBinary(OutputStream) + * @stable ICU 63 + */ + public static CodePointTrie fromBinary(Type type, ValueWidth valueWidth, ByteBuffer bytes) { + ByteOrder outerByteOrder = bytes.order(); + try { + // Enough data for a trie header? + if (bytes.remaining() < 16 /* sizeof(UCPTrieHeader) */) { + throw new InternalError("Buffer too short for a CodePointTrie header"); + } + + // struct UCPTrieHeader + /** "Tri3" in big-endian US-ASCII (0x54726933) */ + int signature = bytes.getInt(); + + // Check the signature. + switch (signature) { + case 0x54726933: + // The buffer is already set to the trie data byte order. + break; + case 0x33697254: + // Temporarily reverse the byte order. + boolean isBigEndian = outerByteOrder == ByteOrder.BIG_ENDIAN; + bytes.order(isBigEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + signature = 0x54726933; + break; + default: + throw new InternalError("Buffer does not contain a serialized CodePointTrie"); + } + + // struct UCPTrieHeader continued + /** + * Options bit field: + * Bits 15..12: Data length bits 19..16. + * Bits 11..8: Data null block offset bits 19..16. + * Bits 7..6: UCPTrieType + * Bits 5..3: Reserved (0). + * Bits 2..0: UCPTrieValueWidth + */ + int options = bytes.getChar(); + + /** Total length of the index tables. */ + int indexLength = bytes.getChar(); + + /** Data length bits 15..0. */ + int dataLength = bytes.getChar(); + + /** Index-3 null block offset, 0x7fff or 0xffff if none. */ + int index3NullOffset = bytes.getChar(); + + /** Data null block offset bits 15..0, 0xfffff if none. */ + int dataNullOffset = bytes.getChar(); + + /** + * First code point of the single-value range ending with U+10ffff, + * rounded up and then shifted right by SHIFT_2. + */ + int shiftedHighStart = bytes.getChar(); + // struct UCPTrieHeader end + + int typeInt = (options >> 6) & 3; + Type actualType; + switch (typeInt) { + case 0: actualType = Type.FAST; break; + case 1: actualType = Type.SMALL; break; + default: + throw new InternalError("CodePointTrie data header has an unsupported type"); + } + + int valueWidthInt = options & OPTIONS_VALUE_BITS_MASK; + ValueWidth actualValueWidth; + switch (valueWidthInt) { + case 0: actualValueWidth = ValueWidth.BITS_16; break; + case 1: actualValueWidth = ValueWidth.BITS_32; break; + case 2: actualValueWidth = ValueWidth.BITS_8; break; + default: + throw new InternalError("CodePointTrie data header has an unsupported value width"); + } + + if ((options & OPTIONS_RESERVED_MASK) != 0) { + throw new InternalError("CodePointTrie data header has unsupported options"); + } + + if (type == null) { + type = actualType; + } + if (valueWidth == null) { + valueWidth = actualValueWidth; + } + if (type != actualType || valueWidth != actualValueWidth) { + throw new InternalError("CodePointTrie data header has a different type or value width than required"); + } + + // Get the length values and offsets. + dataLength |= ((options & OPTIONS_DATA_LENGTH_MASK) << 4); + dataNullOffset |= ((options & OPTIONS_DATA_NULL_OFFSET_MASK) << 8); + + int highStart = shiftedHighStart << SHIFT_2; + + // Calculate the actual length, minus the header. + int actualLength = indexLength * 2; + if (valueWidth == ValueWidth.BITS_16) { + actualLength += dataLength * 2; + } else if (valueWidth == ValueWidth.BITS_32) { + actualLength += dataLength * 4; + } else { + actualLength += dataLength; + } + if (bytes.remaining() < actualLength) { + throw new InternalError("Buffer too short for the CodePointTrie data"); + } + + char[] index = ICUBinary.getChars(bytes, indexLength, 0); + switch (valueWidth) { + case BITS_16: { + char[] data16 = ICUBinary.getChars(bytes, dataLength, 0); + return type == Type.FAST ? + new Fast16(index, data16, highStart, index3NullOffset, dataNullOffset) : + new Small16(index, data16, highStart, index3NullOffset, dataNullOffset); + } + case BITS_32: { + int[] data32 = ICUBinary.getInts(bytes, dataLength, 0); + return type == Type.FAST ? + new Fast32(index, data32, highStart, index3NullOffset, dataNullOffset) : + new Small32(index, data32, highStart, index3NullOffset, dataNullOffset); + } + case BITS_8: { + byte[] data8 = ICUBinary.getBytes(bytes, dataLength, 0); + return type == Type.FAST ? + new Fast8(index, data8, highStart, index3NullOffset, dataNullOffset) : + new Small8(index, data8, highStart, index3NullOffset, dataNullOffset); + } + default: + throw new AssertionError("should be unreachable"); + } + } finally { + bytes.order(outerByteOrder); + } + } + + /** + * Returns the trie type. + * + * @return the trie type + * @stable ICU 63 + */ + public abstract Type getType(); + /** + * Returns the number of bits in a trie data value. + * + * @return the number of bits in a trie data value + * @stable ICU 63 + */ + public final ValueWidth getValueWidth() { return data.getValueWidth(); } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public int get(int c) { + return data.getFromIndex(cpIndex(c)); + } + + /** + * Returns a trie value for an ASCII code point, without range checking. + * + * @param c the input code point; must be U+0000..U+007F + * @return The ASCII code point's trie value. + * @stable ICU 63 + */ + public final int asciiGet(int c) { + return ascii[c]; + } + + private static final int MAX_UNICODE = 0x10ffff; + + private static final int ASCII_LIMIT = 0x80; + + private static final int maybeFilterValue(int value, int trieNullValue, int nullValue, + ValueFilter filter) { + if (value == trieNullValue) { + value = nullValue; + } else if (filter != null) { + value = filter.apply(value); + } + return value; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final boolean getRange(int start, ValueFilter filter, Range range) { + if (start < 0 || MAX_UNICODE < start) { + return false; + } + if (start >= highStart) { + int di = dataLength - HIGH_VALUE_NEG_DATA_OFFSET; + int value = data.getFromIndex(di); + if (filter != null) { value = filter.apply(value); } + range.set(start, MAX_UNICODE, value); + return true; + } + + int nullValue = this.nullValue; + if (filter != null) { nullValue = filter.apply(nullValue); } + Type type = getType(); + + int prevI3Block = -1; + int prevBlock = -1; + int c = start; + // Initialize to make compiler happy. Real value when haveValue is true. + int trieValue = 0, value = 0; + boolean haveValue = false; + do { + int i3Block; + int i3; + int i3BlockLength; + int dataBlockLength; + if (c <= 0xffff && (type == Type.FAST || c <= SMALL_MAX)) { + i3Block = 0; + i3 = c >> FAST_SHIFT; + i3BlockLength = type == Type.FAST ? BMP_INDEX_LENGTH : SMALL_INDEX_LENGTH; + dataBlockLength = FAST_DATA_BLOCK_LENGTH; + } else { + // Use the multi-stage index. + int i1 = c >> SHIFT_1; + if (type == Type.FAST) { + assert(0xffff < c && c < highStart); + i1 += BMP_INDEX_LENGTH - OMITTED_BMP_INDEX_1_LENGTH; + } else { + assert(c < highStart && highStart > SMALL_LIMIT); + i1 += SMALL_INDEX_LENGTH; + } + i3Block = index[index[i1] + ((c >> SHIFT_2) & INDEX_2_MASK)]; + if (i3Block == prevI3Block && (c - start) >= CP_PER_INDEX_2_ENTRY) { + // The index-3 block is the same as the previous one, and filled with value. + assert((c & (CP_PER_INDEX_2_ENTRY - 1)) == 0); + c += CP_PER_INDEX_2_ENTRY; + continue; + } + prevI3Block = i3Block; + if (i3Block == index3NullOffset) { + // This is the index-3 null block. + if (haveValue) { + if (nullValue != value) { + range.set(start, c - 1, value); + return true; + } + } else { + trieValue = this.nullValue; + value = nullValue; + haveValue = true; + } + prevBlock = dataNullOffset; + c = (c + CP_PER_INDEX_2_ENTRY) & ~(CP_PER_INDEX_2_ENTRY - 1); + continue; + } + i3 = (c >> SHIFT_3) & INDEX_3_MASK; + i3BlockLength = INDEX_3_BLOCK_LENGTH; + dataBlockLength = SMALL_DATA_BLOCK_LENGTH; + } + // Enumerate data blocks for one index-3 block. + do { + int block; + if ((i3Block & 0x8000) == 0) { + block = index[i3Block + i3]; + } else { + // 18-bit indexes stored in groups of 9 entries per 8 indexes. + int group = (i3Block & 0x7fff) + (i3 & ~7) + (i3 >> 3); + int gi = i3 & 7; + block = (index[group++] << (2 + (2 * gi))) & 0x30000; + block |= index[group + gi]; + } + if (block == prevBlock && (c - start) >= dataBlockLength) { + // The block is the same as the previous one, and filled with value. + assert((c & (dataBlockLength - 1)) == 0); + c += dataBlockLength; + } else { + int dataMask = dataBlockLength - 1; + prevBlock = block; + if (block == dataNullOffset) { + // This is the data null block. + if (haveValue) { + if (nullValue != value) { + range.set(start, c - 1, value); + return true; + } + } else { + trieValue = this.nullValue; + value = nullValue; + haveValue = true; + } + c = (c + dataBlockLength) & ~dataMask; + } else { + int di = block + (c & dataMask); + int trieValue2 = data.getFromIndex(di); + if (haveValue) { + if (trieValue2 != trieValue) { + if (filter == null || + maybeFilterValue(trieValue2, this.nullValue, nullValue, + filter) != value) { + range.set(start, c - 1, value); + return true; + } + trieValue = trieValue2; // may or may not help + } + } else { + trieValue = trieValue2; + value = maybeFilterValue(trieValue2, this.nullValue, nullValue, filter); + haveValue = true; + } + while ((++c & dataMask) != 0) { + trieValue2 = data.getFromIndex(++di); + if (trieValue2 != trieValue) { + if (filter == null || + maybeFilterValue(trieValue2, this.nullValue, nullValue, + filter) != value) { + range.set(start, c - 1, value); + return true; + } + trieValue = trieValue2; // may or may not help + } + } + } + } + } while (++i3 < i3BlockLength); + } while (c < highStart); + assert(haveValue); + int di = dataLength - HIGH_VALUE_NEG_DATA_OFFSET; + int highValue = data.getFromIndex(di); + if (maybeFilterValue(highValue, this.nullValue, nullValue, filter) != value) { + --c; + } else { + c = MAX_UNICODE; + } + range.set(start, c, value); + return true; + } + + /** + * Writes a representation of the trie to the output stream. + * Inverse of {@link #fromBinary}. + * + * @param os the output stream + * @return the number of bytes written + * @stable ICU 63 + */ + public final int toBinary(OutputStream os) { + try { + DataOutputStream dos = new DataOutputStream(os); + + // Write the UCPTrieHeader + dos.writeInt(0x54726933); // signature="Tri3" + dos.writeChar( // options + ((dataLength & 0xf0000) >> 4) | + ((dataNullOffset & 0xf0000) >> 8) | + (getType().ordinal() << 6) | + getValueWidth().ordinal()); + dos.writeChar(index.length); + dos.writeChar(dataLength); + dos.writeChar(index3NullOffset); + dos.writeChar(dataNullOffset); + dos.writeChar(highStart >> SHIFT_2); // shiftedHighStart + int length = 16; // sizeof(UCPTrieHeader) + + for (char i : index) { dos.writeChar(i); } + length += index.length * 2; + length += data.write(dos); + return length; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** @internal */ + static final int FAST_SHIFT = 6; + + /** Number of entries in a data block for code points below the fast limit. 64=0x40 @internal */ + static final int FAST_DATA_BLOCK_LENGTH = 1 << FAST_SHIFT; + + /** Mask for getting the lower bits for the in-fast-data-block offset. @internal */ + private static final int FAST_DATA_MASK = FAST_DATA_BLOCK_LENGTH - 1; + + /** @internal */ + private static final int SMALL_MAX = 0xfff; + + /** + * Offset from dataLength (to be subtracted) for fetching the + * value returned for out-of-range code points and ill-formed UTF-8/16. + * @internal + */ + private static final int ERROR_VALUE_NEG_DATA_OFFSET = 1; + /** + * Offset from dataLength (to be subtracted) for fetching the + * value returned for code points highStart..U+10FFFF. + * @internal + */ + private static final int HIGH_VALUE_NEG_DATA_OFFSET = 2; + + // ucptrie_impl.h + + /** The length of the BMP index table. 1024=0x400 */ + private static final int BMP_INDEX_LENGTH = 0x10000 >> FAST_SHIFT; + + static final int SMALL_LIMIT = 0x1000; + private static final int SMALL_INDEX_LENGTH = SMALL_LIMIT >> FAST_SHIFT; + + /** Shift size for getting the index-3 table offset. */ + static final int SHIFT_3 = 4; + + /** Shift size for getting the index-2 table offset. */ + private static final int SHIFT_2 = 5 + SHIFT_3; + + /** Shift size for getting the index-1 table offset. */ + private static final int SHIFT_1 = 5 + SHIFT_2; + + /** + * Difference between two shift sizes, + * for getting an index-2 offset from an index-3 offset. 5=9-4 + */ + static final int SHIFT_2_3 = SHIFT_2 - SHIFT_3; + + /** + * Difference between two shift sizes, + * for getting an index-1 offset from an index-2 offset. 5=14-9 + */ + static final int SHIFT_1_2 = SHIFT_1 - SHIFT_2; + + /** + * Number of index-1 entries for the BMP. (4) + * This part of the index-1 table is omitted from the serialized form. + */ + private static final int OMITTED_BMP_INDEX_1_LENGTH = 0x10000 >> SHIFT_1; + + /** Number of entries in an index-2 block. 32=0x20 */ + static final int INDEX_2_BLOCK_LENGTH = 1 << SHIFT_1_2; + + /** Mask for getting the lower bits for the in-index-2-block offset. */ + static final int INDEX_2_MASK = INDEX_2_BLOCK_LENGTH - 1; + + /** Number of code points per index-2 table entry. 512=0x200 */ + static final int CP_PER_INDEX_2_ENTRY = 1 << SHIFT_2; + + /** Number of entries in an index-3 block. 32=0x20 */ + static final int INDEX_3_BLOCK_LENGTH = 1 << SHIFT_2_3; + + /** Mask for getting the lower bits for the in-index-3-block offset. */ + private static final int INDEX_3_MASK = INDEX_3_BLOCK_LENGTH - 1; + + /** Number of entries in a small data block. 16=0x10 */ + static final int SMALL_DATA_BLOCK_LENGTH = 1 << SHIFT_3; + + /** Mask for getting the lower bits for the in-small-data-block offset. */ + static final int SMALL_DATA_MASK = SMALL_DATA_BLOCK_LENGTH - 1; + + // ucptrie_impl.h: Constants for use with UCPTrieHeader.options. + private static final int OPTIONS_DATA_LENGTH_MASK = 0xf000; + private static final int OPTIONS_DATA_NULL_OFFSET_MASK = 0xf00; + private static final int OPTIONS_RESERVED_MASK = 0x38; + private static final int OPTIONS_VALUE_BITS_MASK = 7; + /** + * Value for index3NullOffset which indicates that there is no index-3 null block. + * Bit 15 is unused for this value because this bit is used if the index-3 contains + * 18-bit indexes. + */ + static final int NO_INDEX3_NULL_OFFSET = 0x7fff; + static final int NO_DATA_NULL_OFFSET = 0xfffff; + + private abstract static class Data { + abstract ValueWidth getValueWidth(); + abstract int getDataLength(); + abstract int getFromIndex(int index); + abstract int write(DataOutputStream dos) throws IOException; + } + + private static final class Data16 extends Data { + char[] array; + Data16(char[] a) { array = a; } + @Override ValueWidth getValueWidth() { return ValueWidth.BITS_16; } + @Override int getDataLength() { return array.length; } + @Override int getFromIndex(int index) { return array[index]; } + @Override int write(DataOutputStream dos) throws IOException { + for (char v : array) { dos.writeChar(v); } + return array.length * 2; + } + } + + private static final class Data32 extends Data { + int[] array; + Data32(int[] a) { array = a; } + @Override ValueWidth getValueWidth() { return ValueWidth.BITS_32; } + @Override int getDataLength() { return array.length; } + @Override int getFromIndex(int index) { return array[index]; } + @Override int write(DataOutputStream dos) throws IOException { + for (int v : array) { dos.writeInt(v); } + return array.length * 4; + } + } + + private static final class Data8 extends Data { + byte[] array; + Data8(byte[] a) { array = a; } + @Override ValueWidth getValueWidth() { return ValueWidth.BITS_8; } + @Override int getDataLength() { return array.length; } + @Override int getFromIndex(int index) { return array[index] & 0xff; } + @Override int write(DataOutputStream dos) throws IOException { + for (byte v : array) { dos.writeByte(v); } + return array.length; + } + } + + /** @internal */ + private final int[] ascii; + + /** @internal */ + private final char[] index; + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected final Data data; + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected final int dataLength; + /** + * Start of the last range which ends at U+10FFFF. + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected final int highStart; + + /** + * Internal index-3 null block offset. + * Set to an impossibly high value (e.g., 0xffff) if there is no dedicated index-3 null block. + * @internal + */ + private final int index3NullOffset; + /** + * Internal data null block offset, not shifted. + * Set to an impossibly high value (e.g., 0xfffff) if there is no dedicated data null block. + * @internal + */ + private final int dataNullOffset; + /** @internal */ + private final int nullValue; + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected final int fastIndex(int c) { + return index[c >> FAST_SHIFT] + (c & FAST_DATA_MASK); + } + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected final int smallIndex(Type type, int c) { + // Split into two methods to make this part inline-friendly. + // In C, this part is a macro. + if (c >= highStart) { + return dataLength - HIGH_VALUE_NEG_DATA_OFFSET; + } + return internalSmallIndex(type, c); + } + + private final int internalSmallIndex(Type type, int c) { + int i1 = c >> SHIFT_1; + if (type == Type.FAST) { + assert(0xffff < c && c < highStart); + i1 += BMP_INDEX_LENGTH - OMITTED_BMP_INDEX_1_LENGTH; + } else { + assert(0 <= c && c < highStart && highStart > SMALL_LIMIT); + i1 += SMALL_INDEX_LENGTH; + } + int i3Block = index[index[i1] + ((c >> SHIFT_2) & INDEX_2_MASK)]; + int i3 = (c >> SHIFT_3) & INDEX_3_MASK; + int dataBlock; + if ((i3Block & 0x8000) == 0) { + // 16-bit indexes + dataBlock = index[i3Block + i3]; + } else { + // 18-bit indexes stored in groups of 9 entries per 8 indexes. + i3Block = (i3Block & 0x7fff) + (i3 & ~7) + (i3 >> 3); + i3 &= 7; + dataBlock = (index[i3Block++] << (2 + (2 * i3))) & 0x30000; + dataBlock |= index[i3Block + i3]; + } + return dataBlock + (c & SMALL_DATA_MASK); + } + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + protected abstract int cpIndex(int c); + + /** + * A CodePointTrie with {@link Type#FAST}. + * + * @stable ICU 63 + */ + public abstract static class Fast extends CodePointTrie { + private Fast(char[] index, Data data, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, data, highStart, index3NullOffset, dataNullOffset); + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#FAST}. + * + * @param valueWidth selects the number of bits in a data value; this method throws an exception + * if the valueWidth does not match the binary data; + * use null to accept any data value width + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Fast fromBinary(ValueWidth valueWidth, ByteBuffer bytes) { + return (Fast) CodePointTrie.fromBinary(Type.FAST, valueWidth, bytes); + } + + /** + * @return {@link Type#FAST} + * @stable ICU 63 + */ + @Override + public final Type getType() { return Type.FAST; } + + /** + * Returns a trie value for a BMP code point (U+0000..U+FFFF), without range checking. + * Can be used to look up a value for a UTF-16 code unit if other parts of + * the string processing check for surrogates. + * + * @param c the input code point, must be U+0000..U+FFFF + * @return The BMP code point's trie value. + * @stable ICU 63 + */ + public abstract int bmpGet(int c); + + /** + * Returns a trie value for a supplementary code point (U+10000..U+10FFFF), + * without range checking. + * + * @param c the input code point, must be U+10000..U+10FFFF + * @return The supplementary code point's trie value. + * @stable ICU 63 + */ + public abstract int suppGet(int c); + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + @Override + protected final int cpIndex(int c) { + if (c >= 0) { + if (c <= 0xffff) { + return fastIndex(c); + } else if (c <= 0x10ffff) { + return smallIndex(Type.FAST, c); + } + } + return dataLength - ERROR_VALUE_NEG_DATA_OFFSET; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final StringIterator stringIterator(CharSequence s, int sIndex) { + return new FastStringIterator(s, sIndex); + } + + private final class FastStringIterator extends StringIterator { + private FastStringIterator(CharSequence s, int sIndex) { + super(s, sIndex); + } + + @Override + public boolean next() { + if (sIndex >= s.length()) { + return false; + } + char lead = s.charAt(sIndex++); + c = lead; + int dataIndex; + if (!Character.isSurrogate(lead)) { + dataIndex = fastIndex(c); + } else { + char trail; + if (UTF16Plus.isSurrogateLead(lead) && sIndex < s.length() && + Character.isLowSurrogate(trail = s.charAt(sIndex))) { + ++sIndex; + c = Character.toCodePoint(lead, trail); + dataIndex = smallIndex(Type.FAST, c); + } else { + dataIndex = dataLength - ERROR_VALUE_NEG_DATA_OFFSET; + } + } + value = data.getFromIndex(dataIndex); + return true; + } + + @Override + public boolean previous() { + if (sIndex <= 0) { + return false; + } + char trail = s.charAt(--sIndex); + c = trail; + int dataIndex; + if (!Character.isSurrogate(trail)) { + dataIndex = fastIndex(c); + } else { + char lead; + if (!UTF16Plus.isSurrogateLead(trail) && sIndex > 0 && + Character.isHighSurrogate(lead = s.charAt(sIndex - 1))) { + --sIndex; + c = Character.toCodePoint(lead, trail); + dataIndex = smallIndex(Type.FAST, c); + } else { + dataIndex = dataLength - ERROR_VALUE_NEG_DATA_OFFSET; + } + } + value = data.getFromIndex(dataIndex); + return true; + } + } + } + + /** + * A CodePointTrie with {@link Type#SMALL}. + * + * @stable ICU 63 + */ + public abstract static class Small extends CodePointTrie { + private Small(char[] index, Data data, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, data, highStart, index3NullOffset, dataNullOffset); + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#SMALL}. + * + * @param valueWidth selects the number of bits in a data value; this method throws an exception + * if the valueWidth does not match the binary data; + * use null to accept any data value width + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Small fromBinary(ValueWidth valueWidth, ByteBuffer bytes) { + return (Small) CodePointTrie.fromBinary(Type.SMALL, valueWidth, bytes); + } + + /** + * @return {@link Type#SMALL} + * @stable ICU 63 + */ + @Override + public final Type getType() { return Type.SMALL; } + + /** + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + @Override + protected final int cpIndex(int c) { + if (c >= 0) { + if (c <= SMALL_MAX) { + return fastIndex(c); + } else if (c <= 0x10ffff) { + return smallIndex(Type.SMALL, c); + } + } + return dataLength - ERROR_VALUE_NEG_DATA_OFFSET; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final StringIterator stringIterator(CharSequence s, int sIndex) { + return new SmallStringIterator(s, sIndex); + } + + private final class SmallStringIterator extends StringIterator { + private SmallStringIterator(CharSequence s, int sIndex) { + super(s, sIndex); + } + + @Override + public boolean next() { + if (sIndex >= s.length()) { + return false; + } + char lead = s.charAt(sIndex++); + c = lead; + int dataIndex; + if (!Character.isSurrogate(lead)) { + dataIndex = cpIndex(c); + } else { + char trail; + if (UTF16Plus.isSurrogateLead(lead) && sIndex < s.length() && + Character.isLowSurrogate(trail = s.charAt(sIndex))) { + ++sIndex; + c = Character.toCodePoint(lead, trail); + dataIndex = smallIndex(Type.SMALL, c); + } else { + dataIndex = dataLength - ERROR_VALUE_NEG_DATA_OFFSET; + } + } + value = data.getFromIndex(dataIndex); + return true; + } + + @Override + public boolean previous() { + if (sIndex <= 0) { + return false; + } + char trail = s.charAt(--sIndex); + c = trail; + int dataIndex; + if (!Character.isSurrogate(trail)) { + dataIndex = cpIndex(c); + } else { + char lead; + if (!UTF16Plus.isSurrogateLead(trail) && sIndex > 0 && + Character.isHighSurrogate(lead = s.charAt(sIndex - 1))) { + --sIndex; + c = Character.toCodePoint(lead, trail); + dataIndex = smallIndex(Type.SMALL, c); + } else { + dataIndex = dataLength - ERROR_VALUE_NEG_DATA_OFFSET; + } + } + value = data.getFromIndex(dataIndex); + return true; + } + } + } + + /** + * A CodePointTrie with {@link Type#FAST} and {@link ValueWidth#BITS_16}. + * + * @stable ICU 63 + */ + public static final class Fast16 extends Fast { + private final char[] dataArray; + + Fast16(char[] index, char[] data16, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, new Data16(data16), highStart, index3NullOffset, dataNullOffset); + this.dataArray = data16; + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#FAST} and {@link ValueWidth#BITS_16}. + * + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Fast16 fromBinary(ByteBuffer bytes) { + return (Fast16) CodePointTrie.fromBinary(Type.FAST, ValueWidth.BITS_16, bytes); + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int get(int c) { + return dataArray[cpIndex(c)]; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int bmpGet(int c) { + assert 0 <= c && c <= 0xffff; + return dataArray[fastIndex(c)]; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int suppGet(int c) { + assert 0x10000 <= c && c <= 0x10ffff; + return dataArray[smallIndex(Type.FAST, c)]; + } + } + + /** + * A CodePointTrie with {@link Type#FAST} and {@link ValueWidth#BITS_32}. + * + * @stable ICU 63 + */ + public static final class Fast32 extends Fast { + private final int[] dataArray; + + Fast32(char[] index, int[] data32, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, new Data32(data32), highStart, index3NullOffset, dataNullOffset); + this.dataArray = data32; + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#FAST} and {@link ValueWidth#BITS_32}. + * + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Fast32 fromBinary(ByteBuffer bytes) { + return (Fast32) CodePointTrie.fromBinary(Type.FAST, ValueWidth.BITS_32, bytes); + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int get(int c) { + return dataArray[cpIndex(c)]; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int bmpGet(int c) { + assert 0 <= c && c <= 0xffff; + return dataArray[fastIndex(c)]; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int suppGet(int c) { + assert 0x10000 <= c && c <= 0x10ffff; + return dataArray[smallIndex(Type.FAST, c)]; + } + } + + /** + * A CodePointTrie with {@link Type#FAST} and {@link ValueWidth#BITS_8}. + * + * @stable ICU 63 + */ + public static final class Fast8 extends Fast { + private final byte[] dataArray; + + Fast8(char[] index, byte[] data8, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, new Data8(data8), highStart, index3NullOffset, dataNullOffset); + this.dataArray = data8; + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#FAST} and {@link ValueWidth#BITS_8}. + * + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Fast8 fromBinary(ByteBuffer bytes) { + return (Fast8) CodePointTrie.fromBinary(Type.FAST, ValueWidth.BITS_8, bytes); + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int get(int c) { + return dataArray[cpIndex(c)] & 0xff; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int bmpGet(int c) { + assert 0 <= c && c <= 0xffff; + return dataArray[fastIndex(c)] & 0xff; + } + + /** + * {@inheritDoc} + * @stable ICU 63 + */ + @Override + public final int suppGet(int c) { + assert 0x10000 <= c && c <= 0x10ffff; + return dataArray[smallIndex(Type.FAST, c)] & 0xff; + } + } + + /** + * A CodePointTrie with {@link Type#SMALL} and {@link ValueWidth#BITS_16}. + * + * @stable ICU 63 + */ + public static final class Small16 extends Small { + Small16(char[] index, char[] data16, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, new Data16(data16), highStart, index3NullOffset, dataNullOffset); + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#SMALL} and {@link ValueWidth#BITS_16}. + * + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Small16 fromBinary(ByteBuffer bytes) { + return (Small16) CodePointTrie.fromBinary(Type.SMALL, ValueWidth.BITS_16, bytes); + } + } + + /** + * A CodePointTrie with {@link Type#SMALL} and {@link ValueWidth#BITS_32}. + * + * @stable ICU 63 + */ + public static final class Small32 extends Small { + Small32(char[] index, int[] data32, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, new Data32(data32), highStart, index3NullOffset, dataNullOffset); + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#SMALL} and {@link ValueWidth#BITS_32}. + * + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Small32 fromBinary(ByteBuffer bytes) { + return (Small32) CodePointTrie.fromBinary(Type.SMALL, ValueWidth.BITS_32, bytes); + } + } + + /** + * A CodePointTrie with {@link Type#SMALL} and {@link ValueWidth#BITS_8}. + * + * @stable ICU 63 + */ + public static final class Small8 extends Small { + Small8(char[] index, byte[] data8, int highStart, + int index3NullOffset, int dataNullOffset) { + super(index, new Data8(data8), highStart, index3NullOffset, dataNullOffset); + } + + /** + * Creates a trie from its binary form. + * Same as {@link CodePointTrie#fromBinary(Type, ValueWidth, ByteBuffer)} + * with {@link Type#SMALL} and {@link ValueWidth#BITS_8}. + * + * @param bytes a buffer containing the binary data of a CodePointTrie + * @return the trie + * @stable ICU 63 + */ + public static Small8 fromBinary(ByteBuffer bytes) { + return (Small8) CodePointTrie.fromBinary(Type.SMALL, ValueWidth.BITS_8, bytes); + } + } +} diff --git a/tests/test_data/std/jdk/internal/icu/util/OutputInt.class b/tests/test_data/std/jdk/internal/icu/util/OutputInt.class new file mode 100644 index 00000000..ab3a2b2f Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/OutputInt.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/OutputInt.java b/tests/test_data/std/jdk/internal/icu/util/OutputInt.java new file mode 100644 index 00000000..13c65841 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/util/OutputInt.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + ******************************************************************************* + * Copyright (C) 2014, International Business Machines Corporation and + * others. All Rights Reserved. + ******************************************************************************* + */ +package jdk.internal.icu.util; + +/** + * Simple struct-like class for int output parameters. + * Like Output<Integer> but without auto-boxing. + * + * @internal but could become public + * deprecated This API is ICU internal only. + */ +public class OutputInt { + + /** + * The value field. + * + * @internal + * deprecated This API is ICU internal only. + */ + public int value; +} diff --git a/tests/test_data/std/jdk/internal/icu/util/VersionInfo.class b/tests/test_data/std/jdk/internal/icu/util/VersionInfo.class new file mode 100644 index 00000000..4bb4d3a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/icu/util/VersionInfo.class differ diff --git a/tests/test_data/std/jdk/internal/icu/util/VersionInfo.java b/tests/test_data/std/jdk/internal/icu/util/VersionInfo.java new file mode 100644 index 00000000..12228446 --- /dev/null +++ b/tests/test_data/std/jdk/internal/icu/util/VersionInfo.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + ******************************************************************************* + * (C) Copyright IBM Corp. and others, 1996-2009 - All Rights Reserved * + * * + * The original version of this source code and documentation is copyrighted * + * and owned by IBM, These materials are provided under terms of a License * + * Agreement between IBM and Sun. This technology is protected by multiple * + * US and International patents. This notice and attribution to IBM may not * + * to removed. * + ******************************************************************************* + */ + +package jdk.internal.icu.util; + +import java.util.HashMap; + +/** + * Class to store version numbers of the form major.minor.milli.micro. + * @author synwee + * @stable ICU 2.6 + */ +public final class VersionInfo +{ + // public data members ------------------------------------------------- + + /** + * Data version string for ICU's internal data. + * Used for appending to data path (e.g. icudt43b) + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + public static final String ICU_DATA_VERSION_PATH = "74b"; + + // public methods ------------------------------------------------------ + + /** + * Returns an instance of VersionInfo with the argument version. + * @param version version String in the format of "major.minor.milli.micro" + * or "major.minor.milli" or "major.minor" or "major", + * where major, minor, milli, micro are non-negative numbers + * {@literal <=} 255. If the trailing version numbers are + * not specified they are taken as 0s. E.g. Version "3.1" is + * equivalent to "3.1.0.0". + * @return an instance of VersionInfo with the argument version. + * @exception throws an IllegalArgumentException when the argument version + * is not in the right format + * @stable ICU 2.6 + */ + public static VersionInfo getInstance(String version) + { + int length = version.length(); + int array[] = {0, 0, 0, 0}; + int count = 0; + int index = 0; + + while (count < 4 && index < length) { + char c = version.charAt(index); + if (c == '.') { + count ++; + } + else { + c -= '0'; + if (c < 0 || c > 9) { + throw new IllegalArgumentException(INVALID_VERSION_NUMBER_); + } + array[count] *= 10; + array[count] += c; + } + index ++; + } + if (index != length) { + throw new IllegalArgumentException( + "Invalid version number: String '" + version + "' exceeds version format"); + } + for (int i = 0; i < 4; i ++) { + if (array[i] < 0 || array[i] > 255) { + throw new IllegalArgumentException(INVALID_VERSION_NUMBER_); + } + } + + return getInstance(array[0], array[1], array[2], array[3]); + } + + /** + * Returns an instance of VersionInfo with the argument version. + * @param major major version, non-negative number {@literal <=} 255. + * @param minor minor version, non-negative number {@literal <=} 255. + * @param milli milli version, non-negative number {@literal <=} 255. + * @param micro micro version, non-negative number {@literal <=} 255. + * @exception throws an IllegalArgumentException when either arguments are + * negative or {@literal >} 255 + * @stable ICU 2.6 + */ + public static VersionInfo getInstance(int major, int minor, int milli, + int micro) + { + // checks if it is in the hashmap + // else + if (major < 0 || major > 255 || minor < 0 || minor > 255 || + milli < 0 || milli > 255 || micro < 0 || micro > 255) { + throw new IllegalArgumentException(INVALID_VERSION_NUMBER_); + } + int version = getInt(major, minor, milli, micro); + Integer key = Integer.valueOf(version); + Object result = MAP_.get(key); + if (result == null) { + result = new VersionInfo(version); + MAP_.put(key, result); + } + return (VersionInfo)result; + } + + /** + * Compares other with this VersionInfo. + * @param other VersionInfo to be compared + * @return 0 if the argument is a VersionInfo object that has version + * information equal to this object. + * Less than 0 if the argument is a VersionInfo object that has + * version information greater than this object. + * Greater than 0 if the argument is a VersionInfo object that + * has version information less than this object. + * @stable ICU 2.6 + */ + public int compareTo(VersionInfo other) + { + // m_version_ is an int, a signed 32-bit integer. + // When the major version is >=128, then the version int is negative. + // Compare it in two steps to simulate an unsigned-int comparison. + // (Alternatively we could turn each int into a long and reset the upper 32 bits.) + // Compare the upper bits first, using logical shift right (unsigned). + int diff = (m_version_ >>> 1) - (other.m_version_ >>> 1); + if (diff != 0) { return diff; } + // Compare the remaining bits. + return (m_version_ & 1) - (other.m_version_ & 1); + } + + // private data members ---------------------------------------------- + + /** + * Version number stored as a byte for each of the major, minor, milli and + * micro numbers in the 32 bit int. + * Most significant for the major and the least significant contains the + * micro numbers. + */ + private int m_version_; + /** + * Map of singletons + */ + private static final HashMap MAP_ = new HashMap<>(); + /** + * Error statement string + */ + private static final String INVALID_VERSION_NUMBER_ = + "Invalid version number: Version number may be negative or greater than 255"; + + // private constructor ----------------------------------------------- + + /** + * Constructor with int + * @param compactversion a 32 bit int with each byte representing a number + */ + private VersionInfo(int compactversion) + { + m_version_ = compactversion; + } + + /** + * Gets the int from the version numbers + * @param major non-negative version number + * @param minor non-negativeversion number + * @param milli non-negativeversion number + * @param micro non-negativeversion number + */ + private static int getInt(int major, int minor, int milli, int micro) + { + return (major << 24) | (minor << 16) | (milli << 8) | micro; + } +} diff --git a/tests/test_data/std/jdk/internal/io/JdkConsole.class b/tests/test_data/std/jdk/internal/io/JdkConsole.class new file mode 100644 index 00000000..167d4ee7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/io/JdkConsole.class differ diff --git a/tests/test_data/std/jdk/internal/io/JdkConsole.java b/tests/test_data/std/jdk/internal/io/JdkConsole.java new file mode 100644 index 00000000..6c911ed6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/io/JdkConsole.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.io; + +import java.io.PrintWriter; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.Locale; + +/** + * Delegate interface for custom Console implementations. + * Methods defined here duplicates the ones in Console class. + * Providers should implement jdk.internal.io.JdkConsoleProvider + * to instantiate an implementation of this interface. + */ +public interface JdkConsole { + PrintWriter writer(); + Reader reader(); + JdkConsole println(Object obj); + JdkConsole print(Object obj); + String readln(String prompt); + JdkConsole format(Locale locale, String format, Object ... args); + String readLine(Locale locale, String format, Object ... args); + String readLine(); + char[] readPassword(Locale locale, String format, Object ... args); + char[] readPassword(); + void flush(); + Charset charset(); +} diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$1.class b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$1.class new file mode 100644 index 00000000..4c60b18c Binary files /dev/null and b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$2.class b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$2.class new file mode 100644 index 00000000..176904a2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$2.class differ diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$LineReader.class b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$LineReader.class new file mode 100644 index 00000000..68faf089 Binary files /dev/null and b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl$LineReader.class differ diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleImpl.class b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl.class new file mode 100644 index 00000000..7b8819f6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl.class differ diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleImpl.java b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl.java new file mode 100644 index 00000000..a1086b24 --- /dev/null +++ b/tests/test_data/std/jdk/internal/io/JdkConsoleImpl.java @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.io; + +import java.io.IOError; +import java.io.IOException; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Formatter; +import java.util.Locale; +import java.util.Objects; + +import jdk.internal.access.SharedSecrets; +import sun.nio.cs.StreamDecoder; +import sun.nio.cs.StreamEncoder; + +/** + * JdkConsole implementation based on the platform's TTY. + */ +public final class JdkConsoleImpl implements JdkConsole { + @Override + public PrintWriter writer() { + return pw; + } + + @Override + public Reader reader() { + return reader; + } + + @Override + public JdkConsole println(Object obj) { + pw.println(obj); + // automatic flushing covers println + return this; + } + + @Override + public JdkConsole print(Object obj) { + pw.print(obj); + pw.flush(); // automatic flushing does not cover print + return this; + } + + @Override + public String readln(String prompt) { + String line = null; + synchronized (writeLock) { + synchronized(readLock) { + pw.print(prompt); + pw.flush(); // automatic flushing does not cover print + try { + char[] ca = readline(false); + if (ca != null) + line = new String(ca); + } catch (IOException x) { + throw new IOError(x); + } + } + } + return line; + } + + @Override + public JdkConsole format(Locale locale, String format, Object ... args) { + formatter.format(locale, format, args).flush(); + return this; + } + + @Override + public String readLine(Locale locale, String format, Object ... args) { + String line = null; + synchronized (writeLock) { + synchronized(readLock) { + if (!format.isEmpty()) + pw.format(locale, format, args); + try { + char[] ca = readline(false); + if (ca != null) + line = new String(ca); + } catch (IOException x) { + throw new IOError(x); + } + } + } + return line; + } + + @Override + public String readLine() { + return readLine(Locale.getDefault(Locale.Category.FORMAT), ""); + } + + @Override + public char[] readPassword(Locale locale, String format, Object ... args) { + char[] passwd = null; + synchronized (writeLock) { + synchronized(readLock) { + installShutdownHook(); + try { + synchronized(restoreEchoLock) { + restoreEcho = echo(false); + } + } catch (IOException x) { + throw new IOError(x); + } + IOError ioe = null; + try { + if (!format.isEmpty()) + pw.format(locale, format, args); + passwd = readline(true); + } catch (IOException x) { + ioe = new IOError(x); + } finally { + try { + synchronized(restoreEchoLock) { + if (restoreEcho) { + restoreEcho = echo(true); + } + } + } catch (IOException x) { + if (ioe == null) + ioe = new IOError(x); + else + ioe.addSuppressed(x); + } + if (ioe != null) { + Arrays.fill(passwd, ' '); + try { + if (reader instanceof LineReader lr) { + lr.zeroOut(); + } + } catch (IOException _) { + // ignore + } + throw ioe; + } + } + pw.println(); + } + } + return passwd; + } + + private void installShutdownHook() { + if (shutdownHookInstalled) + return; + try { + // Add a shutdown hook to restore console's echo state should + // it be necessary. + SharedSecrets.getJavaLangAccess() + .registerShutdownHook(0 /* shutdown hook invocation order */, + false /* only register if shutdown is not in progress */, + new Runnable() { + public void run() { + try { + synchronized(restoreEchoLock) { + if (restoreEcho) { + echo(true); + } + } + } catch (IOException _) { } + } + }); + } catch (IllegalStateException _) { + // shutdown is already in progress and readPassword is first used + // by a shutdown hook + } + shutdownHookInstalled = true; + } + + @Override + public char[] readPassword() { + return readPassword(Locale.getDefault(Locale.Category.FORMAT), ""); + } + + @Override + public void flush() { + pw.flush(); + } + + @Override + public Charset charset() { + return charset; + } + + private final Charset charset; + private final Object readLock; + private final Object writeLock; + // Must not block while holding this. It is used in the shutdown hook. + private final Object restoreEchoLock; + private final Reader reader; + private final Writer out; + private final PrintWriter pw; + private final Formatter formatter; + private char[] rcb; + private boolean restoreEcho; + private boolean shutdownHookInstalled; + + private char[] readline(boolean zeroOut) throws IOException { + int len = reader.read(rcb, 0, rcb.length); + if (len < 0) + return null; //EOL + if (rcb[len-1] == '\r') + len--; //remove CR at end; + else if (rcb[len-1] == '\n') { + len--; //remove LF at end; + if (len > 0 && rcb[len-1] == '\r') + len--; //remove the CR, if there is one + } + char[] b = new char[len]; + if (len > 0) { + System.arraycopy(rcb, 0, b, 0, len); + if (zeroOut) { + Arrays.fill(rcb, 0, len, ' '); + if (reader instanceof LineReader lr) { + lr.zeroOut(); + } + } + } + return b; + } + + private char[] grow() { + assert Thread.holdsLock(readLock); + char[] t = new char[rcb.length * 2]; + System.arraycopy(rcb, 0, t, 0, rcb.length); + rcb = t; + return rcb; + } + + /* + * Sets the console echo status to {@code on} and returns the previous + * console on/off status. + * @param on the echo status to set to. {@code true} for echo on and + * {@code false} for echo off + * @return true if the previous console echo status is on + */ + private static native boolean echo(boolean on) throws IOException; + + class LineReader extends Reader { + private final Reader in; + private final char[] cb; + private int nChars, nextChar; + boolean leftoverLF; + LineReader(Reader in) { + this.in = in; + cb = new char[1024]; + nextChar = nChars = 0; + leftoverLF = false; + } + public void zeroOut() throws IOException { + if (in instanceof StreamDecoder sd) { + sd.fillZeroToPosition(); + } + } + public void close () {} + public boolean ready() throws IOException { + //in.ready synchronizes on readLock already + return in.ready(); + } + + public int read(char[] cbuf, int offset, int length) + throws IOException + { + int off = offset; + int end = offset + length; + if (offset < 0 || offset > cbuf.length || length < 0 || + end < 0 || end > cbuf.length) { + throw new IndexOutOfBoundsException(); + } + synchronized(readLock) { + boolean eof = false; + char c; + for (;;) { + if (nextChar >= nChars) { //fill + int n; + do { + n = in.read(cb, 0, cb.length); + } while (n == 0); + if (n > 0) { + nChars = n; + nextChar = 0; + if (n < cb.length && + cb[n-1] != '\n' && cb[n-1] != '\r') { + /* + * we're in canonical mode so each "fill" should + * come back with an eol. if there is no lf or nl at + * the end of returned bytes we reached an eof. + */ + eof = true; + } + } else { /*EOF*/ + if (off - offset == 0) + return -1; + return off - offset; + } + } + if (leftoverLF && cbuf == rcb && cb[nextChar] == '\n') { + /* + * if invoked by our readline, skip the leftover, otherwise + * return the LF. + */ + nextChar++; + } + leftoverLF = false; + while (nextChar < nChars) { + c = cbuf[off++] = cb[nextChar]; + cb[nextChar++] = 0; + if (c == '\n') { + return off - offset; + } else if (c == '\r') { + if (off == end) { + /* no space left even the next is LF, so return + * whatever we have if the invoker is not our + * readLine() + */ + if (cbuf == rcb) { + cbuf = grow(); + } else { + leftoverLF = true; + return off - offset; + } + } + if (nextChar == nChars && in.ready()) { + /* + * we have a CR and we reached the end of + * the read in buffer, fill to make sure we + * don't miss a LF, if there is one, it's possible + * that it got cut off during last round reading + * simply because the read in buffer was full. + */ + nChars = in.read(cb, 0, cb.length); + nextChar = 0; + } + if (nextChar < nChars && cb[nextChar] == '\n') { + cbuf[off++] = '\n'; + nextChar++; + } + return off - offset; + } else if (off == end) { + if (cbuf == rcb) { + cbuf = grow(); + end = cbuf.length; + } else { + return off - offset; + } + } + } + if (eof) + return off - offset; + } + } + } + } + + public JdkConsoleImpl(Charset charset) { + Objects.requireNonNull(charset); + this.charset = charset; + readLock = new Object(); + writeLock = new Object(); + restoreEchoLock = new Object(); + out = StreamEncoder.forOutputStreamWriter( + new FileOutputStream(FileDescriptor.out), + writeLock, + charset); + pw = new PrintWriter(out, true) { + public void close() { + } + }; + formatter = new Formatter(out); + reader = new LineReader(StreamDecoder.forInputStreamReader( + new FileInputStream(FileDescriptor.in), + readLock, + charset)); + rcb = new char[1024]; + } +} diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleProvider.class b/tests/test_data/std/jdk/internal/io/JdkConsoleProvider.class new file mode 100644 index 00000000..d2946d6c Binary files /dev/null and b/tests/test_data/std/jdk/internal/io/JdkConsoleProvider.class differ diff --git a/tests/test_data/std/jdk/internal/io/JdkConsoleProvider.java b/tests/test_data/std/jdk/internal/io/JdkConsoleProvider.java new file mode 100644 index 00000000..26e59c84 --- /dev/null +++ b/tests/test_data/std/jdk/internal/io/JdkConsoleProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.io; + +import java.nio.charset.Charset; + +/** + * Service provider interface for JdkConsole implementations. + */ +public interface JdkConsoleProvider { + /** + * The module name of the JdkConsole default provider. + */ + String DEFAULT_PROVIDER_MODULE_NAME = "jdk.internal.le"; + + /** + * {@return the Console instance, or {@code null} if not available} + * @param isTTY indicates if the jvm is attached to a terminal + * @param charset charset of the platform console + */ + JdkConsole console(boolean isTTY, Charset charset); +} diff --git a/tests/test_data/std/jdk/internal/javac/NoPreview.class b/tests/test_data/std/jdk/internal/javac/NoPreview.class new file mode 100644 index 00000000..971a4454 Binary files /dev/null and b/tests/test_data/std/jdk/internal/javac/NoPreview.class differ diff --git a/tests/test_data/std/jdk/internal/javac/NoPreview.java b/tests/test_data/std/jdk/internal/javac/NoPreview.java new file mode 100644 index 00000000..51a06cd2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/javac/NoPreview.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.javac; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The element annotated with this annotation should not be marked as a preview element. + * + * Note this internal annotation is handled specially by the javac compiler. + * To work properly with {@code --release older-release}, it requires special + * handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java} + * and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}. + * + */ +@Target({ElementType.METHOD, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.PACKAGE, + ElementType.MODULE, + ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface NoPreview { +} diff --git a/tests/test_data/std/jdk/internal/javac/ParticipatesInPreview.class b/tests/test_data/std/jdk/internal/javac/ParticipatesInPreview.class new file mode 100644 index 00000000..5e5007a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/javac/ParticipatesInPreview.class differ diff --git a/tests/test_data/std/jdk/internal/javac/ParticipatesInPreview.java b/tests/test_data/std/jdk/internal/javac/ParticipatesInPreview.java new file mode 100644 index 00000000..c291ca9f --- /dev/null +++ b/tests/test_data/std/jdk/internal/javac/ParticipatesInPreview.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.javac; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates, when declared on a module declaration, that the module participates + * in preview features and therefore does not need to be compiled with "--enable-preview". + */ +@Target(ElementType.MODULE) +@Retention(RetentionPolicy.CLASS) +public @interface ParticipatesInPreview { +} diff --git a/tests/test_data/std/jdk/internal/javac/PreviewFeature$Feature.class b/tests/test_data/std/jdk/internal/javac/PreviewFeature$Feature.class new file mode 100644 index 00000000..1dc609c3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/javac/PreviewFeature$Feature.class differ diff --git a/tests/test_data/std/jdk/internal/javac/PreviewFeature$JEP.class b/tests/test_data/std/jdk/internal/javac/PreviewFeature$JEP.class new file mode 100644 index 00000000..c49f3f8c Binary files /dev/null and b/tests/test_data/std/jdk/internal/javac/PreviewFeature$JEP.class differ diff --git a/tests/test_data/std/jdk/internal/javac/PreviewFeature.class b/tests/test_data/std/jdk/internal/javac/PreviewFeature.class new file mode 100644 index 00000000..32639421 Binary files /dev/null and b/tests/test_data/std/jdk/internal/javac/PreviewFeature.class differ diff --git a/tests/test_data/std/jdk/internal/javac/PreviewFeature.java b/tests/test_data/std/jdk/internal/javac/PreviewFeature.java new file mode 100644 index 00000000..05049eff --- /dev/null +++ b/tests/test_data/std/jdk/internal/javac/PreviewFeature.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.javac; + +import java.lang.annotation.*; + +/** + * Indicates the API declaration in question is associated with a + * preview feature. See JEP 12: "Preview Language and VM + * Features" (https://openjdk.org/jeps/12). + * + * Note this internal annotation is handled specially by the javac compiler. + * To work properly with {@code --release older-release}, it requires special + * handling in {@code make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java} + * and {@code src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java}. + * + * @since 14 + */ +// Match the meaningful targets of java.lang.Deprecated, omit local +// variables and parameter declarations +@Target({ElementType.METHOD, + ElementType.CONSTRUCTOR, + ElementType.FIELD, + ElementType.PACKAGE, + ElementType.MODULE, + ElementType.TYPE}) + // CLASS retention will hopefully be sufficient for the purposes at hand +@Retention(RetentionPolicy.CLASS) +// *Not* @Documented +public @interface PreviewFeature { + /** + * Name of the preview feature the annotated API is associated + * with. + */ + public Feature feature(); + + public boolean reflective() default false; + + /** + * Enum of preview features in the current release. + * Values should be annotated with the feature's {@code JEP}. + */ + public enum Feature { + // not used, but required for interim javac to not warn. + VIRTUAL_THREADS, + FOREIGN, + @JEP(number=459, title="String Templates", status="Second Preview") + STRING_TEMPLATES, + @JEP(number=477, title="Implicitly Declared Classes and Instance Main Methods", status="Third Preview") + IMPLICIT_CLASSES, + @JEP(number=481, title="Scoped Values", status="Third Preview") + SCOPED_VALUES, + @JEP(number=480, title="Structured Concurrency", status="Third Preview") + STRUCTURED_CONCURRENCY, + @JEP(number=466, title="ClassFile API", status="Second Preview") + CLASSFILE_API, + @JEP(number=473, title="Stream Gatherers", status="Second Preview") + STREAM_GATHERERS, + @JEP(number=476, title="Module Import Declarations", status="Preview") + MODULE_IMPORTS, + LANGUAGE_MODEL, + /** + * A key for testing. + */ + @JEP(number=2_147_483_647, title="Test Feature") + TEST, + ; + } + + /** + * Annotation identifying the JEP associated with a preview feature. + */ + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.CLASS) + @interface JEP { + /** JEP number */ + int number() default 0; + /** JEP title in plain text */ + String title(); + /** JEP status such as "Preview", "Second Preview", etc */ + String status() default "Preview"; + } +} diff --git a/tests/test_data/std/jdk/internal/javac/Restricted.class b/tests/test_data/std/jdk/internal/javac/Restricted.class new file mode 100644 index 00000000..0c6a44a8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/javac/Restricted.class differ diff --git a/tests/test_data/std/jdk/internal/javac/Restricted.java b/tests/test_data/std/jdk/internal/javac/Restricted.java new file mode 100644 index 00000000..c6b44dc7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/javac/Restricted.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.javac; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to mark restricted methods in the Java SE API + * (e.g. {@link java.lang.foreign.MemorySegment#reinterpret(long)}). + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Restricted { +} diff --git a/tests/test_data/std/jdk/internal/jimage/BasicImageReader$1.class b/tests/test_data/std/jdk/internal/jimage/BasicImageReader$1.class new file mode 100644 index 00000000..df519673 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/BasicImageReader$1.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/BasicImageReader$2.class b/tests/test_data/std/jdk/internal/jimage/BasicImageReader$2.class new file mode 100644 index 00000000..c51e211c Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/BasicImageReader$2.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/BasicImageReader.class b/tests/test_data/std/jdk/internal/jimage/BasicImageReader.class new file mode 100644 index 00000000..ee2484b8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/BasicImageReader.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/BasicImageReader.java b/tests/test_data/std/jdk/internal/jimage/BasicImageReader.java new file mode 100644 index 00000000..07879353 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/BasicImageReader.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Objects; +import java.util.stream.IntStream; +import jdk.internal.jimage.decompressor.Decompressor; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class BasicImageReader implements AutoCloseable { + @SuppressWarnings("removal") + private static boolean isSystemProperty(String key, String value, String def) { + // No lambdas during bootstrap + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Boolean run() { + return value.equals(System.getProperty(key, def)); + } + }); + } + + private static final boolean IS_64_BIT = + isSystemProperty("sun.arch.data.model", "64", "32"); + private static final boolean USE_JVM_MAP = + isSystemProperty("jdk.image.use.jvm.map", "true", "true"); + private static final boolean MAP_ALL = + isSystemProperty("jdk.image.map.all", "true", IS_64_BIT ? "true" : "false"); + + private final Path imagePath; + private final ByteOrder byteOrder; + private final String name; + private final ByteBuffer memoryMap; + private final FileChannel channel; + private final ImageHeader header; + private final long indexSize; + private final IntBuffer redirect; + private final IntBuffer offsets; + private final ByteBuffer locations; + private final ByteBuffer strings; + private final ImageStringsReader stringsReader; + private final Decompressor decompressor; + + @SuppressWarnings({ "removal", "this-escape" }) + protected BasicImageReader(Path path, ByteOrder byteOrder) + throws IOException { + this.imagePath = Objects.requireNonNull(path); + this.byteOrder = Objects.requireNonNull(byteOrder); + this.name = this.imagePath.toString(); + + ByteBuffer map; + + if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) { + // Check to see if the jvm has opened the file using libjimage + // native entry when loading the image for this runtime + map = NativeImageBuffer.getNativeMap(name); + } else { + map = null; + } + + // Open the file only if no memory map yet or is 32 bit jvm + if (map != null && MAP_ALL) { + channel = null; + } else { + channel = FileChannel.open(imagePath, StandardOpenOption.READ); + // No lambdas during bootstrap + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + if (BasicImageReader.class.getClassLoader() == null) { + try { + Class fileChannelImpl = + Class.forName("sun.nio.ch.FileChannelImpl"); + Method setUninterruptible = + fileChannelImpl.getMethod("setUninterruptible"); + setUninterruptible.invoke(channel); + } catch (ClassNotFoundException | + NoSuchMethodException | + IllegalAccessException | + InvocationTargetException ex) { + // fall thru - will only happen on JDK-8 systems where this code + // is only used by tools using jrt-fs (non-critical.) + } + } + + return null; + } + }); + } + + // If no memory map yet and 64 bit jvm then memory map entire file + if (MAP_ALL && map == null) { + map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); + } + + // Assume we have a memory map to read image file header + ByteBuffer headerBuffer = map; + int headerSize = ImageHeader.getHeaderSize(); + + // If no memory map then read header from image file + if (headerBuffer == null) { + headerBuffer = ByteBuffer.allocateDirect(headerSize); + if (channel.read(headerBuffer, 0L) == headerSize) { + headerBuffer.rewind(); + } else { + throw new IOException("\"" + name + "\" is not an image file"); + } + } else if (headerBuffer.capacity() < headerSize) { + throw new IOException("\"" + name + "\" is not an image file"); + } + + // Interpret the image file header + header = readHeader(intBuffer(headerBuffer, 0, headerSize)); + indexSize = header.getIndexSize(); + + // If no memory map yet then must be 32 bit jvm not previously mapped + if (map == null) { + // Just map the image index + map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize); + } + + memoryMap = map.asReadOnlyBuffer(); + + // Interpret the image index + if (memoryMap.capacity() < indexSize) { + throw new IOException("The image file \"" + name + "\" is corrupted"); + } + redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize()); + offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize()); + locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize()); + strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize()); + + stringsReader = new ImageStringsReader(this); + decompressor = new Decompressor(); + } + + protected BasicImageReader(Path imagePath) throws IOException { + this(imagePath, ByteOrder.nativeOrder()); + } + + public static BasicImageReader open(Path imagePath) throws IOException { + return new BasicImageReader(imagePath, ByteOrder.nativeOrder()); + } + + public ImageHeader getHeader() { + return header; + } + + private ImageHeader readHeader(IntBuffer buffer) throws IOException { + ImageHeader result = ImageHeader.readFrom(buffer); + + if (result.getMagic() != ImageHeader.MAGIC) { + throw new IOException("\"" + name + "\" is not an image file"); + } + + if (result.getMajorVersion() != ImageHeader.MAJOR_VERSION || + result.getMinorVersion() != ImageHeader.MINOR_VERSION) { + throw new IOException("The image file \"" + name + "\" is not " + + "the correct version. Major: " + result.getMajorVersion() + + ". Minor: " + result.getMinorVersion()); + } + + return result; + } + + private static ByteBuffer slice(ByteBuffer buffer, int position, int capacity) { + // Note that this is the only limit and position manipulation of + // BasicImageReader private ByteBuffers. The synchronize could be avoided + // by cloning the buffer to make a local copy, but at the cost of creating + // a new object. + synchronized(buffer) { + buffer.limit(position + capacity); + buffer.position(position); + return buffer.slice(); + } + } + + private IntBuffer intBuffer(ByteBuffer buffer, int offset, int size) { + return slice(buffer, offset, size).order(byteOrder).asIntBuffer(); + } + + public static void releaseByteBuffer(ByteBuffer buffer) { + Objects.requireNonNull(buffer); + + if (!MAP_ALL) { + ImageBufferCache.releaseBuffer(buffer); + } + } + + public String getName() { + return name; + } + + public ByteOrder getByteOrder() { + return byteOrder; + } + + public Path getImagePath() { + return imagePath; + } + + @Override + public void close() throws IOException { + if (channel != null) { + channel.close(); + } + } + + public ImageStringsReader getStrings() { + return stringsReader; + } + + public ImageLocation findLocation(String module, String name) { + int index = getLocationIndex(module, name); + if (index < 0) { + return null; + } + long[] attributes = getAttributes(offsets.get(index)); + if (!ImageLocation.verify(module, name, attributes, stringsReader)) { + return null; + } + return new ImageLocation(attributes, stringsReader); + } + + public ImageLocation findLocation(String name) { + int index = getLocationIndex(name); + if (index < 0) { + return null; + } + long[] attributes = getAttributes(offsets.get(index)); + if (!ImageLocation.verify(name, attributes, stringsReader)) { + return null; + } + return new ImageLocation(attributes, stringsReader); + } + + public boolean verifyLocation(String module, String name) { + int index = getLocationIndex(module, name); + if (index < 0) { + return false; + } + int locationOffset = offsets.get(index); + return ImageLocation.verify(module, name, locations, locationOffset, stringsReader); + } + + // Details of the algorithm used here can be found in + // jdk.tools.jlink.internal.PerfectHashBuilder. + public int getLocationIndex(String name) { + int count = header.getTableLength(); + int index = redirect.get(ImageStringsReader.hashCode(name) % count); + if (index < 0) { + // index is twos complement of location attributes index. + return -index - 1; + } else if (index > 0) { + // index is hash seed needed to compute location attributes index. + return ImageStringsReader.hashCode(name, index) % count; + } else { + // No entry. + return -1; + } + } + + private int getLocationIndex(String module, String name) { + int count = header.getTableLength(); + int index = redirect.get(ImageStringsReader.hashCode(module, name) % count); + if (index < 0) { + // index is twos complement of location attributes index. + return -index - 1; + } else if (index > 0) { + // index is hash seed needed to compute location attributes index. + return ImageStringsReader.hashCode(module, name, index) % count; + } else { + // No entry. + return -1; + } + } + + public String[] getEntryNames() { + int[] attributeOffsets = new int[offsets.capacity()]; + offsets.get(attributeOffsets); + return IntStream.of(attributeOffsets) + .filter(o -> o != 0) + .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) + .sorted() + .toArray(String[]::new); + } + + ImageLocation getLocation(int offset) { + return ImageLocation.readFrom(this, offset); + } + + public long[] getAttributes(int offset) { + if (offset < 0 || offset >= locations.limit()) { + throw new IndexOutOfBoundsException("offset"); + } + return ImageLocation.decompress(locations, offset); + } + + public String getString(int offset) { + if (offset < 0 || offset >= strings.limit()) { + throw new IndexOutOfBoundsException("offset"); + } + return ImageStringsReader.stringFromByteBuffer(strings, offset); + } + + public int match(int offset, String string, int stringOffset) { + if (offset < 0 || offset >= strings.limit()) { + throw new IndexOutOfBoundsException("offset"); + } + return ImageStringsReader.stringFromByteBufferMatches(strings, offset, string, stringOffset); + } + + private byte[] getBufferBytes(ByteBuffer buffer) { + Objects.requireNonNull(buffer); + byte[] bytes = new byte[buffer.limit()]; + buffer.get(bytes); + + return bytes; + } + + private ByteBuffer readBuffer(long offset, long size) { + if (offset < 0 || Integer.MAX_VALUE <= offset) { + throw new IndexOutOfBoundsException("Bad offset: " + offset); + } + + if (size < 0 || Integer.MAX_VALUE <= size) { + throw new IndexOutOfBoundsException("Bad size: " + size); + } + + if (MAP_ALL) { + ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size); + buffer.order(ByteOrder.BIG_ENDIAN); + + return buffer; + } else { + if (channel == null) { + throw new InternalError("Image file channel not open"); + } + + ByteBuffer buffer = ImageBufferCache.getBuffer(size); + int read; + try { + read = channel.read(buffer, offset); + buffer.rewind(); + } catch (IOException ex) { + ImageBufferCache.releaseBuffer(buffer); + throw new RuntimeException(ex); + } + + if (read != size) { + ImageBufferCache.releaseBuffer(buffer); + throw new RuntimeException("Short read: " + read + + " instead of " + size + " bytes"); + } + + return buffer; + } + } + + public byte[] getResource(String name) { + Objects.requireNonNull(name); + ImageLocation location = findLocation(name); + + return location != null ? getResource(location) : null; + } + + public byte[] getResource(ImageLocation loc) { + ByteBuffer buffer = getResourceBuffer(loc); + + if (buffer != null) { + byte[] bytes = getBufferBytes(buffer); + ImageBufferCache.releaseBuffer(buffer); + + return bytes; + } + + return null; + } + + public ByteBuffer getResourceBuffer(ImageLocation loc) { + Objects.requireNonNull(loc); + long offset = loc.getContentOffset() + indexSize; + long compressedSize = loc.getCompressedSize(); + long uncompressedSize = loc.getUncompressedSize(); + + if (compressedSize < 0 || Integer.MAX_VALUE < compressedSize) { + throw new IndexOutOfBoundsException( + "Bad compressed size: " + compressedSize); + } + + if (uncompressedSize < 0 || Integer.MAX_VALUE < uncompressedSize) { + throw new IndexOutOfBoundsException( + "Bad uncompressed size: " + uncompressedSize); + } + + if (compressedSize == 0) { + return readBuffer(offset, uncompressedSize); + } else { + ByteBuffer buffer = readBuffer(offset, compressedSize); + + if (buffer != null) { + byte[] bytesIn = getBufferBytes(buffer); + ImageBufferCache.releaseBuffer(buffer); + byte[] bytesOut; + + try { + bytesOut = decompressor.decompressResource(byteOrder, + (int strOffset) -> getString(strOffset), bytesIn); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + return ByteBuffer.wrap(bytesOut); + } + } + + return null; + } + + public InputStream getResourceStream(ImageLocation loc) { + Objects.requireNonNull(loc); + byte[] bytes = getResource(loc); + + return new ByteArrayInputStream(bytes); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageBufferCache$1.class b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache$1.class new file mode 100644 index 00000000..9e4f0e3c Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache$1.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageBufferCache$2.class b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache$2.class new file mode 100644 index 00000000..a8b528e6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache$2.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageBufferCache.class b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache.class new file mode 100644 index 00000000..c16cce98 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageBufferCache.java b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache.java new file mode 100644 index 00000000..c2cc4a02 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageBufferCache.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage; + +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrt-fs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +class ImageBufferCache { + private static final int MAX_CACHED_BUFFERS = 3; + private static final int LARGE_BUFFER = 0x10000; + + /* + * We used to have a class BufferReference extending from WeakReference. + * BufferReference class had an instance field called "capacity". This field was + * used to make DECREASING_CAPACITY_NULLS_LAST comparator stable in the presence + * of GC clearing the WeakReference concurrently. + * + * But this scheme results in metaspace leak. The thread local is alive till the + * the thread is alive. And so ImageBufferCache$BufferReference class was kept alive. + * Because this class and ImageBufferCache$BufferReference are all loaded by a URL + * class loader from jrt-fs.jar, the class loader and so all the classes loaded by it + * were alive! + * + * Solution is to avoid using a URL loader loaded class type with thread local. All we + * need is a pair of WeakReference, Integer (saved capacity for stability + * of comparator). We use Map.Entry as pair implementation. With this, all types used + * with thread local are bootstrap types and so no metaspace leak. + */ + @SuppressWarnings("unchecked") + private static final ThreadLocal, Integer>[]> CACHE = + new ThreadLocal, Integer>[]>() { + @Override + protected Map.Entry, Integer>[] initialValue() { + // 1 extra slot to simplify logic of releaseBuffer() + return (Map.Entry, Integer>[])new Map.Entry[MAX_CACHED_BUFFERS + 1]; + } + }; + + private static ByteBuffer allocateBuffer(long size) { + return ByteBuffer.allocateDirect((int)((size + 0xFFF) & ~0xFFF)); + } + + static ByteBuffer getBuffer(long size) { + if (size < 0 || Integer.MAX_VALUE < size) { + throw new IndexOutOfBoundsException("size"); + } + + ByteBuffer result = null; + + if (size > LARGE_BUFFER) { + result = allocateBuffer(size); + } else { + Map.Entry, Integer>[] cache = CACHE.get(); + + // buffers are ordered by decreasing capacity + // cache[MAX_CACHED_BUFFERS] is always null + for (int i = MAX_CACHED_BUFFERS - 1; i >= 0; i--) { + Map.Entry, Integer> reference = cache[i]; + + if (reference != null) { + ByteBuffer buffer = getByteBuffer(reference); + + if (buffer != null && size <= buffer.capacity()) { + cache[i] = null; + result = buffer; + result.rewind(); + break; + } + } + } + + if (result == null) { + result = allocateBuffer(size); + } + } + + result.limit((int)size); + + return result; + } + + static void releaseBuffer(ByteBuffer buffer) { + if (buffer.capacity() > LARGE_BUFFER) { + return; + } + + Map.Entry, Integer>[] cache = CACHE.get(); + + // expunge cleared BufferRef(s) + for (int i = 0; i < MAX_CACHED_BUFFERS; i++) { + Map.Entry, Integer> reference = cache[i]; + if (reference != null && getByteBuffer(reference) == null) { + cache[i] = null; + } + } + + // insert buffer back with new BufferRef wrapping it + cache[MAX_CACHED_BUFFERS] = newCacheEntry(buffer); + Arrays.sort(cache, DECREASING_CAPACITY_NULLS_LAST); + // squeeze the smallest one out + cache[MAX_CACHED_BUFFERS] = null; + } + + private static Map.Entry, Integer> newCacheEntry(ByteBuffer bb) { + return new AbstractMap.SimpleEntry, Integer>( + new WeakReference(bb), bb.capacity()); + } + + private static int getCapacity(Map.Entry, Integer> e) { + return e == null? 0 : e.getValue(); + } + + private static ByteBuffer getByteBuffer(Map.Entry, Integer> e) { + return e == null? null : e.getKey().get(); + } + + private static Comparator, Integer>> DECREASING_CAPACITY_NULLS_LAST = + new Comparator, Integer>>() { + @Override + public int compare(Map.Entry, Integer> br1, + Map.Entry, Integer> br2) { + return Integer.compare(getCapacity(br1), getCapacity(br2)); + } + }; +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageHeader.class b/tests/test_data/std/jdk/internal/jimage/ImageHeader.class new file mode 100644 index 00000000..783e9a84 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageHeader.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageHeader.java b/tests/test_data/std/jdk/internal/jimage/ImageHeader.java new file mode 100644 index 00000000..f6366511 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageHeader.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.Objects; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class ImageHeader { + public static final int MAGIC = 0xCAFEDADA; + public static final int MAJOR_VERSION = 1; + public static final int MINOR_VERSION = 0; + private static final int HEADER_SLOTS = 7; + + private final int magic; + private final int majorVersion; + private final int minorVersion; + private final int flags; + private final int resourceCount; + private final int tableLength; + private final int locationsSize; + private final int stringsSize; + + public ImageHeader(int resourceCount, int tableCount, + int locationsSize, int stringsSize) { + this(MAGIC, MAJOR_VERSION, MINOR_VERSION, 0, resourceCount, + tableCount, locationsSize, stringsSize); + } + + public ImageHeader(int magic, int majorVersion, int minorVersion, + int flags, int resourceCount, + int tableLength, int locationsSize, int stringsSize) + { + this.magic = magic; + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.flags = flags; + this.resourceCount = resourceCount; + this.tableLength = tableLength; + this.locationsSize = locationsSize; + this.stringsSize = stringsSize; + } + + public static int getHeaderSize() { + return HEADER_SLOTS * 4; + } + + static ImageHeader readFrom(IntBuffer buffer) { + Objects.requireNonNull(buffer); + + if (buffer.capacity() != HEADER_SLOTS) { + throw new InternalError( + "jimage header not the correct size: " + buffer.capacity()); + } + + int magic = buffer.get(0); + int version = buffer.get(1); + int majorVersion = version >>> 16; + int minorVersion = version & 0xFFFF; + int flags = buffer.get(2); + int resourceCount = buffer.get(3); + int tableLength = buffer.get(4); + int locationsSize = buffer.get(5); + int stringsSize = buffer.get(6); + + return new ImageHeader(magic, majorVersion, minorVersion, flags, + resourceCount, tableLength, locationsSize, stringsSize); + } + + public void writeTo(ImageStream stream) { + Objects.requireNonNull(stream); + stream.ensure(getHeaderSize()); + writeTo(stream.getBuffer()); + } + + public void writeTo(ByteBuffer buffer) { + Objects.requireNonNull(buffer); + buffer.putInt(magic); + buffer.putInt(majorVersion << 16 | minorVersion); + buffer.putInt(flags); + buffer.putInt(resourceCount); + buffer.putInt(tableLength); + buffer.putInt(locationsSize); + buffer.putInt(stringsSize); + } + + public int getMagic() { + return magic; + } + + public int getMajorVersion() { + return majorVersion; + } + + public int getMinorVersion() { + return minorVersion; + } + + public int getFlags() { + return flags; + } + + public int getResourceCount() { + return resourceCount; + } + + public int getTableLength() { + return tableLength; + } + + public int getRedirectSize() { + return tableLength * 4; + } + + public int getOffsetsSize() { + return tableLength * 4; + } + + public int getLocationsSize() { + return locationsSize; + } + + public int getStringsSize() { + return stringsSize; + } + + public int getIndexSize() { + return getHeaderSize() + + getRedirectSize() + + getOffsetsSize() + + getLocationsSize() + + getStringsSize(); + } + + int getRedirectOffset() { + return getHeaderSize(); + } + + int getOffsetsOffset() { + return getRedirectOffset() + + getRedirectSize(); + } + + int getLocationsOffset() { + return getOffsetsOffset() + + getOffsetsSize(); + } + + int getStringsOffset() { + return getLocationsOffset() + + getLocationsSize(); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageLocation.class b/tests/test_data/std/jdk/internal/jimage/ImageLocation.class new file mode 100644 index 00000000..8e886c41 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageLocation.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageLocation.java b/tests/test_data/std/jdk/internal/jimage/ImageLocation.java new file mode 100644 index 00000000..f31c7291 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageLocation.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class ImageLocation { + public static final int ATTRIBUTE_END = 0; + public static final int ATTRIBUTE_MODULE = 1; + public static final int ATTRIBUTE_PARENT = 2; + public static final int ATTRIBUTE_BASE = 3; + public static final int ATTRIBUTE_EXTENSION = 4; + public static final int ATTRIBUTE_OFFSET = 5; + public static final int ATTRIBUTE_COMPRESSED = 6; + public static final int ATTRIBUTE_UNCOMPRESSED = 7; + public static final int ATTRIBUTE_COUNT = 8; + + protected final long[] attributes; + + protected final ImageStrings strings; + + public ImageLocation(long[] attributes, ImageStrings strings) { + this.attributes = Objects.requireNonNull(attributes); + this.strings = Objects.requireNonNull(strings); + } + + ImageStrings getStrings() { + return strings; + } + + static long[] decompress(ByteBuffer bytes, int offset) { + Objects.requireNonNull(bytes); + long[] attributes = new long[ATTRIBUTE_COUNT]; + + int limit = bytes.limit(); + while (offset < limit) { + int data = bytes.get(offset++) & 0xFF; + if (data <= 0x7) { // ATTRIBUTE_END + break; + } + int kind = data >>> 3; + if (ATTRIBUTE_COUNT <= kind) { + throw new InternalError( + "Invalid jimage attribute kind: " + kind); + } + + int length = (data & 0x7) + 1; + attributes[kind] = readValue(length, bytes, offset, limit); + offset += length; + } + return attributes; + } + + public static byte[] compress(long[] attributes) { + Objects.requireNonNull(attributes); + ImageStream stream = new ImageStream(16); + + for (int kind = ATTRIBUTE_END + 1; kind < ATTRIBUTE_COUNT; kind++) { + long value = attributes[kind]; + + if (value != 0) { + int n = (63 - Long.numberOfLeadingZeros(value)) >> 3; + stream.put((kind << 3) | n); + + for (int i = n; i >= 0; i--) { + stream.put((int)(value >> (i << 3))); + } + } + } + + stream.put(ATTRIBUTE_END << 3); + + return stream.toArray(); + } + + public boolean verify(String name) { + return verify(name, attributes, strings); + } + + /** + * A simpler verification would be {@code name.equals(getFullName())}, but + * by not creating the full name and enabling early returns we allocate + * fewer objects. + */ + static boolean verify(String name, long[] attributes, ImageStrings strings) { + Objects.requireNonNull(name); + final int length = name.length(); + int index = 0; + int moduleOffset = (int)attributes[ATTRIBUTE_MODULE]; + if (moduleOffset != 0 && length >= 1) { + int moduleLen = strings.match(moduleOffset, name, 1); + index = moduleLen + 1; + if (moduleLen < 0 + || length <= index + || name.charAt(0) != '/' + || name.charAt(index++) != '/') { + return false; + } + } + return verifyName(null, name, index, length, 0, + (int) attributes[ATTRIBUTE_PARENT], + (int) attributes[ATTRIBUTE_BASE], + (int) attributes[ATTRIBUTE_EXTENSION], + strings); + } + + static boolean verify(String module, String name, ByteBuffer locations, + int locationOffset, ImageStrings strings) { + int moduleOffset = 0; + int parentOffset = 0; + int baseOffset = 0; + int extOffset = 0; + + int limit = locations.limit(); + while (locationOffset < limit) { + int data = locations.get(locationOffset++) & 0xFF; + if (data <= 0x7) { // ATTRIBUTE_END + break; + } + int kind = data >>> 3; + if (ATTRIBUTE_COUNT <= kind) { + throw new InternalError( + "Invalid jimage attribute kind: " + kind); + } + + int length = (data & 0x7) + 1; + switch (kind) { + case ATTRIBUTE_MODULE: + moduleOffset = (int) readValue(length, locations, locationOffset, limit); + break; + case ATTRIBUTE_BASE: + baseOffset = (int) readValue(length, locations, locationOffset, limit); + break; + case ATTRIBUTE_PARENT: + parentOffset = (int) readValue(length, locations, locationOffset, limit); + break; + case ATTRIBUTE_EXTENSION: + extOffset = (int) readValue(length, locations, locationOffset, limit); + break; + } + locationOffset += length; + } + return verifyName(module, name, 0, name.length(), + moduleOffset, parentOffset, baseOffset, extOffset, strings); + } + + private static long readValue(int length, ByteBuffer buffer, int offset, int limit) { + long value = 0; + for (int j = 0; j < length; j++) { + value <<= 8; + if (offset >= limit) { + throw new InternalError("Missing jimage attribute data"); + } + value |= buffer.get(offset++) & 0xFF; + } + return value; + } + + static boolean verify(String module, String name, long[] attributes, + ImageStrings strings) { + Objects.requireNonNull(module); + Objects.requireNonNull(name); + return verifyName(module, name, 0, name.length(), + (int) attributes[ATTRIBUTE_MODULE], + (int) attributes[ATTRIBUTE_PARENT], + (int) attributes[ATTRIBUTE_BASE], + (int) attributes[ATTRIBUTE_EXTENSION], + strings); + } + + private static boolean verifyName(String module, String name, int index, int length, + int moduleOffset, int parentOffset, int baseOffset, int extOffset, ImageStrings strings) { + + if (moduleOffset != 0) { + if (strings.match(moduleOffset, module, 0) != module.length()) { + return false; + } + } + if (parentOffset != 0) { + int parentLen = strings.match(parentOffset, name, index); + if (parentLen < 0) { + return false; + } + index += parentLen; + if (length <= index || name.charAt(index++) != '/') { + return false; + } + } + int baseLen = strings.match(baseOffset, name, index); + if (baseLen < 0) { + return false; + } + index += baseLen; + if (extOffset != 0) { + if (length <= index + || name.charAt(index++) != '.') { + return false; + } + + int extLen = strings.match(extOffset, name, index); + if (extLen < 0) { + return false; + } + index += extLen; + } + return length == index; + } + + long getAttribute(int kind) { + if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) { + throw new InternalError( + "Invalid jimage attribute kind: " + kind); + } + return attributes[kind]; + } + + String getAttributeString(int kind) { + if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) { + throw new InternalError( + "Invalid jimage attribute kind: " + kind); + } + return getStrings().get((int)attributes[kind]); + } + + public String getModule() { + return getAttributeString(ATTRIBUTE_MODULE); + } + + public int getModuleOffset() { + return (int)getAttribute(ATTRIBUTE_MODULE); + } + + public String getBase() { + return getAttributeString(ATTRIBUTE_BASE); + } + + public int getBaseOffset() { + return (int)getAttribute(ATTRIBUTE_BASE); + } + + public String getParent() { + return getAttributeString(ATTRIBUTE_PARENT); + } + + public int getParentOffset() { + return (int)getAttribute(ATTRIBUTE_PARENT); + } + + public String getExtension() { + return getAttributeString(ATTRIBUTE_EXTENSION); + } + + public int getExtensionOffset() { + return (int)getAttribute(ATTRIBUTE_EXTENSION); + } + + public String getFullName() { + return getFullName(false); + } + + public String getFullName(boolean modulesPrefix) { + StringBuilder builder = new StringBuilder(); + + if (getModuleOffset() != 0) { + if (modulesPrefix) { + builder.append("/modules"); + } + + builder.append('/'); + builder.append(getModule()); + builder.append('/'); + } + + if (getParentOffset() != 0) { + builder.append(getParent()); + builder.append('/'); + } + + builder.append(getBase()); + + if (getExtensionOffset() != 0) { + builder.append('.'); + builder.append(getExtension()); + } + + return builder.toString(); + } + + String buildName(boolean includeModule, boolean includeParent, + boolean includeName) { + StringBuilder builder = new StringBuilder(); + + if (includeModule && getModuleOffset() != 0) { + builder.append("/modules/"); + builder.append(getModule()); + } + + if (includeParent && getParentOffset() != 0) { + builder.append('/'); + builder.append(getParent()); + } + + if (includeName) { + if (includeModule || includeParent) { + builder.append('/'); + } + + builder.append(getBase()); + + if (getExtensionOffset() != 0) { + builder.append('.'); + builder.append(getExtension()); + } + } + + return builder.toString(); + } + + public long getContentOffset() { + return getAttribute(ATTRIBUTE_OFFSET); + } + + public long getCompressedSize() { + return getAttribute(ATTRIBUTE_COMPRESSED); + } + + public long getUncompressedSize() { + return getAttribute(ATTRIBUTE_UNCOMPRESSED); + } + + static ImageLocation readFrom(BasicImageReader reader, int offset) { + Objects.requireNonNull(reader); + long[] attributes = reader.getAttributes(offset); + ImageStringsReader strings = reader.getStrings(); + + return new ImageLocation(attributes, strings); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader$Directory.class b/tests/test_data/std/jdk/internal/jimage/ImageReader$Directory.class new file mode 100644 index 00000000..4f7574e0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader$Directory.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader$LinkNode.class b/tests/test_data/std/jdk/internal/jimage/ImageReader$LinkNode.class new file mode 100644 index 00000000..a4915ae0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader$LinkNode.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader$Node.class b/tests/test_data/std/jdk/internal/jimage/ImageReader$Node.class new file mode 100644 index 00000000..8172c516 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader$Node.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader$Resource.class b/tests/test_data/std/jdk/internal/jimage/ImageReader$Resource.class new file mode 100644 index 00000000..51fa6c55 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader$Resource.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader$SharedImageReader$LocationVisitor.class b/tests/test_data/std/jdk/internal/jimage/ImageReader$SharedImageReader$LocationVisitor.class new file mode 100644 index 00000000..3fae44c9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader$SharedImageReader$LocationVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader$SharedImageReader.class b/tests/test_data/std/jdk/internal/jimage/ImageReader$SharedImageReader.class new file mode 100644 index 00000000..d54436e5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader$SharedImageReader.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader.class b/tests/test_data/std/jdk/internal/jimage/ImageReader.class new file mode 100644 index 00000000..8be01a18 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReader.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReader.java b/tests/test_data/std/jdk/internal/jimage/ImageReader.java new file mode 100644 index 00000000..f12c39f3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageReader.java @@ -0,0 +1,858 @@ +/* + * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class ImageReader implements AutoCloseable { + private final SharedImageReader reader; + + private volatile boolean closed; + + private ImageReader(SharedImageReader reader) { + this.reader = reader; + } + + public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { + Objects.requireNonNull(imagePath); + Objects.requireNonNull(byteOrder); + + return SharedImageReader.open(imagePath, byteOrder); + } + + public static ImageReader open(Path imagePath) throws IOException { + return open(imagePath, ByteOrder.nativeOrder()); + } + + @Override + public void close() throws IOException { + if (closed) { + throw new IOException("image file already closed"); + } + reader.close(this); + closed = true; + } + + private void ensureOpen() throws IOException { + if (closed) { + throw new IOException("image file closed"); + } + } + + private void requireOpen() { + if (closed) { + throw new IllegalStateException("image file closed"); + } + } + + // directory management interface + public Directory getRootDirectory() throws IOException { + ensureOpen(); + return reader.getRootDirectory(); + } + + + public Node findNode(String name) throws IOException { + ensureOpen(); + return reader.findNode(name); + } + + public byte[] getResource(Node node) throws IOException { + ensureOpen(); + return reader.getResource(node); + } + + public byte[] getResource(Resource rs) throws IOException { + ensureOpen(); + return reader.getResource(rs); + } + + public ImageHeader getHeader() { + requireOpen(); + return reader.getHeader(); + } + + public static void releaseByteBuffer(ByteBuffer buffer) { + BasicImageReader.releaseByteBuffer(buffer); + } + + public String getName() { + requireOpen(); + return reader.getName(); + } + + public ByteOrder getByteOrder() { + requireOpen(); + return reader.getByteOrder(); + } + + public Path getImagePath() { + requireOpen(); + return reader.getImagePath(); + } + + public ImageStringsReader getStrings() { + requireOpen(); + return reader.getStrings(); + } + + public ImageLocation findLocation(String mn, String rn) { + requireOpen(); + return reader.findLocation(mn, rn); + } + + public boolean verifyLocation(String mn, String rn) { + requireOpen(); + return reader.verifyLocation(mn, rn); + } + + public ImageLocation findLocation(String name) { + requireOpen(); + return reader.findLocation(name); + } + + public String[] getEntryNames() { + requireOpen(); + return reader.getEntryNames(); + } + + public String[] getModuleNames() { + requireOpen(); + int off = "/modules/".length(); + return reader.findNode("/modules") + .getChildren() + .stream() + .map(Node::getNameString) + .map(s -> s.substring(off, s.length())) + .toArray(String[]::new); + } + + public long[] getAttributes(int offset) { + requireOpen(); + return reader.getAttributes(offset); + } + + public String getString(int offset) { + requireOpen(); + return reader.getString(offset); + } + + public byte[] getResource(String name) { + requireOpen(); + return reader.getResource(name); + } + + public byte[] getResource(ImageLocation loc) { + requireOpen(); + return reader.getResource(loc); + } + + public ByteBuffer getResourceBuffer(ImageLocation loc) { + requireOpen(); + return reader.getResourceBuffer(loc); + } + + public InputStream getResourceStream(ImageLocation loc) { + requireOpen(); + return reader.getResourceStream(loc); + } + + private static final class SharedImageReader extends BasicImageReader { + static final int SIZE_OF_OFFSET = Integer.BYTES; + + static final Map OPEN_FILES = new HashMap<>(); + + // List of openers for this shared image. + final Set openers; + + // attributes of the .jimage file. jimage file does not contain + // attributes for the individual resources (yet). We use attributes + // of the jimage file itself (creation, modification, access times). + // Iniitalized lazily, see {@link #imageFileAttributes()}. + BasicFileAttributes imageFileAttributes; + + // directory management implementation + final HashMap nodes; + volatile Directory rootDir; + + Directory packagesDir; + Directory modulesDir; + + private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException { + super(imagePath, byteOrder); + this.openers = new HashSet<>(); + this.nodes = new HashMap<>(); + } + + public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { + Objects.requireNonNull(imagePath); + Objects.requireNonNull(byteOrder); + + synchronized (OPEN_FILES) { + SharedImageReader reader = OPEN_FILES.get(imagePath); + + if (reader == null) { + // Will fail with an IOException if wrong byteOrder. + reader = new SharedImageReader(imagePath, byteOrder); + OPEN_FILES.put(imagePath, reader); + } else if (reader.getByteOrder() != byteOrder) { + throw new IOException("\"" + reader.getName() + "\" is not an image file"); + } + + ImageReader image = new ImageReader(reader); + reader.openers.add(image); + + return image; + } + } + + public void close(ImageReader image) throws IOException { + Objects.requireNonNull(image); + + synchronized (OPEN_FILES) { + if (!openers.remove(image)) { + throw new IOException("image file already closed"); + } + + if (openers.isEmpty()) { + close(); + nodes.clear(); + rootDir = null; + + if (!OPEN_FILES.remove(this.getImagePath(), this)) { + throw new IOException("image file not found in open list"); + } + } + } + } + + void addOpener(ImageReader reader) { + synchronized (OPEN_FILES) { + openers.add(reader); + } + } + + boolean removeOpener(ImageReader reader) { + synchronized (OPEN_FILES) { + return openers.remove(reader); + } + } + + // directory management interface + Directory getRootDirectory() { + return buildRootDirectory(); + } + + /** + * Lazily build a node from a name. + */ + synchronized Node buildNode(String name) { + Node n; + boolean isPackages = name.startsWith("/packages"); + boolean isModules = !isPackages && name.startsWith("/modules"); + + if (!(isModules || isPackages)) { + return null; + } + + ImageLocation loc = findLocation(name); + + if (loc != null) { // A sub tree node + if (isPackages) { + n = handlePackages(name, loc); + } else { // modules sub tree + n = handleModulesSubTree(name, loc); + } + } else { // Asking for a resource? /modules/java.base/java/lang/Object.class + if (isModules) { + n = handleResource(name); + } else { + // Possibly ask for /packages/java.lang/java.base + // although /packages/java.base not created + n = handleModuleLink(name); + } + } + return n; + } + + synchronized Directory buildRootDirectory() { + Directory root = rootDir; // volatile read + if (root != null) { + return root; + } + + root = newDirectory(null, "/"); + root.setIsRootDir(); + + // /packages dir + packagesDir = newDirectory(root, "/packages"); + packagesDir.setIsPackagesDir(); + + // /modules dir + modulesDir = newDirectory(root, "/modules"); + modulesDir.setIsModulesDir(); + + root.setCompleted(true); + return rootDir = root; + } + + /** + * To visit sub tree resources. + */ + interface LocationVisitor { + void visit(ImageLocation loc); + } + + void visitLocation(ImageLocation loc, LocationVisitor visitor) { + byte[] offsets = getResource(loc); + ByteBuffer buffer = ByteBuffer.wrap(offsets); + buffer.order(getByteOrder()); + IntBuffer intBuffer = buffer.asIntBuffer(); + for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) { + int offset = intBuffer.get(i); + ImageLocation pkgLoc = getLocation(offset); + visitor.visit(pkgLoc); + } + } + + void visitPackageLocation(ImageLocation loc) { + // Retrieve package name + String pkgName = getBaseExt(loc); + // Content is array of offsets in Strings table + byte[] stringsOffsets = getResource(loc); + ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets); + buffer.order(getByteOrder()); + IntBuffer intBuffer = buffer.asIntBuffer(); + // For each module, create a link node. + for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) { + // skip empty state, useless. + intBuffer.get(i); + i++; + int offset = intBuffer.get(i); + String moduleName = getString(offset); + Node targetNode = findNode("/modules/" + moduleName); + if (targetNode != null) { + String pkgDirName = packagesDir.getName() + "/" + pkgName; + Directory pkgDir = (Directory) nodes.get(pkgDirName); + newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode); + } + } + } + + Node handlePackages(String name, ImageLocation loc) { + long size = loc.getUncompressedSize(); + Node n = null; + // Only possibilities are /packages, /packages/package/module + if (name.equals("/packages")) { + visitLocation(loc, (childloc) -> { + findNode(childloc.getFullName()); + }); + packagesDir.setCompleted(true); + n = packagesDir; + } else { + if (size != 0) { // children are offsets to module in StringsTable + String pkgName = getBaseExt(loc); + Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName); + visitPackageLocation(loc); + pkgDir.setCompleted(true); + n = pkgDir; + } else { // Link to module + String pkgName = loc.getParent(); + String modName = getBaseExt(loc); + Node targetNode = findNode("/modules/" + modName); + if (targetNode != null) { + String pkgDirName = packagesDir.getName() + "/" + pkgName; + Directory pkgDir = (Directory) nodes.get(pkgDirName); + Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode); + n = linkNode; + } + } + } + return n; + } + + // Asking for /packages/package/module although + // /packages// not yet created, need to create it + // prior to return the link to module node. + Node handleModuleLink(String name) { + // eg: unresolved /packages/package/module + // Build /packages/package node + Node ret = null; + String radical = "/packages/"; + String path = name; + if (path.startsWith(radical)) { + int start = radical.length(); + int pkgEnd = path.indexOf('/', start); + if (pkgEnd != -1) { + String pkg = path.substring(start, pkgEnd); + String pkgPath = radical + pkg; + Node n = findNode(pkgPath); + // If not found means that this is a symbolic link such as: + // /packages/java.util/java.base/java/util/Vector.class + // and will be done by a retry of the filesystem + for (Node child : n.getChildren()) { + if (child.name.equals(name)) { + ret = child; + break; + } + } + } + } + return ret; + } + + Node handleModulesSubTree(String name, ImageLocation loc) { + Node n; + assert (name.equals(loc.getFullName())); + Directory dir = makeDirectories(name); + visitLocation(loc, (childloc) -> { + String path = childloc.getFullName(); + if (path.startsWith("/modules")) { // a package + makeDirectories(path); + } else { // a resource + makeDirectories(childloc.buildName(true, true, false)); + // if we have already created a resource for this name previously, then don't + // recreate it + if (!nodes.containsKey(childloc.getFullName(true))) { + newResource(dir, childloc); + } + } + }); + dir.setCompleted(true); + n = dir; + return n; + } + + Node handleResource(String name) { + Node n = null; + if (!name.startsWith("/modules/")) { + return null; + } + // Make sure that the thing that follows "/modules/" is a module name. + int moduleEndIndex = name.indexOf('/', "/modules/".length()); + if (moduleEndIndex == -1) { + return null; + } + ImageLocation moduleLoc = findLocation(name.substring(0, moduleEndIndex)); + if (moduleLoc == null || moduleLoc.getModuleOffset() == 0) { + return null; + } + + String locationPath = name.substring("/modules".length()); + ImageLocation resourceLoc = findLocation(locationPath); + if (resourceLoc != null) { + Directory dir = makeDirectories(resourceLoc.buildName(true, true, false)); + Resource res = newResource(dir, resourceLoc); + n = res; + } + return n; + } + + String getBaseExt(ImageLocation loc) { + String base = loc.getBase(); + String ext = loc.getExtension(); + if (ext != null && !ext.isEmpty()) { + base = base + "." + ext; + } + return base; + } + + synchronized Node findNode(String name) { + buildRootDirectory(); + Node n = nodes.get(name); + if (n == null || !n.isCompleted()) { + n = buildNode(name); + } + return n; + } + + /** + * Returns the file attributes of the image file. + */ + BasicFileAttributes imageFileAttributes() { + BasicFileAttributes attrs = imageFileAttributes; + if (attrs == null) { + try { + Path file = getImagePath(); + attrs = Files.readAttributes(file, BasicFileAttributes.class); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + imageFileAttributes = attrs; + } + return attrs; + } + + Directory newDirectory(Directory parent, String name) { + Directory dir = Directory.create(parent, name, imageFileAttributes()); + nodes.put(dir.getName(), dir); + return dir; + } + + Resource newResource(Directory parent, ImageLocation loc) { + Resource res = Resource.create(parent, loc, imageFileAttributes()); + nodes.put(res.getName(), res); + return res; + } + + LinkNode newLinkNode(Directory dir, String name, Node link) { + LinkNode linkNode = LinkNode.create(dir, name, link); + nodes.put(linkNode.getName(), linkNode); + return linkNode; + } + + Directory makeDirectories(String parent) { + Directory last = rootDir; + for (int offset = parent.indexOf('/', 1); + offset != -1; + offset = parent.indexOf('/', offset + 1)) { + String dir = parent.substring(0, offset); + last = makeDirectory(dir, last); + } + return makeDirectory(parent, last); + + } + + Directory makeDirectory(String dir, Directory last) { + Directory nextDir = (Directory) nodes.get(dir); + if (nextDir == null) { + nextDir = newDirectory(last, dir); + } + return nextDir; + } + + byte[] getResource(Node node) throws IOException { + if (node.isResource()) { + return super.getResource(node.getLocation()); + } + throw new IOException("Not a resource: " + node); + } + + byte[] getResource(Resource rs) throws IOException { + return super.getResource(rs.getLocation()); + } + } + + // jimage file does not store directory structure. We build nodes + // using the "path" strings found in the jimage file. + // Node can be a directory or a resource + public abstract static class Node { + private static final int ROOT_DIR = 0b0000_0000_0000_0001; + private static final int PACKAGES_DIR = 0b0000_0000_0000_0010; + private static final int MODULES_DIR = 0b0000_0000_0000_0100; + + private int flags; + private final String name; + private final BasicFileAttributes fileAttrs; + private boolean completed; + + protected Node(String name, BasicFileAttributes fileAttrs) { + this.name = Objects.requireNonNull(name); + this.fileAttrs = Objects.requireNonNull(fileAttrs); + } + + /** + * A node is completed when all its direct children have been built. + * + * @return + */ + public boolean isCompleted() { + return completed; + } + + public void setCompleted(boolean completed) { + this.completed = completed; + } + + public final void setIsRootDir() { + flags |= ROOT_DIR; + } + + public final boolean isRootDir() { + return (flags & ROOT_DIR) != 0; + } + + public final void setIsPackagesDir() { + flags |= PACKAGES_DIR; + } + + public final boolean isPackagesDir() { + return (flags & PACKAGES_DIR) != 0; + } + + public final void setIsModulesDir() { + flags |= MODULES_DIR; + } + + public final boolean isModulesDir() { + return (flags & MODULES_DIR) != 0; + } + + public final String getName() { + return name; + } + + public final BasicFileAttributes getFileAttributes() { + return fileAttrs; + } + + // resolve this Node (if this is a soft link, get underlying Node) + public final Node resolveLink() { + return resolveLink(false); + } + + public Node resolveLink(boolean recursive) { + return this; + } + + // is this a soft link Node? + public boolean isLink() { + return false; + } + + public boolean isDirectory() { + return false; + } + + public List getChildren() { + throw new IllegalArgumentException("not a directory: " + getNameString()); + } + + public boolean isResource() { + return false; + } + + public ImageLocation getLocation() { + throw new IllegalArgumentException("not a resource: " + getNameString()); + } + + public long size() { + return 0L; + } + + public long compressedSize() { + return 0L; + } + + public String extension() { + return null; + } + + public long contentOffset() { + return 0L; + } + + public final FileTime creationTime() { + return fileAttrs.creationTime(); + } + + public final FileTime lastAccessTime() { + return fileAttrs.lastAccessTime(); + } + + public final FileTime lastModifiedTime() { + return fileAttrs.lastModifiedTime(); + } + + public final String getNameString() { + return name; + } + + @Override + public final String toString() { + return getNameString(); + } + + @Override + public final int hashCode() { + return name.hashCode(); + } + + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof Node) { + return name.equals(((Node) other).name); + } + + return false; + } + } + + // directory node - directory has full path name without '/' at end. + static final class Directory extends Node { + private final List children; + + private Directory(String name, BasicFileAttributes fileAttrs) { + super(name, fileAttrs); + children = new ArrayList<>(); + } + + static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) { + Directory d = new Directory(name, fileAttrs); + if (parent != null) { + parent.addChild(d); + } + return d; + } + + @Override + public boolean isDirectory() { + return true; + } + + @Override + public List getChildren() { + return Collections.unmodifiableList(children); + } + + void addChild(Node node) { + assert !children.contains(node) : "Child " + node + " already added"; + children.add(node); + } + + public void walk(Consumer consumer) { + consumer.accept(this); + for (Node child : children) { + if (child.isDirectory()) { + ((Directory)child).walk(consumer); + } else { + consumer.accept(child); + } + } + } + } + + // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. + // full path of the resource is the "name" of the resource. + static class Resource extends Node { + private final ImageLocation loc; + + private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) { + super(loc.getFullName(true), fileAttrs); + this.loc = loc; + } + + static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { + Resource rs = new Resource(loc, fileAttrs); + parent.addChild(rs); + return rs; + } + + @Override + public boolean isCompleted() { + return true; + } + + @Override + public boolean isResource() { + return true; + } + + @Override + public ImageLocation getLocation() { + return loc; + } + + @Override + public long size() { + return loc.getUncompressedSize(); + } + + @Override + public long compressedSize() { + return loc.getCompressedSize(); + } + + @Override + public String extension() { + return loc.getExtension(); + } + + @Override + public long contentOffset() { + return loc.getContentOffset(); + } + } + + // represents a soft link to another Node + static class LinkNode extends Node { + private final Node link; + + private LinkNode(String name, Node link) { + super(name, link.getFileAttributes()); + this.link = link; + } + + static LinkNode create(Directory parent, String name, Node link) { + LinkNode ln = new LinkNode(name, link); + parent.addChild(ln); + return ln; + } + + @Override + public boolean isCompleted() { + return true; + } + + @Override + public Node resolveLink(boolean recursive) { + return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link; + } + + @Override + public boolean isLink() { + return true; + } + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory$1.class b/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory$1.class new file mode 100644 index 00000000..40a621d2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory$1.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory.class b/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory.class new file mode 100644 index 00000000..9519a410 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory.java b/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory.java new file mode 100644 index 00000000..24ac9bfc --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageReaderFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +/** + * Factory to get ImageReader + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class ImageReaderFactory { + private ImageReaderFactory() {} + + private static final String JAVA_HOME = System.getProperty("java.home"); + private static final Path BOOT_MODULES_JIMAGE = + Paths.get(JAVA_HOME, "lib", "modules"); + + private static final Map readers = new ConcurrentHashMap<>(); + + /** + * Returns an {@code ImageReader} to read from the given image file + */ + public static ImageReader get(Path jimage) throws IOException { + Objects.requireNonNull(jimage); + try { + return readers.computeIfAbsent(jimage, OPENER); + } catch (UncheckedIOException io) { + throw io.getCause(); + } + } + + private static Function OPENER = new Function() { + public ImageReader apply(Path path) { + try { + return ImageReader.open(path); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } + }; + + /** + * Returns the {@code ImageReader} to read the image file in this + * run-time image. + * + * @throws UncheckedIOException if an I/O error occurs + */ + public static ImageReader getImageReader() { + try { + return get(BOOT_MODULES_JIMAGE); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageStream.class b/tests/test_data/std/jdk/internal/jimage/ImageStream.class new file mode 100644 index 00000000..e43fdca2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageStream.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageStream.java b/tests/test_data/std/jdk/internal/jimage/ImageStream.java new file mode 100644 index 00000000..774e0e6e --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageStream.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Objects; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class ImageStream { + private ByteBuffer buffer; + + public ImageStream() { + this(1024, ByteOrder.nativeOrder()); + } + + public ImageStream(int size) { + this(size, ByteOrder.nativeOrder()); + } + + public ImageStream(byte[] bytes) { + this(bytes, ByteOrder.nativeOrder()); + } + + public ImageStream(ByteOrder byteOrder) { + this(1024, byteOrder); + } + + public ImageStream(int size, ByteOrder byteOrder) { + buffer = ByteBuffer.allocate(size); + buffer.order(Objects.requireNonNull(byteOrder)); + } + + public ImageStream(byte[] bytes, ByteOrder byteOrder) { + buffer = ByteBuffer.wrap(Objects.requireNonNull(bytes)); + buffer.order(Objects.requireNonNull(byteOrder)); + } + + public ImageStream(ByteBuffer buffer) { + this.buffer = Objects.requireNonNull(buffer); + } + + public ImageStream align(int alignment) { + int padding = (getSize() - 1) & ((1 << alignment) - 1); + + for (int i = 0; i < padding; i++) { + put((byte)0); + } + + return this; + } + + public void ensure(int needs) { + if (needs < 0) { + throw new IndexOutOfBoundsException("Bad value: " + needs); + } + + if (needs > buffer.remaining()) { + byte[] bytes = buffer.array(); + ByteOrder byteOrder = buffer.order(); + int position = buffer.position(); + int newSize = needs <= bytes.length ? bytes.length << 1 : position + needs; + buffer = ByteBuffer.allocate(newSize); + buffer.order(byteOrder); + buffer.put(bytes, 0, position); + } + } + + public boolean hasByte() { + return buffer.remaining() != 0; + } + + public boolean hasBytes(int needs) { + return needs <= buffer.remaining(); + } + + public void skip(int n) { + if (n < 0) { + throw new IndexOutOfBoundsException("skip value = " + n); + } + + buffer.position(buffer.position() + n); + } + + public int get() { + return buffer.get() & 0xFF; + } + + public void get(byte bytes[], int offset, int size) { + buffer.get(bytes, offset, size); + } + + public int getShort() { + return buffer.getShort(); + } + + public int getInt() { + return buffer.getInt(); + } + + public long getLong() { + return buffer.getLong(); + } + + public ImageStream put(byte byt) { + ensure(1); + buffer.put(byt); + + return this; + } + + public ImageStream put(int byt) { + return put((byte)byt); + } + + public ImageStream put(byte bytes[], int offset, int size) { + ensure(size); + buffer.put(bytes, offset, size); + + return this; + } + + public ImageStream put(ImageStream stream) { + put(stream.buffer.array(), 0, stream.buffer.position()); + + return this; + } + + public ImageStream putShort(short value) { + ensure(2); + buffer.putShort(value); + + return this; + } + + public ImageStream putShort(int value) { + return putShort((short)value); + } + + public ImageStream putInt(int value) { + ensure(4); + buffer.putInt(value); + + return this; + } + + public ImageStream putLong(long value) { + ensure(8); + buffer.putLong(value); + + return this; + } + + public ByteBuffer getBuffer() { + return buffer; + } + + public int getPosition() { + return buffer.position(); + } + + public int getSize() { + return buffer.position(); + } + + public byte[] getBytes() { + return buffer.array(); + } + + public void setPosition(int offset) { + buffer.position(offset); + } + + public byte[] toArray() { + return Arrays.copyOf(buffer.array(), buffer.position()); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageStrings.class b/tests/test_data/std/jdk/internal/jimage/ImageStrings.class new file mode 100644 index 00000000..eb09fb3d Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageStrings.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageStrings.java b/tests/test_data/std/jdk/internal/jimage/ImageStrings.java new file mode 100644 index 00000000..eea62e44 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageStrings.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +/** + * @implNote This interface needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public interface ImageStrings { + String get(int offset); + + int add(final String string); + + /** + * If there's a string at {@code offset} matching in full a substring of + * {@code string} starting at {@code stringOffset}, return the length + * of that string. Otherwise returns -1. Optional operation. + */ + default int match(int offset, String string, int stringOffset) { + throw new UnsupportedOperationException(); + } + +} diff --git a/tests/test_data/std/jdk/internal/jimage/ImageStringsReader.class b/tests/test_data/std/jdk/internal/jimage/ImageStringsReader.class new file mode 100644 index 00000000..e14d9e9f Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/ImageStringsReader.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/ImageStringsReader.java b/tests/test_data/std/jdk/internal/jimage/ImageStringsReader.java new file mode 100644 index 00000000..c4601ef3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/ImageStringsReader.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +import java.io.UTFDataFormatException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class ImageStringsReader implements ImageStrings { + public static final int HASH_MULTIPLIER = 0x01000193; + public static final int POSITIVE_MASK = 0x7FFFFFFF; + + private final BasicImageReader reader; + + ImageStringsReader(BasicImageReader reader) { + this.reader = Objects.requireNonNull(reader); + } + + @Override + public String get(int offset) { + return reader.getString(offset); + } + + @Override + public int match(int offset, String string, int stringOffset) { + return reader.match(offset, string, stringOffset); + } + + @Override + public int add(final String string) { + throw new InternalError("Can not add strings at runtime"); + } + + public static int hashCode(String s) { + return hashCode(s, HASH_MULTIPLIER); + } + + public static int hashCode(String s, int seed) { + return unmaskedHashCode(s, seed) & POSITIVE_MASK; + } + + public static int hashCode(String module, String name) { + return hashCode(module, name, HASH_MULTIPLIER); + } + + public static int hashCode(String module, String name, int seed) { + seed = (seed * HASH_MULTIPLIER) ^ ('/'); + seed = unmaskedHashCode(module, seed); + seed = (seed * HASH_MULTIPLIER) ^ ('/'); + seed = unmaskedHashCode(name, seed); + return seed & POSITIVE_MASK; + } + + public static int unmaskedHashCode(String s, int seed) { + int slen = s.length(); + byte[] buffer = null; + + for (int i = 0; i < slen; i++) { + int uch = s.charAt(i); + + if ((uch & ~0x7F) != 0) { + if (buffer == null) { + buffer = new byte[8]; + } + int mask = ~0x3F; + int n = 0; + + do { + buffer[n++] = (byte)(0x80 | (uch & 0x3F)); + uch >>= 6; + mask >>= 1; + } while ((uch & mask) != 0); + + buffer[n] = (byte)((mask << 1) | uch); + + do { + seed = (seed * HASH_MULTIPLIER) ^ (buffer[n--] & 0xFF); + } while (0 <= n); + } else if (uch == 0) { + seed = (seed * HASH_MULTIPLIER) ^ (0xC0); + seed = (seed * HASH_MULTIPLIER) ^ (0x80); + } else { + seed = (seed * HASH_MULTIPLIER) ^ (uch); + } + } + return seed; + } + + static int charsFromMUTF8Length(byte[] bytes, int offset, int count) { + int length = 0; + + for (int i = offset; i < offset + count; i++) { + byte ch = bytes[i]; + + if (ch == 0) { + break; + } + + if ((ch & 0xC0) != 0x80) { + length++; + } + } + + return length; + } + + static void charsFromMUTF8(char[] chars, byte[] bytes, int offset, int count) throws UTFDataFormatException { + int j = 0; + + for (int i = offset; i < offset + count; i++) { + byte ch = bytes[i]; + + if (ch == 0) { + break; + } + + boolean is_unicode = (ch & 0x80) != 0; + int uch = ch & 0x7F; + + if (is_unicode) { + int mask = 0x40; + + while ((uch & mask) != 0) { + ch = bytes[++i]; + + if ((ch & 0xC0) != 0x80) { + throw new UTFDataFormatException("bad continuation 0x" + Integer.toHexString(ch)); + } + + uch = ((uch & ~mask) << 6) | (ch & 0x3F); + mask <<= 6 - 1; + } + + if ((uch & 0xFFFF) != uch) { + throw new UTFDataFormatException("character out of range \\u" + Integer.toHexString(uch)); + } + } + + chars[j++] = (char)uch; + } + } + + public static String stringFromMUTF8(byte[] bytes, int offset, int count) { + int length = charsFromMUTF8Length(bytes, offset, count); + char[] chars = new char[length]; + + try { + charsFromMUTF8(chars, bytes, offset, count); + } catch (UTFDataFormatException ex) { + throw new InternalError("Attempt to convert non modified UTF-8 byte sequence", ex); + } + + return new String(chars); + } + + public static String stringFromMUTF8(byte[] bytes) { + return stringFromMUTF8(bytes, 0, bytes.length); + } + + /** + * Calculates the number of characters in the String present at the + * specified offset. As an optimization, the length returned will + * be positive if the characters are all ASCII, and negative otherwise. + */ + private static int charsFromByteBufferLength(ByteBuffer buffer, int offset) { + int length = 0; + + int limit = buffer.limit(); + boolean asciiOnly = true; + while (offset < limit) { + byte ch = buffer.get(offset++); + + if (ch < 0) { + asciiOnly = false; + } else if (ch == 0) { + return asciiOnly ? length : -length; + } + + if ((ch & 0xC0) != 0x80) { + length++; + } + } + throw new InternalError("No terminating zero byte for modified UTF-8 byte sequence"); + } + + private static void charsFromByteBuffer(char[] chars, ByteBuffer buffer, int offset) { + int j = 0; + + int limit = buffer.limit(); + while (offset < limit) { + byte ch = buffer.get(offset++); + + if (ch == 0) { + return; + } + + boolean is_unicode = (ch & 0x80) != 0; + int uch = ch & 0x7F; + + if (is_unicode) { + int mask = 0x40; + + while ((uch & mask) != 0) { + ch = buffer.get(offset++); + + if ((ch & 0xC0) != 0x80) { + throw new InternalError("Bad continuation in " + + "modified UTF-8 byte sequence: " + ch); + } + + uch = ((uch & ~mask) << 6) | (ch & 0x3F); + mask <<= 6 - 1; + } + } + + if ((uch & 0xFFFF) != uch) { + throw new InternalError("UTF-32 char in modified UTF-8 " + + "byte sequence: " + uch); + } + + chars[j++] = (char)uch; + } + + throw new InternalError("No terminating zero byte for modified UTF-8 byte sequence"); + } + + public static String stringFromByteBuffer(ByteBuffer buffer) { + return stringFromByteBuffer(buffer, 0); + } + + /* package-private */ + static String stringFromByteBuffer(ByteBuffer buffer, int offset) { + int length = charsFromByteBufferLength(buffer, offset); + if (length > 0) { + byte[] asciiBytes = new byte[length]; + // Ideally we could use buffer.get(offset, asciiBytes, 0, length) + // here, but that was introduced in JDK 13 + for (int i = 0; i < length; i++) { + asciiBytes[i] = buffer.get(offset++); + } + return new String(asciiBytes, StandardCharsets.US_ASCII); + } + char[] chars = new char[-length]; + charsFromByteBuffer(chars, buffer, offset); + return new String(chars); + } + + /* package-private */ + static int stringFromByteBufferMatches(ByteBuffer buffer, int offset, String string, int stringOffset) { + // ASCII fast-path + int limit = buffer.limit(); + int current = offset; + int slen = string.length(); + while (current < limit) { + byte ch = buffer.get(current); + if (ch <= 0) { + if (ch == 0) { + // Match + return current - offset; + } + // non-ASCII byte, run slow-path from current offset + break; + } + if (slen <= stringOffset || string.charAt(stringOffset) != (char)ch) { + // No match + return -1; + } + stringOffset++; + current++; + } + // invariant: remainder of the string starting at current is non-ASCII, + // so return value from charsFromByteBufferLength will be negative + int length = -charsFromByteBufferLength(buffer, current); + char[] chars = new char[length]; + charsFromByteBuffer(chars, buffer, current); + for (int i = 0; i < length; i++) { + if (string.charAt(stringOffset++) != chars[i]) { + return -1; + } + } + return current - offset + length; + } + + static int mutf8FromStringLength(String s) { + int length = 0; + int slen = s.length(); + + for (int i = 0; i < slen; i++) { + char ch = s.charAt(i); + int uch = ch & 0xFFFF; + + if ((uch & ~0x7F) != 0) { + int mask = ~0x3F; + int n = 0; + + do { + n++; + uch >>= 6; + mask >>= 1; + } while ((uch & mask) != 0); + + length += n + 1; + } else if (uch == 0) { + length += 2; + } else { + length++; + } + } + + return length; + } + + static void mutf8FromString(byte[] bytes, int offset, String s) { + int j = offset; + byte[] buffer = null; + int slen = s.length(); + + for (int i = 0; i < slen; i++) { + char ch = s.charAt(i); + int uch = ch & 0xFFFF; + + if ((uch & ~0x7F) != 0) { + if (buffer == null) { + buffer = new byte[8]; + } + int mask = ~0x3F; + int n = 0; + + do { + buffer[n++] = (byte)(0x80 | (uch & 0x3F)); + uch >>= 6; + mask >>= 1; + } while ((uch & mask) != 0); + + buffer[n] = (byte)((mask << 1) | uch); + + do { + bytes[j++] = buffer[n--]; + } while (0 <= n); + } else if (uch == 0) { + bytes[j++] = (byte)0xC0; + bytes[j++] = (byte)0x80; + } else { + bytes[j++] = (byte)uch; + } + } + } + + public static byte[] mutf8FromString(String string) { + int length = mutf8FromStringLength(string); + byte[] bytes = new byte[length]; + mutf8FromString(bytes, 0, string); + + return bytes; + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer$1.class b/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer$1.class new file mode 100644 index 00000000..94116fcf Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer$1.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer.class b/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer.class new file mode 100644 index 00000000..887a6270 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer.java b/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer.java new file mode 100644 index 00000000..8d228c05 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/NativeImageBuffer.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage; + +import java.nio.ByteBuffer; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +@SuppressWarnings("removal") +class NativeImageBuffer { + static { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Void run() { + System.loadLibrary("jimage"); + return null; + } + }); + } + + static native ByteBuffer getNativeMap(String imagePath); +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/CompressIndexes.class b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressIndexes.class new file mode 100644 index 00000000..97399040 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressIndexes.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/CompressIndexes.java b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressIndexes.java new file mode 100644 index 00000000..709b82a8 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressIndexes.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * + * Index compressor. Use the minimal amount of bytes required to store + * an integer. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class CompressIndexes { + private static final int COMPRESSED_FLAG = 1 << (Byte.SIZE - 1); + private static final int HEADER_WIDTH = 3; + private static final int HEADER_SHIFT = Byte.SIZE - HEADER_WIDTH; + + public static List decompressFlow(byte[] values) { + List lst = new ArrayList<>(); + + for (int i = 0; i < values.length; i += getHeaderLength(values[i])) { + int decompressed = decompress(values, i); + lst.add(decompressed); + } + + return lst; + } + + public static int readInt(DataInputStream cr) throws IOException { + // Get header byte. + byte header = cr.readByte(); + // Determine size. + int size = getHeaderLength(header); + // Prepare result. + int result = getHeaderValue(header); + + // For each value byte + for (int i = 1; i < size; i++) { + // Merge byte value. + result <<= Byte.SIZE; + result |= cr.readByte() & 0xFF; + } + + return result; + } + + private static boolean isCompressed(byte b) { + return (b & COMPRESSED_FLAG) != 0; + } + + private static int getHeaderLength(byte b) { + return isCompressed(b) ? (b >> HEADER_SHIFT) & 3 : Integer.BYTES; + } + + private static int getHeaderValue(byte b) { + return isCompressed(b) ? b & (1 << HEADER_SHIFT) - 1 : b; + } + + public static int decompress(byte[] value, int offset) { + // Get header byte. + byte header = value[offset]; + // Determine size. + int size = getHeaderLength(header); + // Prepare result. + int result = getHeaderValue(header); + + // For each value byte + for (int i = 1; i < size; i++) { + // Merge byte value. + result <<= Byte.SIZE; + result |= value[offset + i] & 0xFF; + } + + return result; + } + + public static byte[] compress(int value) { + // Only positive values are supported. + if (value < 0) { + throw new IllegalArgumentException("value < 0"); + } + + // Determine number of significant digits. + int width = 32 - Integer.numberOfLeadingZeros(value); + // Determine number of byte to represent. Allow for header if + // compressed. + int size = Math.min(((width + HEADER_WIDTH - 1) >> 3) + 1, Integer.BYTES); + + // Allocate result buffer. + byte[] result = new byte[size]; + + // Insert significant bytes in result. + for (int i = 0; i < size; i++) { + result[i] = (byte)(value >> ((size - i - 1) * Byte.SIZE)); + } + + // If compressed, mark and insert size. + if (size < Integer.BYTES) { + result[0] |= (byte)(COMPRESSED_FLAG | (size << HEADER_SHIFT)); + } + + return result; + } + +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/CompressedResourceHeader.class b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressedResourceHeader.class new file mode 100644 index 00000000..5bfcd507 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressedResourceHeader.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/CompressedResourceHeader.java b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressedResourceHeader.java new file mode 100644 index 00000000..16a3b7d6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/CompressedResourceHeader.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * + * A resource header for compressed resource. This class is handled internally, + * you don't have to add header to the resource, headers are added automatically + * for compressed resources. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class CompressedResourceHeader { + + private static final int SIZE = 29; + public static final int MAGIC = 0xCAFEFAFA; + + // Standard header offsets + private static final int MAGIC_OFFSET = 0; // 4 bytes + private static final int COMPRESSED_OFFSET = 4; // 8 bytes + private static final int UNCOMPRESSED_OFFSET = 12; // 8 bytes + private static final int DECOMPRESSOR_NAME_OFFSET = 20; // 4 bytes, followed by 4 byte gap + private static final int IS_TERMINAL_OFFSET = 28; // 1 byte + + private final long uncompressedSize; + private final long compressedSize; + private final int decompressorNameOffset; + private final boolean isTerminal; + + public CompressedResourceHeader(long compressedSize, + long uncompressedSize, int decompressorNameOffset, + boolean isTerminal) { + this.compressedSize = compressedSize; + this.uncompressedSize = uncompressedSize; + this.decompressorNameOffset = decompressorNameOffset; + this.isTerminal = isTerminal; + } + + public boolean isTerminal() { + return isTerminal; + } + + public int getDecompressorNameOffset() { + return decompressorNameOffset; + } + + public long getUncompressedSize() { + return uncompressedSize; + } + + public long getResourceSize() { + return compressedSize; + } + + public byte[] getBytes(ByteOrder order) { + Objects.requireNonNull(order); + ByteBuffer buffer = ByteBuffer.allocate(SIZE); + buffer.order(order); + buffer.putInt(MAGIC); + buffer.putLong(compressedSize); + buffer.putLong(uncompressedSize); + buffer.putInt(decompressorNameOffset); + // Compatibility + buffer.putInt(-1); + buffer.put(isTerminal ? (byte)1 : (byte)0); + return buffer.array(); + } + + public static int getSize() { + return SIZE; + } + + public static CompressedResourceHeader readFromResource(ByteOrder order, + byte[] resource) { + Objects.requireNonNull(order); + Objects.requireNonNull(resource); + if (resource.length < getSize()) { + return null; + } + ByteBuffer buffer = ByteBuffer.wrap(resource, 0, SIZE); + buffer.order(order); + int magic = buffer.getInt(MAGIC_OFFSET); + if (magic != MAGIC) { + return null; + } + long size = buffer.getLong(COMPRESSED_OFFSET); + long uncompressedSize = buffer.getLong(UNCOMPRESSED_OFFSET); + int decompressorNameOffset = buffer.getInt(DECOMPRESSOR_NAME_OFFSET); + // skip unused 'contentOffset' int + byte isTerminal = buffer.get(IS_TERMINAL_OFFSET); + return new CompressedResourceHeader(size, uncompressedSize, + decompressorNameOffset, isTerminal == 1); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/Decompressor.class b/tests/test_data/std/jdk/internal/jimage/decompressor/Decompressor.class new file mode 100644 index 00000000..cf1a35fa Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/Decompressor.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/Decompressor.java b/tests/test_data/std/jdk/internal/jimage/decompressor/Decompressor.java new file mode 100644 index 00000000..3600f31c --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/Decompressor.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import jdk.internal.jimage.decompressor.ResourceDecompressor.StringsProvider; + +/** + * Entry point to decompress resources. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class Decompressor { + + private final Map pluginsCache = new HashMap<>(); + + public Decompressor() { + } + + /** + * Decompress a resource. + * @param order Byte order. + * @param provider Strings provider + * @param content The resource content to uncompress. + * @return A fully uncompressed resource. + * @throws IOException + */ + public byte[] decompressResource(ByteOrder order, StringsProvider provider, + byte[] content) throws IOException { + Objects.requireNonNull(order); + Objects.requireNonNull(provider); + Objects.requireNonNull(content); + CompressedResourceHeader header; + do { + header = CompressedResourceHeader.readFromResource(order, content); + if (header != null) { + ResourceDecompressor decompressor = + pluginsCache.get(header.getDecompressorNameOffset()); + if (decompressor == null) { + String pluginName = + provider.getString(header.getDecompressorNameOffset()); + if (pluginName == null) { + throw new IOException("Plugin name not found"); + } + decompressor = ResourceDecompressorRepository. + newResourceDecompressor(pluginName); + if (decompressor == null) { + throw new IOException("Plugin not found: " + pluginName); + } + + pluginsCache.put(header.getDecompressorNameOffset(), decompressor); + } + try { + content = decompressor.decompress(provider, content, + CompressedResourceHeader.getSize(), header.getUncompressedSize()); + } catch (Exception ex) { + throw new IOException(ex); + } + } + } while (header != null); + return content; + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor$StringsProvider.class b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor$StringsProvider.class new file mode 100644 index 00000000..49504acd Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor$StringsProvider.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor.class b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor.class new file mode 100644 index 00000000..ab85c087 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor.java b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor.java new file mode 100644 index 00000000..3e4e32d2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressor.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +/** + * + * JLink Image Decompressor. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public interface ResourceDecompressor { + + public interface StringsProvider { + public String getString(int offset); + } + /** + * Decompressor unique name. + * @return The decompressor name. + */ + public String getName(); + + /** + * Decompress a resource. + * @param strings The String provider + * @param content The resource content + * @param offset Resource content offset + * @param originalSize Uncompressed size + * @return Uncompressed resource + * @throws Exception + */ + public byte[] decompress(StringsProvider strings, byte[] content, int offset, + long originalSize) throws Exception; +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.class b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.class new file mode 100644 index 00000000..7638bde2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.java b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.java new file mode 100644 index 00000000..7c6e3265 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.IOException; + +/** + * + * JLink Resource Decompressor factory + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public abstract class ResourceDecompressorFactory { + private final String name; + + protected ResourceDecompressorFactory(String name) { + this.name = name; + } + + /** + * The Factory name. + * @return The name. + */ + public String getName() { + return name; + } + + /** + * To build a new decompressor. + * @return A new decompressor. + * @throws IOException + */ + public abstract ResourceDecompressor newDecompressor() throws IOException; + +} + diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.class b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.class new file mode 100644 index 00000000..6cff9f9a Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.java b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.java new file mode 100644 index 00000000..859f7a03 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/ResourceDecompressorRepository.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * + * JLink Decompressors. All decompressors must be registered in the static + * initializer of this class. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class ResourceDecompressorRepository { + + private ResourceDecompressorRepository() { + } + + private static final Map factories = new HashMap<>(); + + static { + registerReaderProvider(new ZipDecompressorFactory()); + registerReaderProvider(new StringSharingDecompressorFactory()); + } + + /** + * Build a new decompressor for the passed name. + * @param name The plugin name to build. + * @return A decompressor or null if not found + * @throws IOException + */ + public static ResourceDecompressor newResourceDecompressor(String name) throws IOException { + + ResourceDecompressorFactory fact = factories.get(name); + if (fact != null) { + return fact.newDecompressor(); + } + return null; + } + + private static void registerReaderProvider(ResourceDecompressorFactory factory) { + factories.put(factory.getName(), factory); + } + + +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser$ParseResult.class b/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser$ParseResult.class new file mode 100644 index 00000000..96cf5353 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser$ParseResult.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser.class b/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser.class new file mode 100644 index 00000000..a6e16f62 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser.java b/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser.java new file mode 100644 index 00000000..9b65995f --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/SignatureParser.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * A descriptor parser used to extract java type strings from + * UTF_8 descriptors. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class SignatureParser { + + public static class ParseResult { + + public final List types = new ArrayList<>(); + public String formatted; + private ParseResult() {} + } + + private SignatureParser() {} + + public static String reconstruct(String formatted, List arguments) { + int arg_index = 0; + char[] chars = formatted.toCharArray(); + StringBuilder out = new StringBuilder(); + + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + out.append(c); + switch (c) { + case 'L': { + String pkg = arguments.get(arg_index); + if(!pkg.isEmpty()) { + out.append(pkg).append("/"); + } + arg_index+=1; + out.append(arguments.get(arg_index)); + arg_index+=1; + break; + } + default: { + break; + } + } + } + return out.toString(); + } + + public static ParseResult parseSignatureDescriptor(String str) { + ParseResult res = new ParseResult(); + char[] chars = str.toCharArray(); + StringBuilder type = null; + StringBuilder formatted = new StringBuilder(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + switch (c) { + case ';': + case ':': + case '<': { + if(type != null) { + String fullName = type.toString(); + int endIndex = fullName.lastIndexOf("/"); + String clazz = fullName; + String pkg = ""; + if(endIndex != -1) { + pkg = fullName.substring(0, endIndex); + clazz = fullName.substring(endIndex+1); + } + res.types.add(pkg); + res.types.add(clazz); + } + formatted.append(c); + + type = null; + break; + } + case 'L': { + if(type == null) { + type = new StringBuilder(); + formatted.append(c); + } else { + type.append(c); + } + break; + } + default: { + if(type == null) { + formatted.append(c); + } else { + type.append(c); + } + break; + } + } + } + res.formatted = formatted.toString(); + return res; + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressor.class b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressor.class new file mode 100644 index 00000000..d5858899 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressor.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressor.java b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressor.java new file mode 100644 index 00000000..6fc90319 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressor.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.List; + +/** + * + * A Decompressor that reconstructs the constant pool of classes. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class StringSharingDecompressor implements ResourceDecompressor { + + public static final int EXTERNALIZED_STRING = 23; + public static final int EXTERNALIZED_STRING_DESCRIPTOR = 25; + + private static final int CONSTANT_Utf8 = 1; + private static final int CONSTANT_Integer = 3; + private static final int CONSTANT_Float = 4; + private static final int CONSTANT_Long = 5; + private static final int CONSTANT_Double = 6; + private static final int CONSTANT_Class = 7; + private static final int CONSTANT_String = 8; + private static final int CONSTANT_Fieldref = 9; + private static final int CONSTANT_Methodref = 10; + private static final int CONSTANT_InterfaceMethodref = 11; + private static final int CONSTANT_NameAndType = 12; + private static final int CONSTANT_MethodHandle = 15; + private static final int CONSTANT_MethodType = 16; + private static final int CONSTANT_InvokeDynamic = 18; + private static final int CONSTANT_Module = 19; + private static final int CONSTANT_Package = 20; + + private static final int[] SIZES = new int[21]; + + static { + + //SIZES[CONSTANT_Utf8] = XXX; + SIZES[CONSTANT_Integer] = 4; + SIZES[CONSTANT_Float] = 4; + SIZES[CONSTANT_Long] = 8; + SIZES[CONSTANT_Double] = 8; + SIZES[CONSTANT_Class] = 2; + SIZES[CONSTANT_String] = 2; + SIZES[CONSTANT_Fieldref] = 4; + SIZES[CONSTANT_Methodref] = 4; + SIZES[CONSTANT_InterfaceMethodref] = 4; + SIZES[CONSTANT_NameAndType] = 4; + SIZES[CONSTANT_MethodHandle] = 3; + SIZES[CONSTANT_MethodType] = 2; + SIZES[CONSTANT_InvokeDynamic] = 4; + SIZES[CONSTANT_Module] = 2; + SIZES[CONSTANT_Package] = 2; + } + + public static int[] getSizes() { + return SIZES.clone(); + } + + @SuppressWarnings("fallthrough") + public static byte[] normalize(StringsProvider provider, byte[] transformed, + int offset) throws IOException { + DataInputStream stream = new DataInputStream(new ByteArrayInputStream(transformed, + offset, transformed.length - offset)); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(transformed.length); + DataOutputStream out = new DataOutputStream(outStream); + byte[] header = new byte[8]; //maginc/4, minor/2, major/2 + stream.readFully(header); + out.write(header); + int count = stream.readUnsignedShort(); + out.writeShort(count); + for (int i = 1; i < count; i++) { + int tag = stream.readUnsignedByte(); + byte[] arr; + switch (tag) { + case CONSTANT_Utf8: { + out.write(tag); + String utf = stream.readUTF(); + out.writeUTF(utf); + break; + } + + case EXTERNALIZED_STRING: { + int index = CompressIndexes.readInt(stream); + String orig = provider.getString(index); + out.write(CONSTANT_Utf8); + out.writeUTF(orig); + break; + } + + case EXTERNALIZED_STRING_DESCRIPTOR: { + String orig = reconstruct(provider, stream); + out.write(CONSTANT_Utf8); + out.writeUTF(orig); + break; + } + case CONSTANT_Long: + case CONSTANT_Double: { + i++; + } + default: { + out.write(tag); + int size = SIZES[tag]; + arr = new byte[size]; + stream.readFully(arr); + out.write(arr); + } + } + } + out.write(transformed, transformed.length - stream.available(), + stream.available()); + out.flush(); + + return outStream.toByteArray(); + } + + private static String reconstruct(StringsProvider reader, DataInputStream cr) + throws IOException { + int descIndex = CompressIndexes.readInt(cr); + String desc = reader.getString(descIndex); + byte[] encodedDesc = getEncoded(desc); + int indexes_length = CompressIndexes.readInt(cr); + byte[] bytes = new byte[indexes_length]; + cr.readFully(bytes); + List indices = CompressIndexes.decompressFlow(bytes); + ByteBuffer buffer = ByteBuffer.allocate(encodedDesc.length * 2); + buffer.order(ByteOrder.BIG_ENDIAN); + int argIndex = 0; + for (byte c : encodedDesc) { + if (c == 'L') { + buffer = safeAdd(buffer, c); + int index = indices.get(argIndex); + argIndex += 1; + String pkg = reader.getString(index); + if (!pkg.isEmpty()) { + pkg = pkg + "/"; + byte[] encoded = getEncoded(pkg); + buffer = safeAdd(buffer, encoded); + } + int classIndex = indices.get(argIndex); + argIndex += 1; + String clazz = reader.getString(classIndex); + byte[] encoded = getEncoded(clazz); + buffer = safeAdd(buffer, encoded); + } else { + buffer = safeAdd(buffer, c); + } + } + + byte[] encoded = buffer.array(); + ByteBuffer result = ByteBuffer.allocate(encoded.length + 2); + result.order(ByteOrder.BIG_ENDIAN); + result.putShort((short) buffer.position()); + result.put(encoded, 0, buffer.position()); + ByteArrayInputStream stream = new ByteArrayInputStream(result.array()); + DataInputStream inStream = new DataInputStream(stream); + String str = inStream.readUTF(); + return str; + } + + public static byte[] getEncoded(String pre) throws IOException { + ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); + DataOutputStream resultOut = new DataOutputStream(resultStream); + resultOut.writeUTF(pre); + byte[] content = resultStream.toByteArray(); + // first 2 items are length; + if (content.length <= 2) { + return new byte[0]; + } + return Arrays.copyOfRange(content, 2, content.length); + } + + private static ByteBuffer safeAdd(ByteBuffer current, byte b) { + byte[] bytes = {b}; + return safeAdd(current, bytes); + } + + private static ByteBuffer safeAdd(ByteBuffer current, byte[] bytes) { + if (current.remaining() < bytes.length) { + ByteBuffer newBuffer = ByteBuffer.allocate((current.capacity() + + bytes.length) * 2); + newBuffer.order(ByteOrder.BIG_ENDIAN); + newBuffer.put(current.array(), 0, current.position()); + current = newBuffer; + } + current.put(bytes); + return current; + } + + @Override + public String getName() { + return StringSharingDecompressorFactory.NAME; + } + + public StringSharingDecompressor() {} + + @Override + public byte[] decompress(StringsProvider reader, byte[] content, + int offset, long originalSize) throws Exception { + return normalize(reader, content, offset); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressorFactory.class b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressorFactory.class new file mode 100644 index 00000000..4bbe1c60 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressorFactory.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressorFactory.java b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressorFactory.java new file mode 100644 index 00000000..ecdf15fc --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/StringSharingDecompressorFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.IOException; + +/** + * + * Constant Pool strings sharing Decompressor factory. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public class StringSharingDecompressorFactory extends ResourceDecompressorFactory { + + public static final String NAME = "compact-cp"; + public StringSharingDecompressorFactory() { + super(NAME); + } + + @Override + public ResourceDecompressor newDecompressor() + throws IOException { + return new StringSharingDecompressor(); + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressor.class b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressor.class new file mode 100644 index 00000000..51b214f0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressor.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressor.java b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressor.java new file mode 100644 index 00000000..f8e9d46a --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressor.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.IOException; +import java.util.zip.Inflater; + +/** + * + * ZIP Decompressor + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class ZipDecompressor implements ResourceDecompressor { + + @Override + public String getName() { + return ZipDecompressorFactory.NAME; + } + + static byte[] decompress(byte[] bytesIn, int offset, long originalSize) throws Exception { + if (originalSize > Integer.MAX_VALUE) { + throw new OutOfMemoryError("Required array size too large"); + } + byte[] bytesOut = new byte[(int) originalSize]; + + Inflater inflater = new Inflater(); + inflater.setInput(bytesIn, offset, bytesIn.length - offset); + + int count = 0; + while (!inflater.finished() && count < originalSize) { + count += inflater.inflate(bytesOut, count, bytesOut.length - count); + } + + inflater.end(); + + if (count != originalSize) { + throw new IOException("Resource content size mismatch"); + } + + return bytesOut; + } + + @Override + public byte[] decompress(StringsProvider reader, byte[] content, int offset, + long originalSize) throws Exception { + byte[] decompressed = decompress(content, offset, originalSize); + return decompressed; + } +} diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressorFactory.class b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressorFactory.class new file mode 100644 index 00000000..132be2f3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressorFactory.class differ diff --git a/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressorFactory.java b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressorFactory.java new file mode 100644 index 00000000..b57ddc42 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jimage/decompressor/ZipDecompressorFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage.decompressor; + +import java.io.IOException; + +/** + * + * ZIP decompressor factory + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class ZipDecompressorFactory extends ResourceDecompressorFactory { + public static final String NAME = "zip"; + public ZipDecompressorFactory() { + super(NAME); + } + + @Override + public ResourceDecompressor newDecompressor() + throws IOException { + return new ZipDecompressor(); + } +} diff --git a/tests/test_data/std/jdk/internal/jmod/JmodFile$Entry.class b/tests/test_data/std/jdk/internal/jmod/JmodFile$Entry.class new file mode 100644 index 00000000..a0e5dba0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jmod/JmodFile$Entry.class differ diff --git a/tests/test_data/std/jdk/internal/jmod/JmodFile$Section.class b/tests/test_data/std/jdk/internal/jmod/JmodFile$Section.class new file mode 100644 index 00000000..beed4a58 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jmod/JmodFile$Section.class differ diff --git a/tests/test_data/std/jdk/internal/jmod/JmodFile.class b/tests/test_data/std/jdk/internal/jmod/JmodFile.class new file mode 100644 index 00000000..d1052e07 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jmod/JmodFile.class differ diff --git a/tests/test_data/std/jdk/internal/jmod/JmodFile.java b/tests/test_data/std/jdk/internal/jmod/JmodFile.java new file mode 100644 index 00000000..987d68f0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jmod/JmodFile.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jmod; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * Helper class to read JMOD file + */ +public class JmodFile implements AutoCloseable { + // jmod magic number and version number + private static final int JMOD_MAJOR_VERSION = 0x01; + private static final int JMOD_MINOR_VERSION = 0x00; + private static final byte[] JMOD_MAGIC_NUMBER = { + 0x4A, 0x4D, /* JM */ + JMOD_MAJOR_VERSION, JMOD_MINOR_VERSION, /* version 1.0 */ + }; + + public static void checkMagic(Path file) throws IOException { + try (InputStream in = Files.newInputStream(file)) { + // validate the header + byte[] magic = in.readNBytes(4); + if (magic.length != 4) { + throw new IOException("Invalid JMOD file: " + file); + } + if (magic[0] != JMOD_MAGIC_NUMBER[0] || + magic[1] != JMOD_MAGIC_NUMBER[1]) { + throw new IOException("Invalid JMOD file: " + file.toString()); + } + if (magic[2] > JMOD_MAJOR_VERSION || + (magic[2] == JMOD_MAJOR_VERSION && magic[3] > JMOD_MINOR_VERSION)) { + throw new IOException("Unsupported jmod version: " + + magic[2] + "." + magic[3] + " in " + file.toString()); + } + } + } + + /** + * JMOD sections + */ + public static enum Section { + CLASSES("classes"), + CONFIG("conf"), + HEADER_FILES("include"), + LEGAL_NOTICES("legal"), + MAN_PAGES("man"), + NATIVE_LIBS("lib"), + NATIVE_CMDS("bin"); + + private final String jmodDir; + private Section(String jmodDir) { + this.jmodDir = jmodDir; + } + + /** + * Returns the directory name in the JMOD file corresponding to + * this section + */ + public String jmodDir() { return jmodDir; } + } + + /** + * JMOD file entry. + * + * Each entry corresponds to a ZipEntry whose name is: + * Section::jmodDir + '/' + name + */ + public static class Entry { + private final ZipEntry zipEntry; + private final Section section; + private final String name; + + private Entry(ZipEntry e) { + String name = e.getName(); + int i = name.indexOf('/'); + if (i <= 1) { + throw new RuntimeException("invalid jmod entry: " + name); + } + + this.zipEntry = e; + this.section = section(name.substring(0, i)); + this.name = name.substring(i+1); + } + + /** + * Returns the section of this entry. + */ + public Section section() { + return section; + } + + /** + * Returns the name of this entry. + */ + public String name() { + return name; + } + + /** + * Returns true if the entry is a directory in the JMOD file. + */ + public boolean isDirectory() { + return zipEntry.isDirectory(); + } + + /** + * Returns the size of this entry. + */ + public long size() { + return zipEntry.getSize(); + } + + public ZipEntry zipEntry() { + return zipEntry; + } + + @Override + public String toString() { + return section.jmodDir() + "/" + name; + } + + /* + * A map from the jmodDir name to Section + */ + static final Map NAME_TO_SECTION = + Arrays.stream(Section.values()) + .collect(Collectors.toMap(Section::jmodDir, Function.identity())); + + static Section section(String name) { + if (!NAME_TO_SECTION.containsKey(name)) { + throw new IllegalArgumentException("invalid section: " + name); + + } + return NAME_TO_SECTION.get(name); + } + + } + + private final Path file; + private final ZipFile zipfile; + + /** + * Constructs a {@code JmodFile} from a given path. + */ + public JmodFile(Path file) throws IOException { + checkMagic(file); + this.file = file; + this.zipfile = new ZipFile(file.toFile()); + } + + public static void writeMagicNumber(OutputStream os) throws IOException { + os.write(JMOD_MAGIC_NUMBER); + } + + /** + * Returns the {@code Entry} for a resource in a JMOD file section + * or {@code null} if not found. + */ + public Entry getEntry(Section section, String name) { + String entry = section.jmodDir() + "/" + name; + ZipEntry ze = zipfile.getEntry(entry); + return (ze != null) ? new Entry(ze) : null; + } + + /** + * Opens an {@code InputStream} for reading the named entry of the given + * section in this JMOD file. + * + * @throws IOException if the named entry is not found, or I/O error + * occurs when reading it + */ + public InputStream getInputStream(Section section, String name) + throws IOException + { + String entry = section.jmodDir() + "/" + name; + ZipEntry e = zipfile.getEntry(entry); + if (e == null) { + throw new IOException(name + " not found: " + file); + } + return zipfile.getInputStream(e); + } + + /** + * Opens an {@code InputStream} for reading an entry in the JMOD file. + * + * @throws IOException if an I/O error occurs + */ + public InputStream getInputStream(Entry entry) throws IOException { + return zipfile.getInputStream(entry.zipEntry()); + } + + /** + * Returns a stream of entries in this JMOD file. + */ + public Stream stream() { + return zipfile.stream() + .map(Entry::new); + } + + @Override + public void close() throws IOException { + if (zipfile != null) { + zipfile.close(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage$PathNode.class b/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage$PathNode.class new file mode 100644 index 00000000..842362b3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage$PathNode.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage.class b/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage.class new file mode 100644 index 00000000..36826f78 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage.java b/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage.java new file mode 100644 index 00000000..e2c17f8c --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/ExplodedImage.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import jdk.internal.jimage.ImageReader.Node; + +/** + * A jrt file system built on $JAVA_HOME/modules directory ('exploded modules + * build') + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +class ExplodedImage extends SystemImage { + + private static final String MODULES = "/modules/"; + private static final String PACKAGES = "/packages/"; + private static final int PACKAGES_LEN = PACKAGES.length(); + + private final FileSystem defaultFS; + private final String separator; + private final Map nodes = Collections.synchronizedMap(new HashMap<>()); + private final BasicFileAttributes modulesDirAttrs; + + ExplodedImage(Path modulesDir) throws IOException { + defaultFS = FileSystems.getDefault(); + String str = defaultFS.getSeparator(); + separator = str.equals("/") ? null : str; + modulesDirAttrs = Files.readAttributes(modulesDir, BasicFileAttributes.class); + initNodes(); + } + + // A Node that is backed by actual default file system Path + private final class PathNode extends Node { + + // Path in underlying default file system + private Path path; + private PathNode link; + private List children; + + PathNode(String name, Path path, BasicFileAttributes attrs) { // path + super(name, attrs); + this.path = path; + } + + PathNode(String name, Node link) { // link + super(name, link.getFileAttributes()); + this.link = (PathNode)link; + } + + PathNode(String name, List children) { // dir + super(name, modulesDirAttrs); + this.children = children; + } + + @Override + public boolean isDirectory() { + return children != null || + (link == null && getFileAttributes().isDirectory()); + } + + @Override + public boolean isLink() { + return link != null; + } + + @Override + public PathNode resolveLink(boolean recursive) { + if (link == null) + return this; + return recursive && link.isLink() ? link.resolveLink(true) : link; + } + + byte[] getContent() throws IOException { + if (!getFileAttributes().isRegularFile()) + throw new FileSystemException(getName() + " is not file"); + return Files.readAllBytes(path); + } + + @Override + public List getChildren() { + if (!isDirectory()) + throw new IllegalArgumentException("not a directory: " + getNameString()); + if (children == null) { + List list = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(path)) { + for (Path p : stream) { + p = explodedModulesDir.relativize(p); + String pName = MODULES + nativeSlashToFrontSlash(p.toString()); + Node node = findNode(pName); + if (node != null) { // findNode may choose to hide certain files! + list.add(node); + } + } + } catch (IOException x) { + return null; + } + children = list; + } + return children; + } + + @Override + public long size() { + try { + return isDirectory() ? 0 : Files.size(path); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + } + + @Override + public void close() throws IOException { + nodes.clear(); + } + + @Override + public byte[] getResource(Node node) throws IOException { + return ((PathNode)node).getContent(); + } + + // find Node for the given Path + @Override + public synchronized Node findNode(String str) { + Node node = findModulesNode(str); + if (node != null) { + return node; + } + // lazily created for paths like /packages///xyz + // For example /packages/java.lang/java.base/java/lang/ + if (str.startsWith(PACKAGES)) { + // pkgEndIdx marks end of part + int pkgEndIdx = str.indexOf('/', PACKAGES_LEN); + if (pkgEndIdx != -1) { + // modEndIdx marks end of part + int modEndIdx = str.indexOf('/', pkgEndIdx + 1); + if (modEndIdx != -1) { + // make sure we have such module link! + // ie., /packages// is valid + Node linkNode = nodes.get(str.substring(0, modEndIdx)); + if (linkNode == null || !linkNode.isLink()) { + return null; + } + // map to "/modules/zyz" path and return that node + // For example, "/modules/java.base/java/lang" for + // "/packages/java.lang/java.base/java/lang". + String mod = MODULES + str.substring(pkgEndIdx + 1); + return findModulesNode(mod); + } + } + } + return null; + } + + // find a Node for a path that starts like "/modules/..." + Node findModulesNode(String str) { + PathNode node = nodes.get(str); + if (node != null) { + return node; + } + // lazily created "/modules/xyz/abc/" Node + // This is mapped to default file system path "/xyz/abc" + Path p = underlyingPath(str); + if (p != null) { + try { + BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); + if (attrs.isRegularFile()) { + Path f = p.getFileName(); + if (f.toString().startsWith("_the.")) + return null; + } + node = new PathNode(str, p, attrs); + nodes.put(str, node); + return node; + } catch (IOException x) { + // does not exists or unable to determine + } + } + return null; + } + + Path underlyingPath(String str) { + if (str.startsWith(MODULES)) { + str = frontSlashToNativeSlash(str.substring("/modules".length())); + return defaultFS.getPath(explodedModulesDir.toString(), str); + } + return null; + } + + // convert "/" to platform path separator + private String frontSlashToNativeSlash(String str) { + return separator == null ? str : str.replace("/", separator); + } + + // convert platform path separator to "/" + private String nativeSlashToFrontSlash(String str) { + return separator == null ? str : str.replace(separator, "/"); + } + + // convert "/"s to "."s + private String slashesToDots(String str) { + return str.replace(separator != null ? separator : "/", "."); + } + + // initialize file system Nodes + private void initNodes() throws IOException { + // same package prefix may exist in multiple modules. This Map + // is filled by walking "jdk modules" directory recursively! + Map> packageToModules = new HashMap<>(); + try (DirectoryStream stream = Files.newDirectoryStream(explodedModulesDir)) { + for (Path module : stream) { + if (Files.isDirectory(module)) { + String moduleName = module.getFileName().toString(); + // make sure "/modules/" is created + findModulesNode(MODULES + moduleName); + try (Stream contentsStream = Files.walk(module)) { + contentsStream.filter(Files::isDirectory).forEach((p) -> { + p = module.relativize(p); + String pkgName = slashesToDots(p.toString()); + // skip META-INF and empty strings + if (!pkgName.isEmpty() && !pkgName.startsWith("META-INF")) { + List moduleNames = packageToModules.get(pkgName); + if (moduleNames == null) { + moduleNames = new ArrayList<>(); + packageToModules.put(pkgName, moduleNames); + } + moduleNames.add(moduleName); + } + }); + } + } + } + } + // create "/modules" directory + // "nodes" map contains only /modules/ nodes only so far and so add all as children of /modules + PathNode modulesDir = new PathNode("/modules", new ArrayList<>(nodes.values())); + nodes.put(modulesDir.getName(), modulesDir); + + // create children under "/packages" + List packagesChildren = new ArrayList<>(packageToModules.size()); + for (Map.Entry> entry : packageToModules.entrySet()) { + String pkgName = entry.getKey(); + List moduleNameList = entry.getValue(); + List moduleLinkNodes = new ArrayList<>(moduleNameList.size()); + for (String moduleName : moduleNameList) { + Node moduleNode = findModulesNode(MODULES + moduleName); + PathNode linkNode = new PathNode(PACKAGES + pkgName + "/" + moduleName, moduleNode); + nodes.put(linkNode.getName(), linkNode); + moduleLinkNodes.add(linkNode); + } + PathNode pkgDir = new PathNode(PACKAGES + pkgName, moduleLinkNodes); + nodes.put(pkgDir.getName(), pkgDir); + packagesChildren.add(pkgDir); + } + // "/packages" dir + PathNode packagesDir = new PathNode("/packages", packagesChildren); + nodes.put(packagesDir.getName(), packagesDir); + + // finally "/" dir! + List rootChildren = new ArrayList<>(); + rootChildren.add(packagesDir); + rootChildren.add(modulesDir); + PathNode root = new PathNode("/", rootChildren); + nodes.put(root.getName(), root); + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream$1.class b/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream$1.class new file mode 100644 index 00000000..fa5fd82b Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream$1.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream.class b/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream.class new file mode 100644 index 00000000..7726d424 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream.java b/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream.java new file mode 100644 index 00000000..f2f2aa68 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtDirectoryStream.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.nio.file.DirectoryStream; +import java.nio.file.ClosedDirectoryStreamException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.Objects; +import java.util.NoSuchElementException; +import java.io.IOException; + +/** + * DirectoryStream implementation for jrt file system implementations. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class JrtDirectoryStream implements DirectoryStream { + + private final JrtPath dir; + private final DirectoryStream.Filter filter; + private boolean isClosed; + private Iterator itr; + + JrtDirectoryStream(JrtPath dir, + DirectoryStream.Filter filter) + throws IOException + { + this.dir = dir; + if (!dir.jrtfs.isDirectory(dir, true)) { // sanity check + throw new NotDirectoryException(dir.toString()); + } + this.filter = filter; + } + + @Override + public synchronized Iterator iterator() { + if (isClosed) + throw new ClosedDirectoryStreamException(); + if (itr != null) + throw new IllegalStateException("Iterator has already been returned"); + try { + itr = dir.jrtfs.iteratorOf(dir, filter); + } catch (IOException e) { + throw new IllegalStateException(e); + } + return new Iterator() { + @Override + public boolean hasNext() { + synchronized (JrtDirectoryStream.this) { + if (isClosed) + return false; + return itr.hasNext(); + } + } + + @Override + public Path next() { + synchronized (JrtDirectoryStream.this) { + if (isClosed) + throw new NoSuchElementException(); + return itr.next(); + } + } + }; + } + + @Override + public synchronized void close() throws IOException { + isClosed = true; + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView$AttrID.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView$AttrID.class new file mode 100644 index 00000000..9dcc27a5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView$AttrID.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView.class new file mode 100644 index 00000000..5dba4837 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView.java b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView.java new file mode 100644 index 00000000..874da943 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributeView.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.nio.file.LinkOption; +import java.nio.file.attribute.*; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +/** + * File attribute view for jrt file system. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class JrtFileAttributeView implements BasicFileAttributeView { + + private static enum AttrID { + size, + creationTime, + lastAccessTime, + lastModifiedTime, + isDirectory, + isRegularFile, + isSymbolicLink, + isOther, + fileKey, + compressedSize, + extension + }; + + private final JrtPath path; + private final boolean isJrtView; + private final LinkOption[] options; + + private JrtFileAttributeView(JrtPath path, boolean isJrtView, LinkOption... options) { + this.path = path; + this.isJrtView = isJrtView; + this.options = options; + } + + @SuppressWarnings("unchecked") // Cast to V + static V get(JrtPath path, Class type, LinkOption... options) { + Objects.requireNonNull(type); + if (type == BasicFileAttributeView.class) { + return (V) new JrtFileAttributeView(path, false, options); + } + if (type == JrtFileAttributeView.class) { + return (V) new JrtFileAttributeView(path, true, options); + } + return null; + } + + static JrtFileAttributeView get(JrtPath path, String type, LinkOption... options) { + Objects.requireNonNull(type); + if (type.equals("basic")) { + return new JrtFileAttributeView(path, false, options); + } + if (type.equals("jrt")) { + return new JrtFileAttributeView(path, true, options); + } + return null; + } + + @Override + public String name() { + return isJrtView ? "jrt" : "basic"; + } + + @Override + public JrtFileAttributes readAttributes() throws IOException { + return path.getAttributes(options); + } + + @Override + public void setTimes(FileTime lastModifiedTime, + FileTime lastAccessTime, + FileTime createTime) throws IOException { + path.setTimes(lastModifiedTime, lastAccessTime, createTime); + } + + static void setAttribute(JrtPath path, String attribute, Object value) + throws IOException { + int colonPos = attribute.indexOf(':'); + if (colonPos != -1) { // type = "basic", if no ":" + String type = attribute.substring(0, colonPos++); + if (!type.equals("basic") && !type.equals("jrt")) { + throw new UnsupportedOperationException( + "view <" + type + "> is not supported"); + } + attribute = attribute.substring(colonPos); + } + try { + AttrID id = AttrID.valueOf(attribute); + if (id == AttrID.lastModifiedTime) { + path.setTimes((FileTime) value, null, null); + } else if (id == AttrID.lastAccessTime) { + path.setTimes(null, (FileTime) value, null); + } else if (id == AttrID.creationTime) { + path.setTimes(null, null, (FileTime) value); + } + return; + } catch (IllegalArgumentException x) {} + throw new UnsupportedOperationException("'" + attribute + + "' is unknown or read-only attribute"); + } + + static Map readAttributes(JrtPath path, String attributes, + LinkOption... options) + throws IOException { + int colonPos = attributes.indexOf(':'); + boolean isJrtView = false; + if (colonPos != -1) { // type = "basic", if no ":" + String type = attributes.substring(0, colonPos++); + if (!type.equals("basic") && !type.equals("jrt")) { + throw new UnsupportedOperationException("view <" + type + + "> is not supported"); + } + isJrtView = true; + attributes = attributes.substring(colonPos); + } + JrtFileAttributes jrtfas = path.getAttributes(); + LinkedHashMap map = new LinkedHashMap<>(); + if ("*".equals(attributes)) { + for (AttrID id : AttrID.values()) { + map.put(id.name(), attribute(id, jrtfas, isJrtView)); + } + } else { + String[] as = attributes.split(","); + for (String a : as) { + //throw IllegalArgumentException + map.put(a, attribute(AttrID.valueOf(a), jrtfas, isJrtView)); + } + } + return map; + } + + static Object attribute(AttrID id, JrtFileAttributes jrtfas, boolean isJrtView) { + switch (id) { + case size: + return jrtfas.size(); + case creationTime: + return jrtfas.creationTime(); + case lastAccessTime: + return jrtfas.lastAccessTime(); + case lastModifiedTime: + return jrtfas.lastModifiedTime(); + case isDirectory: + return jrtfas.isDirectory(); + case isRegularFile: + return jrtfas.isRegularFile(); + case isSymbolicLink: + return jrtfas.isSymbolicLink(); + case isOther: + return jrtfas.isOther(); + case fileKey: + return jrtfas.fileKey(); + case compressedSize: + if (isJrtView) { + return jrtfas.compressedSize(); + } + break; + case extension: + if (isJrtView) { + return jrtfas.extension(); + } + break; + } + return null; + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributes.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributes.class new file mode 100644 index 00000000..565ba3f0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributes.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributes.java b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributes.java new file mode 100644 index 00000000..f0804b58 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtFileAttributes.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.Formatter; +import jdk.internal.jimage.ImageReader.Node; + +/** + * File attributes implementation for jrt image file system. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class JrtFileAttributes implements BasicFileAttributes { + + private final Node node; + + JrtFileAttributes(Node node) { + this.node = node; + } + + //-------- basic attributes -------- + @Override + public FileTime creationTime() { + return node.creationTime(); + } + + @Override + public boolean isDirectory() { + return node.isDirectory(); + } + + @Override + public boolean isOther() { + return false; + } + + @Override + public boolean isRegularFile() { + return !isDirectory(); + } + + @Override + public FileTime lastAccessTime() { + return node.lastAccessTime(); + } + + @Override + public FileTime lastModifiedTime() { + return node.lastModifiedTime(); + } + + @Override + public long size() { + return node.size(); + } + + @Override + public boolean isSymbolicLink() { + return node.isLink(); + } + + @Override + public Object fileKey() { + return node.resolveLink(true); + } + + //-------- jrtfs specific attributes -------- + /** + * Compressed resource file. If not available or not applicable, 0L is + * returned. + * + * @return the compressed resource size for compressed resources. + */ + public long compressedSize() { + return node.compressedSize(); + } + + /** + * "file" extension of a file resource. + * + * @return extension string for the file resource + */ + public String extension() { + return node.extension(); + } + + @Override + public final String toString() { + StringBuilder sb = new StringBuilder(1024); + try (Formatter fm = new Formatter(sb)) { + if (creationTime() != null) { + fm.format(" creationTime : %tc%n", creationTime().toMillis()); + } else { + fm.format(" creationTime : null%n"); + } + if (lastAccessTime() != null) { + fm.format(" lastAccessTime : %tc%n", lastAccessTime().toMillis()); + } else { + fm.format(" lastAccessTime : null%n"); + } + fm.format(" lastModifiedTime: %tc%n", lastModifiedTime().toMillis()); + fm.format(" isRegularFile : %b%n", isRegularFile()); + fm.format(" isDirectory : %b%n", isDirectory()); + fm.format(" isSymbolicLink : %b%n", isSymbolicLink()); + fm.format(" isOther : %b%n", isOther()); + fm.format(" fileKey : %s%n", fileKey()); + fm.format(" size : %d%n", size()); + fm.format(" compressedSize : %d%n", compressedSize()); + fm.format(" extension : %s%n", extension()); + } + return sb.toString(); + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileStore.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileStore.class new file mode 100644 index 00000000..9da7a322 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileStore.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileStore.java b/tests/test_data/std/jdk/internal/jrtfs/JrtFileStore.java new file mode 100644 index 00000000..7d4b0a18 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtFileStore.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; +import java.util.Objects; + +/** + * File store implementation for jrt file systems. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class JrtFileStore extends FileStore { + + protected final FileSystem jrtfs; + + JrtFileStore(JrtPath jrtPath) { + this.jrtfs = jrtPath.getFileSystem(); + } + + @Override + public String name() { + return jrtfs.toString() + "/"; + } + + @Override + public String type() { + return "jrtfs"; + } + + @Override + public boolean isReadOnly() { + return jrtfs.isReadOnly(); + } + + @Override + public boolean supportsFileAttributeView(String name) { + return name.equals("basic") || name.equals("jrt"); + } + + @Override + @SuppressWarnings("unchecked") + public V getFileStoreAttributeView(Class type) { + Objects.requireNonNull(type, "type"); + return (V) null; + } + + @Override + public long getTotalSpace() throws IOException { + throw new UnsupportedOperationException("getTotalSpace"); + } + + @Override + public long getUsableSpace() throws IOException { + throw new UnsupportedOperationException("getUsableSpace"); + } + + @Override + public long getUnallocatedSpace() throws IOException { + throw new UnsupportedOperationException("getUnallocatedSpace"); + } + + @Override + public Object getAttribute(String attribute) throws IOException { + throw new UnsupportedOperationException("does not support " + attribute); + } + + @Override + public boolean supportsFileAttributeView(Class type) { + return type == BasicFileAttributeView.class || + type == JrtFileAttributeView.class; + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem$1.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem$1.class new file mode 100644 index 00000000..5b582c9b Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem$1.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem.class new file mode 100644 index 00000000..bdb8efa9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem.java b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem.java new file mode 100644 index 00000000..9a8d9d22 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystem.java @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.ClosedFileSystemException; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemException; +import java.nio.file.InvalidPathException; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.NotDirectoryException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.ReadOnlyFileSystemException; +import java.nio.file.StandardOpenOption; +import java.nio.file.WatchService; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; +import jdk.internal.jimage.ImageReader.Node; +import static java.util.stream.Collectors.toList; + +/** + * jrt file system implementation built on System jimage files. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +class JrtFileSystem extends FileSystem { + + private final JrtFileSystemProvider provider; + private final JrtPath rootPath = new JrtPath(this, "/"); + private volatile boolean isOpen; + private volatile boolean isClosable; + private SystemImage image; + + JrtFileSystem(JrtFileSystemProvider provider, Map env) + throws IOException + { + this.provider = provider; + this.image = SystemImage.open(); // open image file + this.isOpen = true; + this.isClosable = env != null; + } + + // FileSystem method implementations + @Override + public boolean isOpen() { + return isOpen; + } + + @Override + public void close() throws IOException { + if (!isClosable) + throw new UnsupportedOperationException(); + cleanup(); + } + + @Override + public FileSystemProvider provider() { + return provider; + } + + @Override + public Iterable getRootDirectories() { + return Collections.singleton(getRootPath()); + } + + @Override + public JrtPath getPath(String first, String... more) { + if (more.length == 0) { + return new JrtPath(this, first); + } + StringBuilder sb = new StringBuilder(); + sb.append(first); + for (String path : more) { + if (!path.isEmpty()) { + if (sb.length() > 0) { + sb.append('/'); + } + sb.append(path); + } + } + return new JrtPath(this, sb.toString()); + } + + @Override + public final boolean isReadOnly() { + return true; + } + + @Override + public final UserPrincipalLookupService getUserPrincipalLookupService() { + throw new UnsupportedOperationException(); + } + + @Override + public final WatchService newWatchService() { + throw new UnsupportedOperationException(); + } + + @Override + public final Iterable getFileStores() { + return Collections.singleton(getFileStore(getRootPath())); + } + + private static final Set supportedFileAttributeViews + = Collections.unmodifiableSet( + new HashSet(Arrays.asList("basic", "jrt"))); + + @Override + public final Set supportedFileAttributeViews() { + return supportedFileAttributeViews; + } + + @Override + public final String toString() { + return "jrt:/"; + } + + @Override + public final String getSeparator() { + return "/"; + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndInput) { + int pos = syntaxAndInput.indexOf(':'); + if (pos <= 0) { + throw new IllegalArgumentException(); + } + String syntax = syntaxAndInput.substring(0, pos); + String input = syntaxAndInput.substring(pos + 1); + String expr; + if (syntax.equalsIgnoreCase("glob")) { + expr = JrtUtils.toRegexPattern(input); + } else if (syntax.equalsIgnoreCase("regex")) { + expr = input; + } else { + throw new UnsupportedOperationException("Syntax '" + syntax + + "' not recognized"); + } + // return matcher + final Pattern pattern = Pattern.compile(expr); + return (Path path) -> pattern.matcher(path.toString()).matches(); + } + + JrtPath resolveLink(JrtPath path) throws IOException { + Node node = checkNode(path); + if (node.isLink()) { + node = node.resolveLink(); + return new JrtPath(this, node.getName()); // TBD, normalized? + } + return path; + } + + JrtFileAttributes getFileAttributes(JrtPath path, LinkOption... options) + throws IOException { + Node node = checkNode(path); + if (node.isLink() && followLinks(options)) { + return new JrtFileAttributes(node.resolveLink(true)); + } + return new JrtFileAttributes(node); + } + + /** + * returns the list of child paths of the given directory "path" + * + * @param path name of the directory whose content is listed + * @return iterator for child paths of the given directory path + */ + Iterator iteratorOf(JrtPath path, DirectoryStream.Filter filter) + throws IOException { + Node node = checkNode(path).resolveLink(true); + if (!node.isDirectory()) { + throw new NotDirectoryException(path.getName()); + } + if (filter == null) { + return node.getChildren() + .stream() + .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) + .iterator(); + } + return node.getChildren() + .stream() + .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) + .filter(p -> { try { return filter.accept(p); + } catch (IOException x) {} + return false; + }) + .iterator(); + } + + // returns the content of the file resource specified by the path + byte[] getFileContent(JrtPath path) throws IOException { + Node node = checkNode(path); + if (node.isDirectory()) { + throw new FileSystemException(path + " is a directory"); + } + //assert node.isResource() : "resource node expected here"; + return image.getResource(node); + } + + /////////////// Implementation details below this point ////////// + + // static utility methods + static ReadOnlyFileSystemException readOnly() { + return new ReadOnlyFileSystemException(); + } + + // do the supplied options imply that we have to chase symlinks? + static boolean followLinks(LinkOption... options) { + if (options != null) { + for (LinkOption lo : options) { + Objects.requireNonNull(lo); + if (lo == LinkOption.NOFOLLOW_LINKS) { + return false; + } else { + throw new AssertionError("should not reach here"); + } + } + } + return true; + } + + // check that the options passed are supported by (read-only) jrt file system + static void checkOptions(Set options) { + // check for options of null type and option is an instance of StandardOpenOption + for (OpenOption option : options) { + Objects.requireNonNull(option); + if (!(option instanceof StandardOpenOption)) { + throw new IllegalArgumentException( + "option class: " + option.getClass()); + } + } + if (options.contains(StandardOpenOption.WRITE) || + options.contains(StandardOpenOption.APPEND)) { + throw readOnly(); + } + } + + // clean up this file system - called from finalize and close + synchronized void cleanup() throws IOException { + if (isOpen) { + isOpen = false; + image.close(); + image = null; + } + } + + // These methods throw read only file system exception + final void setTimes(JrtPath jrtPath, FileTime mtime, FileTime atime, FileTime ctime) + throws IOException { + throw readOnly(); + } + + // These methods throw read only file system exception + final void createDirectory(JrtPath jrtPath, FileAttribute... attrs) throws IOException { + throw readOnly(); + } + + final void deleteFile(JrtPath jrtPath, boolean failIfNotExists) + throws IOException { + throw readOnly(); + } + + final OutputStream newOutputStream(JrtPath jrtPath, OpenOption... options) + throws IOException { + throw readOnly(); + } + + final void copyFile(boolean deletesrc, JrtPath srcPath, JrtPath dstPath, CopyOption... options) + throws IOException { + throw readOnly(); + } + + final FileChannel newFileChannel(JrtPath path, + Set options, + FileAttribute... attrs) + throws IOException { + throw new UnsupportedOperationException("newFileChannel"); + } + + final InputStream newInputStream(JrtPath path) throws IOException { + return new ByteArrayInputStream(getFileContent(path)); + } + + final SeekableByteChannel newByteChannel(JrtPath path, + Set options, + FileAttribute... attrs) + throws IOException { + checkOptions(options); + + byte[] buf = getFileContent(path); + final ReadableByteChannel rbc + = Channels.newChannel(new ByteArrayInputStream(buf)); + final long size = buf.length; + return new SeekableByteChannel() { + long read = 0; + + @Override + public boolean isOpen() { + return rbc.isOpen(); + } + + @Override + public long position() throws IOException { + return read; + } + + @Override + public SeekableByteChannel position(long pos) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int n = rbc.read(dst); + if (n > 0) { + read += n; + } + return n; + } + + @Override + public SeekableByteChannel truncate(long size) + throws IOException { + throw new NonWritableChannelException(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new NonWritableChannelException(); + } + + @Override + public long size() throws IOException { + return size; + } + + @Override + public void close() throws IOException { + rbc.close(); + } + }; + } + + final JrtFileStore getFileStore(JrtPath path) { + return new JrtFileStore(path); + } + + final void ensureOpen() throws IOException { + if (!isOpen()) { + throw new ClosedFileSystemException(); + } + } + + final JrtPath getRootPath() { + return rootPath; + } + + boolean isSameFile(JrtPath path1, JrtPath path2) throws IOException { + return checkNode(path1) == checkNode(path2); + } + + boolean isLink(JrtPath path) throws IOException { + return checkNode(path).isLink(); + } + + boolean exists(JrtPath path) throws IOException { + try { + checkNode(path); + } catch (NoSuchFileException exp) { + return false; + } + return true; + } + + boolean isDirectory(JrtPath path, boolean resolveLinks) + throws IOException { + Node node = checkNode(path); + return resolveLinks && node.isLink() + ? node.resolveLink(true).isDirectory() + : node.isDirectory(); + } + + JrtPath toRealPath(JrtPath path, LinkOption... options) + throws IOException { + Node node = checkNode(path); + if (followLinks(options) && node.isLink()) { + node = node.resolveLink(); + } + // image node holds the real/absolute path name + return new JrtPath(this, node.getName(), true); + } + + private Node lookup(String path) { + try { + return image.findNode(path); + } catch (RuntimeException | IOException ex) { + throw new InvalidPathException(path, ex.toString()); + } + } + + private Node lookupSymbolic(String path) { + int i = 1; + while (i < path.length()) { + i = path.indexOf('/', i); + if (i == -1) { + break; + } + String prefix = path.substring(0, i); + Node node = lookup(prefix); + if (node == null) { + break; + } + if (node.isLink()) { + Node link = node.resolveLink(true); + // resolved symbolic path concatenated to the rest of the path + String resPath = link.getName() + path.substring(i); + node = lookup(resPath); + return node != null ? node : lookupSymbolic(resPath); + } + i++; + } + return null; + } + + Node checkNode(JrtPath path) throws IOException { + ensureOpen(); + String p = path.getResolvedPath(); + Node node = lookup(p); + if (node == null) { + node = lookupSymbolic(p); + if (node == null) { + throw new NoSuchFileException(p); + } + } + return node; + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider$1.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider$1.class new file mode 100644 index 00000000..3306b4ca Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider$1.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider$JrtFsLoader.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider$JrtFsLoader.class new file mode 100644 index 00000000..8c21f3f5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider$JrtFsLoader.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider.class b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider.class new file mode 100644 index 00000000..106a8ed3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider.java b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider.java new file mode 100644 index 00000000..4d3e0ed9 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtFileSystemProvider.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.channels.*; +import java.nio.file.*; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.attribute.*; +import java.nio.file.spi.FileSystemProvider; +import java.net.URI; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +/** + * File system provider for jrt file systems. Conditionally creates jrt fs on + * .jimage file or exploded modules directory of underlying JDK. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class JrtFileSystemProvider extends FileSystemProvider { + + private volatile FileSystem theFileSystem; + + public JrtFileSystemProvider() { + } + + @Override + public String getScheme() { + return "jrt"; + } + + /** + * Need RuntimePermission "accessSystemModules" to create or get jrt:/ + */ + private void checkPermission() { + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + RuntimePermission perm = new RuntimePermission("accessSystemModules"); + sm.checkPermission(perm); + } + } + + private void checkUri(URI uri) { + if (!uri.getScheme().equalsIgnoreCase(getScheme())) { + throw new IllegalArgumentException("URI does not match this provider"); + } + if (uri.getAuthority() != null) { + throw new IllegalArgumentException("Authority component present"); + } + if (uri.getPath() == null) { + throw new IllegalArgumentException("Path component is undefined"); + } + if (!uri.getPath().equals("/")) { + throw new IllegalArgumentException("Path component should be '/'"); + } + if (uri.getQuery() != null) { + throw new IllegalArgumentException("Query component present"); + } + if (uri.getFragment() != null) { + throw new IllegalArgumentException("Fragment component present"); + } + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) + throws IOException { + Objects.requireNonNull(env); + checkPermission(); + checkUri(uri); + if (env.containsKey("java.home")) { + return newFileSystem((String)env.get("java.home"), uri, env); + } else { + return new JrtFileSystem(this, env); + } + } + + private static final String JRT_FS_JAR = "jrt-fs.jar"; + private FileSystem newFileSystem(String targetHome, URI uri, Map env) + throws IOException { + Objects.requireNonNull(targetHome); + Path jrtfs = FileSystems.getDefault().getPath(targetHome, "lib", JRT_FS_JAR); + if (Files.notExists(jrtfs)) { + throw new IOException(jrtfs.toString() + " not exist"); + } + Map newEnv = new HashMap<>(env); + newEnv.remove("java.home"); + ClassLoader cl = newJrtFsLoader(jrtfs); + try { + Class c = Class.forName(JrtFileSystemProvider.class.getName(), false, cl); + @SuppressWarnings("deprecation") + Object tmp = c.newInstance(); + return ((FileSystemProvider)tmp).newFileSystem(uri, newEnv); + } catch (ClassNotFoundException | + IllegalAccessException | + InstantiationException e) { + throw new IOException(e); + } + } + + private static class JrtFsLoader extends URLClassLoader { + JrtFsLoader(URL[] urls) { + super(urls); + } + @Override + protected Class loadClass(String cn, boolean resolve) + throws ClassNotFoundException + { + Class c = findLoadedClass(cn); + if (c == null) { + URL u = findResource(cn.replace('.', '/') + ".class"); + if (u != null) { + c = findClass(cn); + } else { + return super.loadClass(cn, resolve); + } + } + if (resolve) + resolveClass(c); + return c; + } + } + + @SuppressWarnings("removal") + private static URLClassLoader newJrtFsLoader(Path jrtfs) { + final URL url; + try { + url = jrtfs.toUri().toURL(); + } catch (MalformedURLException mue) { + throw new IllegalArgumentException(mue); + } + + final URL[] urls = new URL[] { url }; + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public URLClassLoader run() { + return new JrtFsLoader(urls); + } + } + ); + } + + @Override + public Path getPath(URI uri) { + checkPermission(); + if (!uri.getScheme().equalsIgnoreCase(getScheme())) { + throw new IllegalArgumentException("URI does not match this provider"); + } + if (uri.getAuthority() != null) { + throw new IllegalArgumentException("Authority component present"); + } + if (uri.getQuery() != null) { + throw new IllegalArgumentException("Query component present"); + } + if (uri.getFragment() != null) { + throw new IllegalArgumentException("Fragment component present"); + } + String path = uri.getPath(); + if (path == null || path.charAt(0) != '/' || path.contains("..")) { + throw new IllegalArgumentException("Invalid path component"); + } + + return getTheFileSystem().getPath("/modules" + path); + } + + private FileSystem getTheFileSystem() { + checkPermission(); + FileSystem fs = this.theFileSystem; + if (fs == null) { + synchronized (this) { + fs = this.theFileSystem; + if (fs == null) { + try { + this.theFileSystem = fs = new JrtFileSystem(this, null); + } catch (IOException ioe) { + throw new InternalError(ioe); + } + } + } + } + return fs; + } + + @Override + public FileSystem getFileSystem(URI uri) { + checkPermission(); + checkUri(uri); + return getTheFileSystem(); + } + + // Checks that the given file is a JrtPath + static final JrtPath toJrtPath(Path path) { + Objects.requireNonNull(path, "path"); + if (!(path instanceof JrtPath)) { + throw new ProviderMismatchException(); + } + return (JrtPath) path; + } + + @Override + public void checkAccess(Path path, AccessMode... modes) throws IOException { + toJrtPath(path).checkAccess(modes); + } + + @Override + public Path readSymbolicLink(Path link) throws IOException { + return toJrtPath(link).readSymbolicLink(); + } + + @Override + public void copy(Path src, Path target, CopyOption... options) + throws IOException { + toJrtPath(src).copy(toJrtPath(target), options); + } + + @Override + public void createDirectory(Path path, FileAttribute... attrs) + throws IOException { + toJrtPath(path).createDirectory(attrs); + } + + @Override + public final void delete(Path path) throws IOException { + toJrtPath(path).delete(); + } + + @Override + @SuppressWarnings("unchecked") + public V + getFileAttributeView(Path path, Class type, LinkOption... options) { + return JrtFileAttributeView.get(toJrtPath(path), type, options); + } + + @Override + public FileStore getFileStore(Path path) throws IOException { + return toJrtPath(path).getFileStore(); + } + + @Override + public boolean isHidden(Path path) { + return toJrtPath(path).isHidden(); + } + + @Override + public boolean isSameFile(Path path, Path other) throws IOException { + return toJrtPath(path).isSameFile(other); + } + + @Override + public void move(Path src, Path target, CopyOption... options) + throws IOException { + toJrtPath(src).move(toJrtPath(target), options); + } + + @Override + public AsynchronousFileChannel newAsynchronousFileChannel(Path path, + Set options, + ExecutorService exec, + FileAttribute... attrs) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public SeekableByteChannel newByteChannel(Path path, + Set options, + FileAttribute... attrs) + throws IOException { + return toJrtPath(path).newByteChannel(options, attrs); + } + + @Override + public DirectoryStream newDirectoryStream( + Path path, Filter filter) throws IOException { + return toJrtPath(path).newDirectoryStream(filter); + } + + @Override + public FileChannel newFileChannel(Path path, + Set options, + FileAttribute... attrs) + throws IOException { + return toJrtPath(path).newFileChannel(options, attrs); + } + + @Override + public InputStream newInputStream(Path path, OpenOption... options) + throws IOException { + return toJrtPath(path).newInputStream(options); + } + + @Override + public OutputStream newOutputStream(Path path, OpenOption... options) + throws IOException { + return toJrtPath(path).newOutputStream(options); + } + + @Override + @SuppressWarnings("unchecked") // Cast to A + public A + readAttributes(Path path, Class type, LinkOption... options) + throws IOException { + if (type == BasicFileAttributes.class || type == JrtFileAttributes.class) { + return (A) toJrtPath(path).getAttributes(options); + } + return null; + } + + @Override + public Map + readAttributes(Path path, String attribute, LinkOption... options) + throws IOException { + return toJrtPath(path).readAttributes(attribute, options); + } + + @Override + public void setAttribute(Path path, String attribute, + Object value, LinkOption... options) + throws IOException { + toJrtPath(path).setAttribute(attribute, value, options); + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtPath$1.class b/tests/test_data/std/jdk/internal/jrtfs/JrtPath$1.class new file mode 100644 index 00000000..7cdfd6f6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtPath$1.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtPath$2.class b/tests/test_data/std/jdk/internal/jrtfs/JrtPath$2.class new file mode 100644 index 00000000..f3595108 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtPath$2.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtPath.class b/tests/test_data/std/jdk/internal/jrtfs/JrtPath.class new file mode 100644 index 00000000..d6725813 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtPath.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtPath.java b/tests/test_data/std/jdk/internal/jrtfs/JrtPath.java new file mode 100644 index 00000000..5010b653 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtPath.java @@ -0,0 +1,949 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.io.File; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.*; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileTime; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import static java.nio.file.StandardOpenOption.*; +import static java.nio.file.StandardCopyOption.*; + +/** + * Base class for Path implementation of jrt file systems. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class JrtPath implements Path { + + final JrtFileSystem jrtfs; + private final String path; + private volatile int[] offsets; + + JrtPath(JrtFileSystem jrtfs, String path) { + this.jrtfs = jrtfs; + this.path = normalize(path); + this.resolved = null; + } + + JrtPath(JrtFileSystem jrtfs, String path, boolean normalized) { + this.jrtfs = jrtfs; + this.path = normalized ? path : normalize(path); + this.resolved = null; + } + + final String getName() { + return path; + } + + @Override + public final JrtPath getRoot() { + if (this.isAbsolute()) { + return jrtfs.getRootPath(); + } else { + return null; + } + } + + @Override + public final JrtPath getFileName() { + if (path.isEmpty()) + return this; + if (path.length() == 1 && path.charAt(0) == '/') + return null; + int off = path.lastIndexOf('/'); + if (off == -1) + return this; + return new JrtPath(jrtfs, path.substring(off + 1), true); + } + + @Override + public final JrtPath getParent() { + initOffsets(); + int count = offsets.length; + if (count == 0) { // no elements so no parent + return null; + } + int off = offsets[count - 1] - 1; + if (off <= 0) { // parent is root only (may be null) + return getRoot(); + } + return new JrtPath(jrtfs, path.substring(0, off)); + } + + @Override + public final int getNameCount() { + initOffsets(); + return offsets.length; + } + + @Override + public final JrtPath getName(int index) { + initOffsets(); + if (index < 0 || index >= offsets.length) { + throw new IllegalArgumentException("index: " + + index + ", offsets length: " + offsets.length); + } + int begin = offsets[index]; + int end; + if (index == (offsets.length - 1)) { + end = path.length(); + } else { + end = offsets[index + 1]; + } + return new JrtPath(jrtfs, path.substring(begin, end)); + } + + @Override + public final JrtPath subpath(int beginIndex, int endIndex) { + initOffsets(); + if (beginIndex < 0 || endIndex > offsets.length || + beginIndex >= endIndex) { + throw new IllegalArgumentException( + "beginIndex: " + beginIndex + ", endIndex: " + endIndex + + ", offsets length: " + offsets.length); + } + // starting/ending offsets + int begin = offsets[beginIndex]; + int end; + if (endIndex == offsets.length) { + end = path.length(); + } else { + end = offsets[endIndex]; + } + return new JrtPath(jrtfs, path.substring(begin, end)); + } + + @Override + public final JrtPath toRealPath(LinkOption... options) throws IOException { + return jrtfs.toRealPath(this, options); + } + + @Override + public final JrtPath toAbsolutePath() { + if (isAbsolute()) + return this; + return new JrtPath(jrtfs, "/" + path, true); + } + + @Override + public final URI toUri() { + String p = toAbsolutePath().path; + if (!p.startsWith("/modules") || p.contains("..")) { + throw new IOError(new RuntimeException(p + " cannot be represented as URI")); + } + + p = p.substring("/modules".length()); + if (p.isEmpty()) { + p = "/"; + } + return toUri(p); + } + + private boolean equalsNameAt(JrtPath other, int index) { + int mbegin = offsets[index]; + int mlen; + if (index == (offsets.length - 1)) { + mlen = path.length() - mbegin; + } else { + mlen = offsets[index + 1] - mbegin - 1; + } + int obegin = other.offsets[index]; + int olen; + if (index == (other.offsets.length - 1)) { + olen = other.path.length() - obegin; + } else { + olen = other.offsets[index + 1] - obegin - 1; + } + if (mlen != olen) { + return false; + } + int n = 0; + while (n < mlen) { + if (path.charAt(mbegin + n) != other.path.charAt(obegin + n)) { + return false; + } + n++; + } + return true; + } + + @Override + public final JrtPath relativize(Path other) { + final JrtPath o = checkPath(other); + if (o.equals(this)) { + return new JrtPath(jrtfs, "", true); + } + if (path.isEmpty()) { + return o; + } + if (jrtfs != o.jrtfs || isAbsolute() != o.isAbsolute()) { + throw new IllegalArgumentException( + "Incorrect filesystem or path: " + other); + } + final String tp = this.path; + final String op = o.path; + if (op.startsWith(tp)) { // fast path + int off = tp.length(); + if (op.charAt(off - 1) == '/') + return new JrtPath(jrtfs, op.substring(off), true); + if (op.charAt(off) == '/') + return new JrtPath(jrtfs, op.substring(off + 1), true); + } + int mc = this.getNameCount(); + int oc = o.getNameCount(); + int n = Math.min(mc, oc); + int i = 0; + while (i < n) { + if (!equalsNameAt(o, i)) { + break; + } + i++; + } + int dotdots = mc - i; + int len = dotdots * 3 - 1; + if (i < oc) { + len += (o.path.length() - o.offsets[i] + 1); + } + StringBuilder sb = new StringBuilder(len); + while (dotdots > 0) { + sb.append(".."); + if (sb.length() < len) { // no tailing slash at the end + sb.append('/'); + } + dotdots--; + } + if (i < oc) { + sb.append(o.path, o.offsets[i], o.path.length()); + } + return new JrtPath(jrtfs, sb.toString(), true); + } + + @Override + public JrtFileSystem getFileSystem() { + return jrtfs; + } + + @Override + public final boolean isAbsolute() { + return !path.isEmpty() && path.charAt(0) == '/'; + } + + @Override + public final JrtPath resolve(Path other) { + final JrtPath o = checkPath(other); + if (this.path.isEmpty() || o.isAbsolute()) { + return o; + } + if (o.path.isEmpty()) { + return this; + } + StringBuilder sb = new StringBuilder(path.length() + o.path.length() + 1); + sb.append(path); + if (path.charAt(path.length() - 1) != '/') + sb.append('/'); + sb.append(o.path); + return new JrtPath(jrtfs, sb.toString(), true); + } + + @Override + public final Path resolveSibling(Path other) { + Objects.requireNonNull(other, "other"); + Path parent = getParent(); + return (parent == null) ? other : parent.resolve(other); + } + + @Override + public final boolean startsWith(Path other) { + if (!(Objects.requireNonNull(other) instanceof JrtPath)) + return false; + final JrtPath o = (JrtPath)other; + final String tp = this.path; + final String op = o.path; + if (isAbsolute() != o.isAbsolute() || !tp.startsWith(op)) { + return false; + } + int off = op.length(); + if (off == 0) { + return tp.isEmpty(); + } + // check match is on name boundary + return tp.length() == off || tp.charAt(off) == '/' || + off == 0 || op.charAt(off - 1) == '/'; + } + + @Override + public final boolean endsWith(Path other) { + if (!(Objects.requireNonNull(other) instanceof JrtPath)) + return false; + final JrtPath o = (JrtPath)other; + final JrtPath t = this; + int olast = o.path.length() - 1; + if (olast > 0 && o.path.charAt(olast) == '/') { + olast--; + } + int last = t.path.length() - 1; + if (last > 0 && t.path.charAt(last) == '/') { + last--; + } + if (olast == -1) { // o.path.length == 0 + return last == -1; + } + if ((o.isAbsolute() && (!t.isAbsolute() || olast != last)) + || last < olast) { + return false; + } + for (; olast >= 0; olast--, last--) { + if (o.path.charAt(olast) != t.path.charAt(last)) { + return false; + } + } + return o.path.charAt(olast + 1) == '/' || + last == -1 || t.path.charAt(last) == '/'; + } + + @Override + public final JrtPath resolve(String other) { + return resolve(getFileSystem().getPath(other)); + } + + @Override + public final Path resolveSibling(String other) { + return resolveSibling(getFileSystem().getPath(other)); + } + + @Override + public final boolean startsWith(String other) { + return startsWith(getFileSystem().getPath(other)); + } + + @Override + public final boolean endsWith(String other) { + return endsWith(getFileSystem().getPath(other)); + } + + @Override + public final JrtPath normalize() { + String res = getResolved(); + if (res == path) { // no change + return this; + } + return new JrtPath(jrtfs, res, true); + } + + private JrtPath checkPath(Path path) { + Objects.requireNonNull(path); + if (!(path instanceof JrtPath)) + throw new ProviderMismatchException("path class: " + + path.getClass()); + return (JrtPath) path; + } + + // create offset list if not already created + private void initOffsets() { + if (this.offsets == null) { + int len = path.length(); + // count names + int count = 0; + int off = 0; + while (off < len) { + char c = path.charAt(off++); + if (c != '/') { + count++; + off = path.indexOf('/', off); + if (off == -1) + break; + } + } + // populate offsets + int[] offsets = new int[count]; + count = 0; + off = 0; + while (off < len) { + char c = path.charAt(off); + if (c == '/') { + off++; + } else { + offsets[count++] = off++; + off = path.indexOf('/', off); + if (off == -1) + break; + } + } + this.offsets = offsets; + } + } + + private volatile String resolved; + + final String getResolvedPath() { + String r = resolved; + if (r == null) { + if (isAbsolute()) { + r = getResolved(); + } else { + r = toAbsolutePath().getResolvedPath(); + } + resolved = r; + } + return r; + } + + // removes redundant slashes, replace "\" to separator "/" + // and check for invalid characters + private static String normalize(String path) { + int len = path.length(); + if (len == 0) { + return path; + } + char prevC = 0; + for (int i = 0; i < len; i++) { + char c = path.charAt(i); + if (c == '\\' || c == '\u0000') { + return normalize(path, i); + } + if (c == '/' && prevC == '/') { + return normalize(path, i - 1); + } + prevC = c; + } + if (prevC == '/' && len > 1) { + return path.substring(0, len - 1); + } + return path; + } + + private static String normalize(String path, int off) { + int len = path.length(); + StringBuilder to = new StringBuilder(len); + to.append(path, 0, off); + char prevC = 0; + while (off < len) { + char c = path.charAt(off++); + if (c == '\\') { + c = '/'; + } + if (c == '/' && prevC == '/') { + continue; + } + if (c == '\u0000') { + throw new InvalidPathException(path, + "Path: NUL character not allowed"); + } + to.append(c); + prevC = c; + } + len = to.length(); + if (len > 1 && to.charAt(len - 1) == '/') { + to.deleteCharAt(len - 1); + } + return to.toString(); + } + + // Remove DotSlash(./) and resolve DotDot (..) components + private String getResolved() { + int length = path.length(); + if (length == 0 || (!path.contains("./") && path.charAt(length - 1) != '.')) { + return path; + } else { + return resolvePath(); + } + } + + private String resolvePath() { + int length = path.length(); + char[] to = new char[length]; + int nc = getNameCount(); + int[] lastM = new int[nc]; + int lastMOff = -1; + int m = 0; + for (int i = 0; i < nc; i++) { + int n = offsets[i]; + int len = (i == offsets.length - 1) ? length - n + : offsets[i + 1] - n - 1; + if (len == 1 && path.charAt(n) == '.') { + if (m == 0 && path.charAt(0) == '/') // absolute path + to[m++] = '/'; + continue; + } + if (len == 2 && path.charAt(n) == '.' && path.charAt(n + 1) == '.') { + if (lastMOff >= 0) { + m = lastM[lastMOff--]; // retreat + continue; + } + if (path.charAt(0) == '/') { // "/../xyz" skip + if (m == 0) + to[m++] = '/'; + } else { // "../xyz" -> "../xyz" + if (m != 0 && to[m-1] != '/') + to[m++] = '/'; + while (len-- > 0) + to[m++] = path.charAt(n++); + } + continue; + } + if (m == 0 && path.charAt(0) == '/' || // absolute path + m != 0 && to[m-1] != '/') { // not the first name + to[m++] = '/'; + } + lastM[++lastMOff] = m; + while (len-- > 0) + to[m++] = path.charAt(n++); + } + if (m > 1 && to[m - 1] == '/') + m--; + return (m == to.length) ? new String(to) : new String(to, 0, m); + } + + @Override + public final String toString() { + return path; + } + + @Override + public final int hashCode() { + return path.hashCode(); + } + + @Override + public final boolean equals(Object obj) { + return obj instanceof JrtPath && + this.path.equals(((JrtPath) obj).path); + } + + @Override + public final int compareTo(Path other) { + final JrtPath o = checkPath(other); + return path.compareTo(o.path); + } + + @Override + public final WatchKey register( + WatchService watcher, + WatchEvent.Kind[] events, + WatchEvent.Modifier... modifiers) { + Objects.requireNonNull(watcher, "watcher"); + Objects.requireNonNull(events, "events"); + Objects.requireNonNull(modifiers, "modifiers"); + throw new UnsupportedOperationException(); + } + + @Override + public final WatchKey register(WatchService watcher, WatchEvent.Kind... events) { + return register(watcher, events, new WatchEvent.Modifier[0]); + } + + @Override + public final File toFile() { + throw new UnsupportedOperationException(); + } + + @Override + public final Iterator iterator() { + return new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return (i < getNameCount()); + } + + @Override + public Path next() { + if (i < getNameCount()) { + Path result = getName(i); + i++; + return result; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + throw new ReadOnlyFileSystemException(); + } + }; + } + + // Helpers for JrtFileSystemProvider and JrtFileSystem + + final JrtPath readSymbolicLink() throws IOException { + if (!jrtfs.isLink(this)) { + throw new IOException("not a symbolic link"); + } + return jrtfs.resolveLink(this); + } + + final boolean isHidden() { + return false; + } + + final void createDirectory(FileAttribute... attrs) + throws IOException { + jrtfs.createDirectory(this, attrs); + } + + final InputStream newInputStream(OpenOption... options) throws IOException { + for (OpenOption opt : options) { + if (opt != READ) { + throw new UnsupportedOperationException("'" + opt + "' not allowed"); + } + } + return jrtfs.newInputStream(this); + } + + final DirectoryStream newDirectoryStream(Filter filter) + throws IOException { + return new JrtDirectoryStream(this, filter); + } + + final void delete() throws IOException { + jrtfs.deleteFile(this, true); + } + + final void deleteIfExists() throws IOException { + jrtfs.deleteFile(this, false); + } + + final JrtFileAttributes getAttributes(LinkOption... options) throws IOException { + return jrtfs.getFileAttributes(this, options); + } + + final void setAttribute(String attribute, Object value, LinkOption... options) + throws IOException { + JrtFileAttributeView.setAttribute(this, attribute, value); + } + + final Map readAttributes(String attributes, LinkOption... options) + throws IOException { + return JrtFileAttributeView.readAttributes(this, attributes, options); + } + + final void setTimes(FileTime mtime, FileTime atime, FileTime ctime) + throws IOException { + jrtfs.setTimes(this, mtime, atime, ctime); + } + + final FileStore getFileStore() throws IOException { + // each JrtFileSystem only has one root (as requested for now) + if (exists()) { + return jrtfs.getFileStore(this); + } + throw new NoSuchFileException(path); + } + + final boolean isSameFile(Path other) throws IOException { + if (this == other || this.equals(other)) { + return true; + } + if (other == null || this.getFileSystem() != other.getFileSystem()) { + return false; + } + this.checkAccess(); + JrtPath o = (JrtPath) other; + o.checkAccess(); + return this.getResolvedPath().equals(o.getResolvedPath()) || + jrtfs.isSameFile(this, o); + } + + final SeekableByteChannel newByteChannel(Set options, + FileAttribute... attrs) + throws IOException + { + return jrtfs.newByteChannel(this, options, attrs); + } + + final FileChannel newFileChannel(Set options, + FileAttribute... attrs) + throws IOException { + return jrtfs.newFileChannel(this, options, attrs); + } + + final void checkAccess(AccessMode... modes) throws IOException { + if (modes.length == 0) { // check if the path exists + jrtfs.checkNode(this); // no need to follow link. the "link" node + // is built from real node under "/module" + } else { + boolean w = false; + for (AccessMode mode : modes) { + switch (mode) { + case READ: + break; + case WRITE: + w = true; + break; + case EXECUTE: + throw new AccessDeniedException(toString()); + default: + throw new UnsupportedOperationException(); + } + } + jrtfs.checkNode(this); + if (w && jrtfs.isReadOnly()) { + throw new AccessDeniedException(toString()); + } + } + } + + final boolean exists() { + try { + return jrtfs.exists(this); + } catch (IOException x) {} + return false; + } + + final OutputStream newOutputStream(OpenOption... options) throws IOException { + if (options.length == 0) { + return jrtfs.newOutputStream(this, CREATE_NEW, WRITE); + } + return jrtfs.newOutputStream(this, options); + } + + final void move(JrtPath target, CopyOption... options) throws IOException { + if (this.jrtfs == target.jrtfs) { + jrtfs.copyFile(true, this, target, options); + } else { + copyToTarget(target, options); + delete(); + } + } + + final void copy(JrtPath target, CopyOption... options) throws IOException { + if (this.jrtfs == target.jrtfs) { + jrtfs.copyFile(false, this, target, options); + } else { + copyToTarget(target, options); + } + } + + private void copyToTarget(JrtPath target, CopyOption... options) + throws IOException { + boolean replaceExisting = false; + boolean copyAttrs = false; + for (CopyOption opt : options) { + if (opt == REPLACE_EXISTING) { + replaceExisting = true; + } else if (opt == COPY_ATTRIBUTES) { + copyAttrs = true; + } + } + // attributes of source file + BasicFileAttributes jrtfas = getAttributes(); + // check if target exists + boolean exists; + if (replaceExisting) { + try { + target.deleteIfExists(); + exists = false; + } catch (DirectoryNotEmptyException x) { + exists = true; + } + } else { + exists = target.exists(); + } + if (exists) { + throw new FileAlreadyExistsException(target.toString()); + } + if (jrtfas.isDirectory()) { + // create directory or file + target.createDirectory(); + } else { + try (InputStream is = jrtfs.newInputStream(this); + OutputStream os = target.newOutputStream()) { + byte[] buf = new byte[8192]; + int n; + while ((n = is.read(buf)) != -1) { + os.write(buf, 0, n); + } + } + } + if (copyAttrs) { + BasicFileAttributeView view = + Files.getFileAttributeView(target, BasicFileAttributeView.class); + try { + view.setTimes(jrtfas.lastModifiedTime(), + jrtfas.lastAccessTime(), + jrtfas.creationTime()); + } catch (IOException x) { + try { + target.delete(); // rollback? + } catch (IOException ignore) {} + throw x; + } + } + } + + // adopted from sun.nio.fs.UnixUriUtils + private static URI toUri(String str) { + char[] path = str.toCharArray(); + assert path[0] == '/'; + StringBuilder sb = new StringBuilder(); + sb.append(path[0]); + for (int i = 1; i < path.length; i++) { + char c = (char)(path[i] & 0xff); + if (match(c, L_PATH, H_PATH)) { + sb.append(c); + } else { + sb.append('%'); + sb.append(hexDigits[(c >> 4) & 0x0f]); + sb.append(hexDigits[(c) & 0x0f]); + } + } + + try { + return new URI("jrt:" + sb.toString()); + } catch (URISyntaxException x) { + throw new AssertionError(x); // should not happen + } + } + + // The following is copied from java.net.URI + + // Compute the low-order mask for the characters in the given string + private static long lowMask(String chars) { + int n = chars.length(); + long m = 0; + for (int i = 0; i < n; i++) { + char c = chars.charAt(i); + if (c < 64) + m |= (1L << c); + } + return m; + } + + // Compute the high-order mask for the characters in the given string + private static long highMask(String chars) { + int n = chars.length(); + long m = 0; + for (int i = 0; i < n; i++) { + char c = chars.charAt(i); + if ((c >= 64) && (c < 128)) + m |= (1L << (c - 64)); + } + return m; + } + + // Compute a low-order mask for the characters + // between first and last, inclusive + private static long lowMask(char first, char last) { + long m = 0; + int f = Math.max(Math.min(first, 63), 0); + int l = Math.max(Math.min(last, 63), 0); + for (int i = f; i <= l; i++) + m |= 1L << i; + return m; + } + + // Compute a high-order mask for the characters + // between first and last, inclusive + private static long highMask(char first, char last) { + long m = 0; + int f = Math.max(Math.min(first, 127), 64) - 64; + int l = Math.max(Math.min(last, 127), 64) - 64; + for (int i = f; i <= l; i++) + m |= 1L << i; + return m; + } + + // Tell whether the given character is permitted by the given mask pair + private static boolean match(char c, long lowMask, long highMask) { + if (c < 64) + return ((1L << c) & lowMask) != 0; + if (c < 128) + return ((1L << (c - 64)) & highMask) != 0; + return false; + } + + // digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | + // "8" | "9" + private static final long L_DIGIT = lowMask('0', '9'); + private static final long H_DIGIT = 0L; + + // upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | + // "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | + // "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" + private static final long L_UPALPHA = 0L; + private static final long H_UPALPHA = highMask('A', 'Z'); + + // lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | + // "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | + // "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" + private static final long L_LOWALPHA = 0L; + private static final long H_LOWALPHA = highMask('a', 'z'); + + // alpha = lowalpha | upalpha + private static final long L_ALPHA = L_LOWALPHA | L_UPALPHA; + private static final long H_ALPHA = H_LOWALPHA | H_UPALPHA; + + // alphanum = alpha | digit + private static final long L_ALPHANUM = L_DIGIT | L_ALPHA; + private static final long H_ALPHANUM = H_DIGIT | H_ALPHA; + + // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | + // "(" | ")" + private static final long L_MARK = lowMask("-_.!~*'()"); + private static final long H_MARK = highMask("-_.!~*'()"); + + // unreserved = alphanum | mark + private static final long L_UNRESERVED = L_ALPHANUM | L_MARK; + private static final long H_UNRESERVED = H_ALPHANUM | H_MARK; + + // pchar = unreserved | escaped | + // ":" | "@" | "&" | "=" | "+" | "$" | "," + private static final long L_PCHAR + = L_UNRESERVED | lowMask(":@&=+$,"); + private static final long H_PCHAR + = H_UNRESERVED | highMask(":@&=+$,"); + + // All valid path characters + private static final long L_PATH = L_PCHAR | lowMask(";/"); + private static final long H_PATH = H_PCHAR | highMask(";/"); + + private static final char[] hexDigits = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtUtils.class b/tests/test_data/std/jdk/internal/jrtfs/JrtUtils.class new file mode 100644 index 00000000..904834f2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/JrtUtils.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/JrtUtils.java b/tests/test_data/std/jdk/internal/jrtfs/JrtUtils.java new file mode 100644 index 00000000..29f681f5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/JrtUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jrtfs; + +import java.util.regex.PatternSyntaxException; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +final class JrtUtils { + private JrtUtils() {} + + private static final String regexMetaChars = ".^$+{[]|()"; + private static final String globMetaChars = "\\*?[{"; + private static boolean isRegexMeta(char c) { + return regexMetaChars.indexOf(c) != -1; + } + private static boolean isGlobMeta(char c) { + return globMetaChars.indexOf(c) != -1; + } + private static final char EOL = 0; + private static char next(String glob, int i) { + if (i < glob.length()) { + return glob.charAt(i); + } + return EOL; + } + + /* + * Creates a regex pattern from the given glob expression. + * + * @throws PatternSyntaxException + */ + public static String toRegexPattern(String globPattern) { + boolean inGroup = false; + StringBuilder regex = new StringBuilder("^"); + + int i = 0; + while (i < globPattern.length()) { + char c = globPattern.charAt(i++); + switch (c) { + case '\\': + // escape special characters + if (i == globPattern.length()) { + throw new PatternSyntaxException("No character to escape", + globPattern, i - 1); + } + char next = globPattern.charAt(i++); + if (isGlobMeta(next) || isRegexMeta(next)) { + regex.append('\\'); + } + regex.append(next); + break; + case '/': + regex.append(c); + break; + case '[': + // don't match name separator in class + regex.append("[[^/]&&["); + if (next(globPattern, i) == '^') { + // escape the regex negation char if it appears + regex.append("\\^"); + i++; + } else { + // negation + if (next(globPattern, i) == '!') { + regex.append('^'); + i++; + } + // hyphen allowed at start + if (next(globPattern, i) == '-') { + regex.append('-'); + i++; + } + } + boolean hasRangeStart = false; + char last = 0; + while (i < globPattern.length()) { + c = globPattern.charAt(i++); + if (c == ']') { + break; + } + if (c == '/') { + throw new PatternSyntaxException("Explicit 'name separator' in class", + globPattern, i - 1); + } + // TBD: how to specify ']' in a class? + if (c == '\\' || c == '[' || + c == '&' && next(globPattern, i) == '&') { + // escape '\', '[' or "&&" for regex class + regex.append('\\'); + } + regex.append(c); + + if (c == '-') { + if (!hasRangeStart) { + throw new PatternSyntaxException("Invalid range", + globPattern, i - 1); + } + if ((c = next(globPattern, i++)) == EOL || c == ']') { + break; + } + if (c < last) { + throw new PatternSyntaxException("Invalid range", + globPattern, i - 3); + } + regex.append(c); + hasRangeStart = false; + } else { + hasRangeStart = true; + last = c; + } + } + if (c != ']') { + throw new PatternSyntaxException("Missing ']", globPattern, i - 1); + } + regex.append("]]"); + break; + case '{': + if (inGroup) { + throw new PatternSyntaxException("Cannot nest groups", + globPattern, i - 1); + } + regex.append("(?:(?:"); + inGroup = true; + break; + case '}': + if (inGroup) { + regex.append("))"); + inGroup = false; + } else { + regex.append('}'); + } + break; + case ',': + if (inGroup) { + regex.append(")|(?:"); + } else { + regex.append(','); + } + break; + case '*': + if (next(globPattern, i) == '*') { + // crosses directory boundaries + regex.append(".*"); + i++; + } else { + // within directory boundary + regex.append("[^/]*"); + } + break; + case '?': + regex.append("[^/]"); + break; + default: + if (isRegexMeta(c)) { + regex.append('\\'); + } + regex.append(c); + } + } + if (inGroup) { + throw new PatternSyntaxException("Missing '}", globPattern, i - 1); + } + return regex.append('$').toString(); + } +} diff --git a/tests/test_data/std/jdk/internal/jrtfs/SystemImage$1.class b/tests/test_data/std/jdk/internal/jrtfs/SystemImage$1.class new file mode 100644 index 00000000..3f45add6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/SystemImage$1.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/SystemImage$2.class b/tests/test_data/std/jdk/internal/jrtfs/SystemImage$2.class new file mode 100644 index 00000000..1b9a0aa1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/SystemImage$2.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/SystemImage.class b/tests/test_data/std/jdk/internal/jrtfs/SystemImage.class new file mode 100644 index 00000000..9a09958e Binary files /dev/null and b/tests/test_data/std/jdk/internal/jrtfs/SystemImage.class differ diff --git a/tests/test_data/std/jdk/internal/jrtfs/SystemImage.java b/tests/test_data/std/jdk/internal/jrtfs/SystemImage.java new file mode 100644 index 00000000..e1770d9c --- /dev/null +++ b/tests/test_data/std/jdk/internal/jrtfs/SystemImage.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jrtfs; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PrivilegedAction; + +import jdk.internal.jimage.ImageReader; +import jdk.internal.jimage.ImageReader.Node; + +/** + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +@SuppressWarnings("removal") +abstract class SystemImage { + + abstract Node findNode(String path) throws IOException; + abstract byte[] getResource(Node node) throws IOException; + abstract void close() throws IOException; + + static SystemImage open() throws IOException { + if (modulesImageExists) { + // open a .jimage and build directory structure + final ImageReader image = ImageReader.open(moduleImageFile); + image.getRootDirectory(); + return new SystemImage() { + @Override + Node findNode(String path) throws IOException { + return image.findNode(path); + } + @Override + byte[] getResource(Node node) throws IOException { + return image.getResource(node); + } + @Override + void close() throws IOException { + image.close(); + } + }; + } + if (Files.notExists(explodedModulesDir)) + throw new FileSystemNotFoundException(explodedModulesDir.toString()); + return new ExplodedImage(explodedModulesDir); + } + + static final String RUNTIME_HOME; + // "modules" jimage file Path + static final Path moduleImageFile; + // "modules" jimage exists or not? + static final boolean modulesImageExists; + // /modules directory Path + static final Path explodedModulesDir; + + static { + PrivilegedAction pa = SystemImage::findHome; + RUNTIME_HOME = AccessController.doPrivileged(pa); + + FileSystem fs = FileSystems.getDefault(); + moduleImageFile = fs.getPath(RUNTIME_HOME, "lib", "modules"); + explodedModulesDir = fs.getPath(RUNTIME_HOME, "modules"); + + modulesImageExists = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Boolean run() { + return Files.isRegularFile(moduleImageFile); + } + }); + } + + /** + * Returns the appropriate JDK home for this usage of the FileSystemProvider. + * When the CodeSource is null (null loader) then jrt:/ is the current runtime, + * otherwise the JDK home is located relative to jrt-fs.jar. + */ + private static String findHome() { + CodeSource cs = SystemImage.class.getProtectionDomain().getCodeSource(); + if (cs == null) + return System.getProperty("java.home"); + + // assume loaded from $TARGETJDK/lib/jrt-fs.jar + URL url = cs.getLocation(); + if (!url.getProtocol().equalsIgnoreCase("file")) + throw new InternalError(url + " loaded in unexpected way"); + try { + Path lib = Paths.get(url.toURI()).getParent(); + if (!lib.getFileName().toString().equals("lib")) + throw new InternalError(url + " unexpected path"); + + return lib.getParent().toString(); + } catch (URISyntaxException e) { + throw new InternalError(e); + } + } +} diff --git a/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Memoizer$RecursiveInvocationException.class b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Memoizer$RecursiveInvocationException.class new file mode 100644 index 00000000..0aedf370 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Memoizer$RecursiveInvocationException.class differ diff --git a/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Memoizer.class b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Memoizer.class new file mode 100644 index 00000000..54fb01b4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Memoizer.class differ diff --git a/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Sub.class b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Sub.class new file mode 100644 index 00000000..89708436 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue$Sub.class differ diff --git a/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue.class b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue.class new file mode 100644 index 00000000..3e34a719 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue.class differ diff --git a/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue.java b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue.java new file mode 100644 index 00000000..739f9c0d --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/AbstractClassLoaderValue.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue} + * and {@link Sub sub}-ClassLoaderValue. + * + * @param the type of concrete ClassLoaderValue (this type) + * @param the type of values associated with ClassLoaderValue + */ +public abstract class AbstractClassLoaderValue, V> { + + /** + * Sole constructor. + */ + AbstractClassLoaderValue() {} + + /** + * Returns the key component of this ClassLoaderValue. The key component of + * the root-{@link ClassLoaderValue} is the ClassLoaderValue itself, + * while the key component of a {@link #sub(Object) sub}-ClassLoaderValue + * is what was given to construct it. + * + * @return the key component of this ClassLoaderValue. + */ + public abstract Object key(); + + /** + * Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given + * key component. + * + * @param key the key component of the sub-ClassLoaderValue. + * @param the type of the key component. + * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key + */ + public Sub sub(K key) { + return new Sub<>(key); + } + + /** + * Returns {@code true} if this ClassLoaderValue is equal to given {@code clv} + * or if this ClassLoaderValue was derived from given {@code clv} by a chain + * of {@link #sub(Object)} invocations. + * + * @param clv the ClassLoaderValue to test this against + * @return if this ClassLoaderValue is equal to given {@code clv} or + * its descendant + */ + public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv); + + /** + * Returns the value associated with this ClassLoaderValue and given ClassLoader + * or {@code null} if there is none. + * + * @param cl the ClassLoader for the associated value + * @return the value associated with this ClassLoaderValue and given ClassLoader + * or {@code null} if there is none. + */ + public V get(ClassLoader cl) { + Object val = AbstractClassLoaderValue.map(cl).get(this); + try { + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate recursive get() for the same key that is just + // being calculated in computeIfAbsent() + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from Memoizer - pretend + // that there was no entry + // (computeIfAbsent invocation will try to remove it anyway) + return null; + } + } + + /** + * Associates given value {@code v} with this ClassLoaderValue and given + * ClassLoader and returns {@code null} if there was no previously associated + * value or does nothing and returns previously associated value if there + * was one. + * + * @param cl the ClassLoader for the associated value + * @param v the value to associate + * @return previously associated value or null if there was none + */ + public V putIfAbsent(ClassLoader cl, V v) { + ConcurrentHashMap map = map(cl); + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + while (true) { + try { + Object val = map.putIfAbsent(clv, v); + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate RecursiveInvocationException for the same key that + // is just being calculated in computeIfAbsent + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from foreign Memoizer - + // pretend that there was no entry and retry + // (foreign computeIfAbsent invocation will try to remove it anyway) + } + // TODO: + // Thread.onSpinLoop(); // when available + } + } + + /** + * Removes the value associated with this ClassLoaderValue and given + * ClassLoader if the associated value is equal to given value {@code v} and + * returns {@code true} or does nothing and returns {@code false} if there is + * no currently associated value or it is not equal to given value {@code v}. + * + * @param cl the ClassLoader for the associated value + * @param v the value to compare with currently associated value + * @return {@code true} if the association was removed or {@code false} if not + */ + public boolean remove(ClassLoader cl, Object v) { + return AbstractClassLoaderValue.map(cl).remove(this, v); + } + + /** + * Returns the value associated with this ClassLoaderValue and given + * ClassLoader if there is one or computes the value by invoking given + * {@code mappingFunction}, associates it and returns it. + *

+ * Computation and association of the computed value is performed atomically + * by the 1st thread that requests a particular association while holding a + * lock associated with this ClassLoaderValue and given ClassLoader. + * Nested calls from the {@code mappingFunction} to {@link #get}, + * {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association + * are not allowed and throw {@link IllegalStateException}. Nested call to + * {@link #remove} for the same association is allowed but will always return + * {@code false} regardless of passed-in comparison value. Nested calls for + * other association(s) are allowed, but care should be taken to avoid + * deadlocks. When two threads perform nested computations of the overlapping + * set of associations they should always request them in the same order. + * + * @param cl the ClassLoader for the associated value + * @param mappingFunction the function to compute the value + * @return the value associated with this ClassLoaderValue and given + * ClassLoader. + * @throws IllegalStateException if a direct or indirect invocation from + * within given {@code mappingFunction} that + * computes the value of a particular association + * to {@link #get}, {@link #putIfAbsent} or + * {@link #computeIfAbsent} + * for the same association is attempted. + */ + public V computeIfAbsent(ClassLoader cl, + BiFunction< + ? super ClassLoader, + ? super CLV, + ? extends V + > mappingFunction) throws IllegalStateException { + ConcurrentHashMap map = map(cl); + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + Memoizer mv = null; + while (true) { + Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv); + if (val == null) { + if (mv == null) { + // create Memoizer lazily when 1st needed and restart loop + mv = new Memoizer<>(cl, clv, mappingFunction); + continue; + } + // mv != null, therefore sv == null was a result of successful + // putIfAbsent + try { + // trigger Memoizer to compute the value + V v = mv.get(); + // attempt to replace our Memoizer with the value + map.replace(clv, mv, v); + // return computed value + return v; + } catch (Throwable t) { + // our Memoizer has thrown, attempt to remove it + map.remove(clv, mv); + // propagate exception because it's from our Memoizer + throw t; + } + } else { + try { + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate recursive attempts to calculate the same + // value as being calculated at the moment + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from foreign Memoizer - + // pretend that there was no entry and retry + // (foreign computeIfAbsent invocation will try to remove it anyway) + } + } + // TODO: + // Thread.onSpinLoop(); // when available + } + } + + /** + * Removes all values associated with given ClassLoader {@code cl} and + * {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants} + * of this ClassLoaderValue. + * This is not an atomic operation. Other threads may see some associations + * be already removed and others still present while this method is executing. + *

+ * The sole intention of this method is to cleanup after a unit test that + * tests ClassLoaderValue directly. It is not intended for use in + * actual algorithms. + * + * @param cl the associated ClassLoader of the values to be removed + */ + public void removeAll(ClassLoader cl) { + ConcurrentHashMap map = map(cl); + for (Iterator i = map.keySet().iterator(); i.hasNext(); ) { + if (i.next().isEqualOrDescendantOf(this)) { + i.remove(); + } + } + } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + /** + * @return a ConcurrentHashMap for given ClassLoader + */ + @SuppressWarnings("unchecked") + private static > + ConcurrentHashMap map(ClassLoader cl) { + return (ConcurrentHashMap) + (cl == null ? BootLoader.getClassLoaderValueMap() + : JLA.createOrGetClassLoaderValueMap(cl)); + } + + /** + * @return value extracted from the {@link Memoizer} if given + * {@code memoizerOrValue} parameter is a {@code Memoizer} or + * just return given parameter. + */ + @SuppressWarnings("unchecked") + private V extractValue(Object memoizerOrValue) { + if (memoizerOrValue instanceof Memoizer) { + return ((Memoizer) memoizerOrValue).get(); + } else { + return (V) memoizerOrValue; + } + } + + /** + * A memoized supplier that invokes given {@code mappingFunction} just once + * and remembers the result or thrown exception for subsequent calls. + * If given mappingFunction returns null, it is converted to NullPointerException, + * thrown from the Memoizer's {@link #get()} method and remembered. + * If the Memoizer is invoked recursively from the given {@code mappingFunction}, + * {@link RecursiveInvocationException} is thrown, but it is not remembered. + * The in-flight call to the {@link #get()} can still complete successfully if + * such exception is handled by the mappingFunction. + */ + private static final class Memoizer, V> + implements Supplier { + + private final ClassLoader cl; + private final CLV clv; + private final BiFunction + mappingFunction; + + private volatile V v; + private volatile Throwable t; + private boolean inCall; + + Memoizer(ClassLoader cl, + CLV clv, + BiFunction + mappingFunction + ) { + this.cl = cl; + this.clv = clv; + this.mappingFunction = mappingFunction; + } + + @Override + public V get() throws RecursiveInvocationException { + V v = this.v; + if (v != null) return v; + Throwable t = this.t; + if (t == null) { + synchronized (this) { + if ((v = this.v) == null && (t = this.t) == null) { + if (inCall) { + throw new RecursiveInvocationException(); + } + inCall = true; + try { + this.v = v = Objects.requireNonNull( + mappingFunction.apply(cl, clv)); + } catch (Throwable x) { + this.t = t = x; + } finally { + inCall = false; + } + } + } + } + if (v != null) return v; + if (t instanceof Error) { + throw (Error) t; + } else if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else { + throw new UndeclaredThrowableException(t); + } + } + + static class RecursiveInvocationException extends IllegalStateException { + @java.io.Serial + private static final long serialVersionUID = 1L; + + RecursiveInvocationException() { + super("Recursive call"); + } + } + } + + /** + * sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue} + * and also a subclass of it. It can therefore be instantiated as an inner + * class of either an instance of root-{@link ClassLoaderValue} or another + * instance of itself. This enables composing type-safe compound keys of + * arbitrary length: + *

{@code
+     * ClassLoaderValue clv = new ClassLoaderValue<>();
+     * ClassLoaderValue.Sub.Sub.Sub clv_k123 =
+     *     clv.sub(k1).sub(k2).sub(k3);
+     * }
+ * From which individual components are accessible in a type-safe way: + *
{@code
+     * K1 k1 = clv_k123.parent().parent().key();
+     * K2 k2 = clv_k123.parent().key();
+     * K3 k3 = clv_k123.key();
+     * }
+ * This allows specifying non-capturing lambdas for the mapping function of + * {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can + * access individual key components from passed-in + * sub-[sub-...]ClassLoaderValue instance in a type-safe way. + * + * @param the type of {@link #key()} component contained in the + * sub-ClassLoaderValue. + */ + public final class Sub extends AbstractClassLoaderValue, V> { + + private final K key; + + Sub(K key) { + this.key = key; + } + + /** + * @return the parent ClassLoaderValue this sub-ClassLoaderValue + * has been {@link #sub(Object) derived} from. + */ + public AbstractClassLoaderValue parent() { + return AbstractClassLoaderValue.this; + } + + /** + * @return the key component of this sub-ClassLoaderValue. + */ + @Override + public K key() { + return key; + } + + /** + * sub-ClassLoaderValue is a descendant of given {@code clv} if it is + * either equal to it or if its {@link #parent() parent} is a + * descendant of given {@code clv}. + */ + @Override + public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { + return equals(Objects.requireNonNull(clv)) || + parent().isEqualOrDescendantOf(clv); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Sub)) return false; + @SuppressWarnings("unchecked") + Sub that = (Sub) o; + return this.parent().equals(that.parent()) && + Objects.equals(this.key, that.key); + } + + @Override + public int hashCode() { + return 31 * parent().hashCode() + + Objects.hashCode(key); + } + } +} diff --git a/tests/test_data/std/jdk/internal/loader/ArchivedClassLoaders.class b/tests/test_data/std/jdk/internal/loader/ArchivedClassLoaders.class new file mode 100644 index 00000000..41934abe Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ArchivedClassLoaders.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ArchivedClassLoaders.java b/tests/test_data/std/jdk/internal/loader/ArchivedClassLoaders.java new file mode 100644 index 00000000..9fcb8cc5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/ArchivedClassLoaders.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.loader; + +import java.util.Map; +import jdk.internal.misc.CDS; +import jdk.internal.module.ServicesCatalog; + +/** + * Used to archive the built-in class loaders, their services catalogs, and the + * package-to-module map used by the built-in class loaders. + */ +class ArchivedClassLoaders { + private static ArchivedClassLoaders archivedClassLoaders; + + private final ClassLoader bootLoader; + private final ClassLoader platformLoader; + private final ClassLoader appLoader; + private final ServicesCatalog[] servicesCatalogs; + private final Map packageToModule; + + private ArchivedClassLoaders() { + bootLoader = ClassLoaders.bootLoader(); + platformLoader = ClassLoaders.platformClassLoader(); + appLoader = ClassLoaders.appClassLoader(); + + servicesCatalogs = new ServicesCatalog[3]; + servicesCatalogs[0] = ServicesCatalog.getServicesCatalog(bootLoader); + servicesCatalogs[1] = ServicesCatalog.getServicesCatalog(platformLoader); + servicesCatalogs[2] = ServicesCatalog.getServicesCatalog(appLoader); + + packageToModule = BuiltinClassLoader.packageToModule(); + } + + ClassLoader bootLoader() { + return bootLoader; + } + + ClassLoader platformLoader() { + return platformLoader; + } + + ClassLoader appLoader() { + return appLoader; + } + + ServicesCatalog servicesCatalog(ClassLoader loader) { + if (loader == bootLoader) { + return servicesCatalogs[0]; + } else if (loader == platformLoader) { + return servicesCatalogs[1]; + } else if (loader == appLoader) { + return servicesCatalogs[2]; + } else { + throw new InternalError(); + } + } + + Map packageToModule() { + return packageToModule; + } + + static void archive() { + archivedClassLoaders = new ArchivedClassLoaders(); + } + + static ArchivedClassLoaders get() { + return archivedClassLoaders; + } + + static { + CDS.initializeFromArchive(ArchivedClassLoaders.class); + } +} diff --git a/tests/test_data/std/jdk/internal/loader/BootLoader$1.class b/tests/test_data/std/jdk/internal/loader/BootLoader$1.class new file mode 100644 index 00000000..c997a00e Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BootLoader$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper$1.class b/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper$1.class new file mode 100644 index 00000000..c8db826a Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper$2.class b/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper$2.class new file mode 100644 index 00000000..428e8070 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper$2.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper.class b/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper.class new file mode 100644 index 00000000..38124949 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BootLoader$PackageHelper.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BootLoader.class b/tests/test_data/std/jdk/internal/loader/BootLoader.class new file mode 100644 index 00000000..8348e1b1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BootLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BootLoader.java b/tests/test_data/std/jdk/internal/loader/BootLoader.java new file mode 100644 index 00000000..98ff60a7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/BootLoader.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.loader; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.module.ModuleReference; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; +import java.util.stream.Stream; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.module.Modules; +import jdk.internal.module.ServicesCatalog; +import jdk.internal.util.StaticProperty; + +/** + * Find resources and packages in modules defined to the boot class loader or + * resources and packages on the "boot class path" specified via -Xbootclasspath/a. + */ + +public class BootLoader { + private BootLoader() { } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + // The unnamed module for the boot loader + private static final Module UNNAMED_MODULE; + private static final String JAVA_HOME = StaticProperty.javaHome(); + + static { + JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); + UNNAMED_MODULE = jla.defineUnnamedModule(null); + jla.addEnableNativeAccess(UNNAMED_MODULE); + setBootLoaderUnnamedModule0(UNNAMED_MODULE); + } + + // ClassLoaderValue map for the boot class loader + private static final ConcurrentHashMap CLASS_LOADER_VALUE_MAP + = new ConcurrentHashMap<>(); + + // native libraries loaded by the boot class loader + private static final NativeLibraries NATIVE_LIBS + = NativeLibraries.newInstance(null); + + /** + * Returns the unnamed module for the boot loader. + */ + public static Module getUnnamedModule() { + return UNNAMED_MODULE; + } + + /** + * Returns the ServiceCatalog for modules defined to the boot class loader. + */ + public static ServicesCatalog getServicesCatalog() { + return ServicesCatalog.getServicesCatalog(ClassLoaders.bootLoader()); + } + + /** + * Returns the ClassLoaderValue map for the boot class loader. + */ + public static ConcurrentHashMap getClassLoaderValueMap() { + return CLASS_LOADER_VALUE_MAP; + } + + /** + * Returns NativeLibraries for the boot class loader. + */ + public static NativeLibraries getNativeLibraries() { + return NATIVE_LIBS; + } + + /** + * Returns {@code true} if there is a class path associated with the + * BootLoader. + */ + public static boolean hasClassPath() { + return ClassLoaders.bootLoader().hasClassPath(); + } + + /** + * Registers a module with this class loader so that its classes + * (and resources) become visible via this class loader. + */ + public static void loadModule(ModuleReference mref) { + ClassLoaders.bootLoader().loadModule(mref); + } + + /** + * Loads the Class object with the given name defined to the boot loader. + */ + public static Class loadClassOrNull(String name) { + return JLA.findBootstrapClassOrNull(name); + } + + /** + * Loads the Class object with the given name in the given module + * defined to the boot loader. Returns {@code null} if not found. + */ + public static Class loadClass(Module module, String name) { + Class c = loadClassOrNull(name); + if (c != null && c.getModule() == module) { + return c; + } else { + return null; + } + } + + /** + * Loads a native library from the system library path. + */ + @SuppressWarnings("removal") + public static void loadLibrary(String name) { + if (System.getSecurityManager() == null) { + BootLoader.getNativeLibraries().loadLibrary(name); + } else { + AccessController.doPrivileged(new java.security.PrivilegedAction<>() { + public Void run() { + BootLoader.getNativeLibraries().loadLibrary(name); + return null; + } + }); + } + } + + /** + * Returns a URL to a resource in a module defined to the boot loader. + */ + public static URL findResource(String mn, String name) throws IOException { + return ClassLoaders.bootLoader().findResource(mn, name); + } + + /** + * Returns an input stream to a resource in a module defined to the + * boot loader. + */ + public static InputStream findResourceAsStream(String mn, String name) + throws IOException + { + return ClassLoaders.bootLoader().findResourceAsStream(mn, name); + } + + /** + * Returns the URL to the given resource in any of the modules + * defined to the boot loader and the boot class path. + */ + public static URL findResource(String name) { + return ClassLoaders.bootLoader().findResource(name); + } + + /** + * Returns an Iterator to iterate over the resources of the given name + * in any of the modules defined to the boot loader. + */ + public static Enumeration findResources(String name) throws IOException { + return ClassLoaders.bootLoader().findResources(name); + } + + /** + * Define a package for the given class to the boot loader, if not already + * defined. + */ + public static Package definePackage(Class c) { + return getDefinedPackage(c.getPackageName()); + } + + /** + * Returns the Package of the given name defined to the boot loader or null + * if the package has not been defined. + */ + public static Package getDefinedPackage(String pn) { + Package pkg = ClassLoaders.bootLoader().getDefinedPackage(pn); + if (pkg == null) { + String location = getSystemPackageLocation(pn.replace('.', '/')); + if (location != null) { + pkg = PackageHelper.definePackage(pn.intern(), location); + } + } + return pkg; + } + + /** + * Returns a stream of the packages defined to the boot loader. + */ + public static Stream packages() { + return Arrays.stream(getSystemPackageNames()) + .map(name -> getDefinedPackage(name.replace('/', '.'))); + } + + /** + * Helper class to define {@code Package} objects for packages in modules + * defined to the boot loader. + */ + static class PackageHelper { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + /** + * Define the {@code Package} with the given name. The specified + * location is a jrt URL to a named module in the run-time image, + * a file URL to a module in an exploded run-time image, or a file + * path to an entry on the boot class path (java agent Boot-Class-Path + * or -Xbootclasspath/a. + * + *

If the given location is a JAR file containing a manifest, + * the defined Package contains the versioning information from + * the manifest, if present. + * + * @param name package name + * @param location location where the package is (jrt URL or file URL + * for a named module in the run-time or exploded image; + * a file path for a package from -Xbootclasspath/a) + */ + static Package definePackage(String name, String location) { + Module module = findModule(location); + if (module != null) { + // named module from runtime image or exploded module + if (name.isEmpty()) + throw new InternalError("empty package in " + location); + return JLA.definePackage(ClassLoaders.bootLoader(), name, module); + } + + // package in unnamed module (-Xbootclasspath/a) + URL url = toFileURL(location); + Manifest man = url != null ? getManifest(location) : null; + + return ClassLoaders.bootLoader().defineOrCheckPackage(name, man, url); + } + + /** + * Finds the module at the given location defined to the boot loader. + * The module is either in runtime image or exploded image. + * Otherwise this method returns null. + */ + private static Module findModule(String location) { + String mn = null; + if (location.startsWith("jrt:/")) { + // named module in runtime image ("jrt:/".length() == 5) + mn = location.substring(5, location.length()); + } else if (location.startsWith("file:/")) { + // named module in exploded image + Path path = Path.of(URI.create(location)); + Path modulesDir = Path.of(JAVA_HOME, "modules"); + if (path.startsWith(modulesDir)) { + mn = path.getFileName().toString(); + } + } + + // return the Module object for the module name. The Module may + // in the boot layer or a child layer for the case that the module + // is loaded into a running VM + if (mn != null) { + String name = mn; + return Modules.findLoadedModule(mn) + .orElseThrow(() -> new InternalError(name + " not loaded")); + } else { + return null; + } + } + + /** + * Returns URL if the given location is a regular file path. + */ + @SuppressWarnings("removal") + private static URL toFileURL(String location) { + return AccessController.doPrivileged(new PrivilegedAction<>() { + public URL run() { + Path path = Path.of(location); + if (Files.isRegularFile(path)) { + try { + return path.toUri().toURL(); + } catch (MalformedURLException e) {} + } + return null; + } + }); + } + + /** + * Returns the Manifest if the given location is a JAR file + * containing a manifest. + */ + @SuppressWarnings("removal") + private static Manifest getManifest(String location) { + return AccessController.doPrivileged(new PrivilegedAction<>() { + public Manifest run() { + Path jar = Path.of(location); + try (InputStream in = Files.newInputStream(jar); + JarInputStream jis = new JarInputStream(in, false)) { + return jis.getManifest(); + } catch (IOException e) { + return null; + } + } + }); + } + } + + /** + * Returns an array of the binary name of the packages defined by + * the boot loader, in VM internal form (forward slashes instead of dot). + */ + private static native String[] getSystemPackageNames(); + + /** + * Returns the location of the package of the given name, if + * defined by the boot loader; otherwise {@code null} is returned. + * + * The location may be a module from the runtime image or exploded image, + * or from the boot class append path (i.e. -Xbootclasspath/a or + * BOOT-CLASS-PATH attribute specified in java agent). + */ + private static native String getSystemPackageLocation(String name); + private static native void setBootLoaderUnnamedModule0(Module module); +} diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$1.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$1.class new file mode 100644 index 00000000..eefd4f8e Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$2.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$2.class new file mode 100644 index 00000000..8ca8a449 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$2.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$3.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$3.class new file mode 100644 index 00000000..b3ebc7de Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$3.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$4.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$4.class new file mode 100644 index 00000000..7849717b Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$4.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$5.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$5.class new file mode 100644 index 00000000..e8ece064 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$5.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$LoadedModule.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$LoadedModule.class new file mode 100644 index 00000000..0dc5214d Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$LoadedModule.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$NullModuleReader.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$NullModuleReader.class new file mode 100644 index 00000000..3043ddc0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader$NullModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader.class b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader.class new file mode 100644 index 00000000..7913316d Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader.java b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader.java new file mode 100644 index 00000000..1f7df790 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/BuiltinClassLoader.java @@ -0,0 +1,1088 @@ +/* + * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleReference; +import java.lang.module.ModuleReader; +import java.lang.ref.SoftReference; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.SecureClassLoader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.stream.Stream; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.VM; +import jdk.internal.module.ModulePatcher.PatchedModuleReader; +import jdk.internal.module.Resources; +import jdk.internal.vm.annotation.Stable; +import sun.security.util.LazyCodeSourcePermissionCollection; + + +/** + * The platform or application class loader. Resources loaded from modules + * defined to the boot class loader are also loaded via an instance of this + * ClassLoader type. + * + *

This ClassLoader supports loading of classes and resources from modules. + * Modules are defined to the ClassLoader by invoking the {@link #loadModule} + * method. Defining a module to this ClassLoader has the effect of making the + * types in the module visible.

+ * + *

This ClassLoader also supports loading of classes and resources from a + * class path of URLs that are specified to the ClassLoader at construction + * time. The class path may expand at runtime (the Class-Path attribute in JAR + * files or via instrumentation agents).

+ * + *

The delegation model used by this ClassLoader differs to the regular + * delegation model. When requested to load a class then this ClassLoader first + * maps the class name to its package name. If there is a module defined to a + * BuiltinClassLoader containing this package then the class loader delegates + * directly to that class loader. If there isn't a module containing the + * package then it delegates the search to the parent class loader and if not + * found in the parent then it searches the class path. The main difference + * between this and the usual delegation model is that it allows the platform + * class loader to delegate to the application class loader, important with + * upgraded modules defined to the platform class loader. + */ + +public class BuiltinClassLoader + extends SecureClassLoader +{ + static { + if (!ClassLoader.registerAsParallelCapable()) + throw new InternalError("Unable to register as parallel capable"); + } + + // parent ClassLoader + private final BuiltinClassLoader parent; + + // the URL class path, or null if there is no class path + private @Stable URLClassPath ucp; + + /** + * A module defined/loaded by a built-in class loader. + * + * A LoadedModule encapsulates a ModuleReference along with its CodeSource + * URL to avoid needing to create this URL when defining classes. + */ + private static class LoadedModule { + private final BuiltinClassLoader loader; + private final ModuleReference mref; + private final URI uri; // may be null + private @Stable URL codeSourceURL; // may be null + + LoadedModule(BuiltinClassLoader loader, ModuleReference mref) { + URL url = null; + this.uri = mref.location().orElse(null); + + // for non-jrt schemes we need to resolve the codeSourceURL + // eagerly during bootstrap since the handler might be + // overridden + if (uri != null && !"jrt".equals(uri.getScheme())) { + url = createURL(uri); + } + this.loader = loader; + this.mref = mref; + this.codeSourceURL = url; + } + + BuiltinClassLoader loader() { return loader; } + ModuleReference mref() { return mref; } + String name() { return mref.descriptor().name(); } + + URL codeSourceURL() { + URL url = codeSourceURL; + if (url == null && uri != null) { + codeSourceURL = url = createURL(uri); + } + return url; + } + + private URL createURL(URI uri) { + URL url = null; + try { + url = uri.toURL(); + } catch (MalformedURLException | IllegalArgumentException e) { + } + return url; + } + } + + // maps package name to loaded module for modules in the boot layer + private static final Map packageToModule; + static { + ArchivedClassLoaders archivedClassLoaders = ArchivedClassLoaders.get(); + if (archivedClassLoaders != null) { + @SuppressWarnings("unchecked") + Map map + = (Map) archivedClassLoaders.packageToModule(); + packageToModule = map; + } else { + packageToModule = new ConcurrentHashMap<>(1024); + } + } + + /** + * Invoked by ArchivedClassLoaders to archive the package-to-module map. + */ + static Map packageToModule() { + return packageToModule; + } + + // maps a module name to a module reference + private final Map nameToModule; + + // maps a module reference to a module reader + private final Map moduleToReader; + + // cache of resource name -> list of URLs. + // used only for resources that are not in module packages + private volatile SoftReference>> resourceCache; + + /** + * Create a new instance. + */ + BuiltinClassLoader(String name, BuiltinClassLoader parent, URLClassPath ucp) { + // ensure getParent() returns null when the parent is the boot loader + super(name, parent == null || parent == ClassLoaders.bootLoader() ? null : parent); + + this.parent = parent; + this.ucp = ucp; + + this.nameToModule = new ConcurrentHashMap<>(32); + this.moduleToReader = new ConcurrentHashMap<>(); + } + + /** + * Appends to the given file path to the class path. + */ + void appendClassPath(String path) { + // assert ucp != null; + ucp.addFile(path); + } + + /** + * Sets the class path, called to reset the class path during -Xshare:dump + */ + void setClassPath(URLClassPath ucp) { + this.ucp = ucp; + } + + /** + * Returns {@code true} if there is a class path associated with this + * class loader. + */ + boolean hasClassPath() { + return ucp != null; + } + + /** + * Register a module this class loader. This has the effect of making the + * types in the module visible. + */ + public void loadModule(ModuleReference mref) { + ModuleDescriptor descriptor = mref.descriptor(); + String mn = descriptor.name(); + if (nameToModule.putIfAbsent(mn, mref) != null) { + throw new InternalError(mn + " already defined to this loader"); + } + + LoadedModule loadedModule = new LoadedModule(this, mref); + for (String pn : descriptor.packages()) { + LoadedModule other = packageToModule.putIfAbsent(pn, loadedModule); + if (other != null) { + throw new InternalError(pn + " in modules " + mn + " and " + + other.name()); + } + } + + // clear resources cache if VM is already initialized + if (resourceCache != null && VM.isModuleSystemInited()) { + resourceCache = null; + } + } + + /** + * Returns the {@code ModuleReference} for the named module defined to + * this class loader; or {@code null} if not defined. + * + * @param name The name of the module to find + */ + protected ModuleReference findModule(String name) { + return nameToModule.get(name); + } + + + // -- finding resources + + /** + * Returns a URL to a resource of the given name in a module defined to + * this class loader. + */ + @Override + public URL findResource(String mn, String name) throws IOException { + URL url = null; + + if (mn != null) { + // find in module + ModuleReference mref = nameToModule.get(mn); + if (mref != null) { + url = findResource(mref, name); + } + } else { + // find on class path + url = findResourceOnClassPath(name); + } + + return checkURL(url); // check access before returning + } + + /** + * Returns an input stream to a resource of the given name in a module + * defined to this class loader. + */ + @SuppressWarnings("removal") + public InputStream findResourceAsStream(String mn, String name) + throws IOException + { + // Need URL to resource when running with a security manager so that + // the right permission check is done. + if (System.getSecurityManager() != null || mn == null) { + URL url = findResource(mn, name); + return (url != null) ? url.openStream() : null; + } + + // find in module defined to this loader, no security manager + ModuleReference mref = nameToModule.get(mn); + if (mref != null) { + return moduleReaderFor(mref).open(name).orElse(null); + } else { + return null; + } + } + + /** + * Finds a resource with the given name in the modules defined to this + * class loader or its class path. + */ + @Override + public URL findResource(String name) { + String pn = Resources.toPackageName(name); + LoadedModule module = packageToModule.get(pn); + if (module != null) { + + // resource is in a package of a module defined to this loader + if (module.loader() == this) { + URL url; + try { + url = findResource(module.name(), name); // checks URL + } catch (IOException ioe) { + return null; + } + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + return url; + } + } + + } else { + + // not in a module package but may be in module defined to this loader + try { + List urls = findMiscResource(name); + if (!urls.isEmpty()) { + URL url = urls.get(0); + if (url != null) { + return checkURL(url); // check access before returning + } + } + } catch (IOException ioe) { + return null; + } + + } + + // search class path + URL url = findResourceOnClassPath(name); + return checkURL(url); + } + + /** + * Returns an enumeration of URL objects to all the resources with the + * given name in modules defined to this class loader or on the class + * path of this loader. + */ + @Override + public Enumeration findResources(String name) throws IOException { + List checked = new ArrayList<>(); // list of checked URLs + + String pn = Resources.toPackageName(name); + LoadedModule module = packageToModule.get(pn); + if (module != null) { + + // resource is in a package of a module defined to this loader + if (module.loader() == this) { + URL url = findResource(module.name(), name); // checks URL + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + checked.add(url); + } + } + + } else { + // not in a package of a module defined to this loader + for (URL url : findMiscResource(name)) { + url = checkURL(url); + if (url != null) { + checked.add(url); + } + } + } + + // class path (not checked) + Enumeration e = findResourcesOnClassPath(name); + + // concat the checked URLs and the (not checked) class path + return new Enumeration<>() { + final Iterator iterator = checked.iterator(); + URL next; + private boolean hasNext() { + if (next != null) { + return true; + } else if (iterator.hasNext()) { + next = iterator.next(); + return true; + } else { + // need to check each URL + while (e.hasMoreElements() && next == null) { + next = checkURL(e.nextElement()); + } + return next != null; + } + } + @Override + public boolean hasMoreElements() { + return hasNext(); + } + @Override + public URL nextElement() { + if (hasNext()) { + URL result = next; + next = null; + return result; + } else { + throw new NoSuchElementException(); + } + } + }; + + } + + /** + * Returns the list of URLs to a "miscellaneous" resource in modules + * defined to this loader. A miscellaneous resource is not in a module + * package, e.g. META-INF/services/p.S. + * + * The cache used by this method avoids repeated searching of all modules. + */ + @SuppressWarnings("removal") + private List findMiscResource(String name) throws IOException { + SoftReference>> ref = this.resourceCache; + Map> map = (ref != null) ? ref.get() : null; + if (map == null) { + // only cache resources after VM is fully initialized + if (VM.isModuleSystemInited()) { + map = new ConcurrentHashMap<>(); + this.resourceCache = new SoftReference<>(map); + } + } else { + List urls = map.get(name); + if (urls != null) + return urls; + } + + // search all modules for the resource + List urls; + try { + urls = AccessController.doPrivileged( + new PrivilegedExceptionAction<>() { + @Override + public List run() throws IOException { + List result = null; + for (ModuleReference mref : nameToModule.values()) { + URI u = moduleReaderFor(mref).find(name).orElse(null); + if (u != null) { + try { + if (result == null) + result = new ArrayList<>(); + result.add(u.toURL()); + } catch (MalformedURLException | + IllegalArgumentException e) { + } + } + } + return (result != null) ? result : Collections.emptyList(); + } + }); + } catch (PrivilegedActionException pae) { + throw (IOException) pae.getCause(); + } + + // only cache resources after VM is fully initialized + if (map != null) { + map.putIfAbsent(name, urls); + } + + return urls; + } + + /** + * Returns the URL to a resource in a module or {@code null} if not found. + */ + @SuppressWarnings("removal") + private URL findResource(ModuleReference mref, String name) throws IOException { + URI u; + if (System.getSecurityManager() == null) { + u = moduleReaderFor(mref).find(name).orElse(null); + } else { + try { + u = AccessController.doPrivileged(new PrivilegedExceptionAction<> () { + @Override + public URI run() throws IOException { + return moduleReaderFor(mref).find(name).orElse(null); + } + }); + } catch (PrivilegedActionException pae) { + throw (IOException) pae.getCause(); + } + } + if (u != null) { + try { + return u.toURL(); + } catch (MalformedURLException | IllegalArgumentException e) { } + } + return null; + } + + /** + * Returns the URL to a resource in a module. Returns {@code null} if not found + * or an I/O error occurs. + */ + private URL findResourceOrNull(ModuleReference mref, String name) { + try { + return findResource(mref, name); + } catch (IOException ignore) { + return null; + } + } + + /** + * Returns a URL to a resource on the class path. + */ + @SuppressWarnings("removal") + private URL findResourceOnClassPath(String name) { + if (hasClassPath()) { + if (System.getSecurityManager() == null) { + return ucp.findResource(name, false); + } else { + PrivilegedAction pa = () -> ucp.findResource(name, false); + return AccessController.doPrivileged(pa); + } + } else { + // no class path + return null; + } + } + + /** + * Returns the URLs of all resources of the given name on the class path. + */ + @SuppressWarnings("removal") + private Enumeration findResourcesOnClassPath(String name) { + if (hasClassPath()) { + if (System.getSecurityManager() == null) { + return ucp.findResources(name, false); + } else { + PrivilegedAction> pa; + pa = () -> ucp.findResources(name, false); + return AccessController.doPrivileged(pa); + } + } else { + // no class path + return Collections.emptyEnumeration(); + } + } + + // -- finding/loading classes + + /** + * Finds the class with the specified binary name. + */ + @Override + protected Class findClass(String cn) throws ClassNotFoundException { + // no class loading until VM is fully initialized + if (!VM.isModuleSystemInited()) + throw new ClassNotFoundException(cn); + + // find the candidate module for this class + LoadedModule loadedModule = findLoadedModule(cn); + + Class c = null; + if (loadedModule != null) { + + // attempt to load class in module defined to this loader + if (loadedModule.loader() == this) { + c = findClassInModuleOrNull(loadedModule, cn); + } + + } else { + + // search class path + if (hasClassPath()) { + c = findClassOnClassPathOrNull(cn); + } + + } + + // not found + if (c == null) + throw new ClassNotFoundException(cn); + + return c; + } + + /** + * Finds the class with the specified binary name in a module. + * This method returns {@code null} if the class cannot be found + * or not defined in the specified module. + */ + @Override + protected Class findClass(String mn, String cn) { + if (mn != null) { + // find the candidate module for this class + LoadedModule loadedModule = findLoadedModule(mn, cn); + if (loadedModule == null) { + return null; + } + + // attempt to load class in module defined to this loader + assert loadedModule.loader() == this; + return findClassInModuleOrNull(loadedModule, cn); + } + + // search class path + if (hasClassPath()) { + return findClassOnClassPathOrNull(cn); + } + + return null; + } + + /** + * Loads the class with the specified binary name. + */ + @Override + protected Class loadClass(String cn, boolean resolve) + throws ClassNotFoundException + { + Class c = loadClassOrNull(cn, resolve); + if (c == null) + throw new ClassNotFoundException(cn); + return c; + } + + /** + * A variation of {@code loadClass} to load a class with the specified + * binary name. This method returns {@code null} when the class is not + * found. + */ + protected Class loadClassOrNull(String cn, boolean resolve) { + synchronized (getClassLoadingLock(cn)) { + // check if already loaded + Class c = findLoadedClass(cn); + + if (c == null) { + + // find the candidate module for this class + LoadedModule loadedModule = findLoadedModule(cn); + if (loadedModule != null) { + + // package is in a module + BuiltinClassLoader loader = loadedModule.loader(); + if (loader == this) { + if (VM.isModuleSystemInited()) { + c = findClassInModuleOrNull(loadedModule, cn); + } + } else { + // delegate to the other loader + c = loader.loadClassOrNull(cn); + } + + } else { + + // check parent + if (parent != null) { + c = parent.loadClassOrNull(cn); + } + + // check class path + if (c == null && hasClassPath() && VM.isModuleSystemInited()) { + c = findClassOnClassPathOrNull(cn); + } + } + + } + + if (resolve && c != null) + resolveClass(c); + + return c; + } + } + + /** + * A variation of {@code loadClass} to load a class with the specified + * binary name. This method returns {@code null} when the class is not + * found. + */ + protected final Class loadClassOrNull(String cn) { + return loadClassOrNull(cn, false); + } + + /** + * Finds the candidate loaded module for the given class name. + * Returns {@code null} if none of the modules defined to this + * class loader contain the API package for the class. + */ + private LoadedModule findLoadedModule(String cn) { + int pos = cn.lastIndexOf('.'); + if (pos < 0) + return null; // unnamed package + + String pn = cn.substring(0, pos); + return packageToModule.get(pn); + } + + /** + * Finds the candidate loaded module for the given class name + * in the named module. Returns {@code null} if the named module + * is not defined to this class loader or does not contain + * the API package for the class. + */ + private LoadedModule findLoadedModule(String mn, String cn) { + LoadedModule loadedModule = findLoadedModule(cn); + if (loadedModule != null && mn.equals(loadedModule.name())) { + return loadedModule; + } else { + return null; + } + } + + /** + * Finds the class with the specified binary name if in a module + * defined to this ClassLoader. + * + * @return the resulting Class or {@code null} if not found + */ + @SuppressWarnings("removal") + private Class findClassInModuleOrNull(LoadedModule loadedModule, String cn) { + if (System.getSecurityManager() == null) { + return defineClass(cn, loadedModule); + } else { + PrivilegedAction> pa = () -> defineClass(cn, loadedModule); + return AccessController.doPrivileged(pa); + } + } + + /** + * Finds the class with the specified binary name on the class path. + * + * @return the resulting Class or {@code null} if not found + */ + @SuppressWarnings("removal") + private Class findClassOnClassPathOrNull(String cn) { + String path = cn.replace('.', '/').concat(".class"); + if (System.getSecurityManager() == null) { + Resource res = ucp.getResource(path, false); + if (res != null) { + try { + return defineClass(cn, res); + } catch (IOException ioe) { + // TBD on how I/O errors should be propagated + } + } + return null; + } else { + // avoid use of lambda here + PrivilegedAction> pa = new PrivilegedAction<>() { + public Class run() { + Resource res = ucp.getResource(path, false); + if (res != null) { + try { + return defineClass(cn, res); + } catch (IOException ioe) { + // TBD on how I/O errors should be propagated + } + } + return null; + } + }; + return AccessController.doPrivileged(pa); + } + } + + /** + * Defines the given binary class name to the VM, loading the class + * bytes from the given module. + * + * @return the resulting Class or {@code null} if an I/O error occurs + */ + private Class defineClass(String cn, LoadedModule loadedModule) { + ModuleReference mref = loadedModule.mref(); + ModuleReader reader = moduleReaderFor(mref); + + try { + ByteBuffer bb = null; + URL csURL = null; + + // locate class file, special handling for patched modules to + // avoid locating the resource twice + String rn = cn.replace('.', '/').concat(".class"); + if (reader instanceof PatchedModuleReader) { + Resource r = ((PatchedModuleReader)reader).findResource(rn); + if (r != null) { + bb = r.getByteBuffer(); + csURL = r.getCodeSourceURL(); + } + } else { + bb = reader.read(rn).orElse(null); + csURL = loadedModule.codeSourceURL(); + } + + if (bb == null) { + // class not found + return null; + } + + CodeSource cs = new CodeSource(csURL, (CodeSigner[]) null); + try { + // define class to VM + return defineClass(cn, bb, cs); + + } finally { + reader.release(bb); + } + + } catch (IOException ioe) { + // TBD on how I/O errors should be propagated + return null; + } + } + + /** + * Defines the given binary class name to the VM, loading the class + * bytes via the given Resource object. + * + * @return the resulting Class + * @throws IOException if reading the resource fails + * @throws SecurityException if there is a sealing violation (JAR spec) + */ + private Class defineClass(String cn, Resource res) throws IOException { + URL url = res.getCodeSourceURL(); + + // if class is in a named package then ensure that the package is defined + int pos = cn.lastIndexOf('.'); + if (pos != -1) { + String pn = cn.substring(0, pos); + Manifest man = res.getManifest(); + defineOrCheckPackage(pn, man, url); + } + + // defines the class to the runtime + ByteBuffer bb = res.getByteBuffer(); + if (bb != null) { + CodeSigner[] signers = res.getCodeSigners(); + CodeSource cs = new CodeSource(url, signers); + return defineClass(cn, bb, cs); + } else { + byte[] b = res.getBytes(); + CodeSigner[] signers = res.getCodeSigners(); + CodeSource cs = new CodeSource(url, signers); + return defineClass(cn, b, 0, b.length, cs); + } + } + + + // -- packages + + /** + * Defines a package in this ClassLoader. If the package is already defined + * then its sealing needs to be checked if sealed by the legacy sealing + * mechanism. + * + * @throws SecurityException if there is a sealing violation (JAR spec) + */ + protected Package defineOrCheckPackage(String pn, Manifest man, URL url) { + Package pkg = getAndVerifyPackage(pn, man, url); + if (pkg == null) { + try { + if (man != null) { + pkg = definePackage(pn, man, url); + } else { + pkg = definePackage(pn, null, null, null, null, null, null, null); + } + } catch (IllegalArgumentException iae) { + // defined by another thread so need to re-verify + pkg = getAndVerifyPackage(pn, man, url); + if (pkg == null) + throw new InternalError("Cannot find package: " + pn); + } + } + return pkg; + } + + /** + * Gets the Package with the specified package name. If defined + * then verifies it against the manifest and code source. + * + * @throws SecurityException if there is a sealing violation (JAR spec) + */ + private Package getAndVerifyPackage(String pn, Manifest man, URL url) { + Package pkg = getDefinedPackage(pn); + if (pkg != null) { + if (pkg.isSealed()) { + if (!pkg.isSealed(url)) { + throw new SecurityException( + "sealing violation: package " + pn + " is sealed"); + } + } else { + // can't seal package if already defined without sealing + if ((man != null) && isSealed(pn, man)) { + throw new SecurityException( + "sealing violation: can't seal package " + pn + + ": already defined"); + } + } + } + return pkg; + } + + /** + * Defines a new package in this ClassLoader. The attributes in the specified + * Manifest are used to get the package version and sealing information. + * + * @throws IllegalArgumentException if the package name duplicates an + * existing package either in this class loader or one of its ancestors + * @throws SecurityException if the package name is untrusted in the manifest + */ + private Package definePackage(String pn, Manifest man, URL url) { + String specTitle = null; + String specVersion = null; + String specVendor = null; + String implTitle = null; + String implVersion = null; + String implVendor = null; + String sealed = null; + URL sealBase = null; + + if (man != null) { + Attributes attr = SharedSecrets.javaUtilJarAccess() + .getTrustedAttributes(man, pn.replace('.', '/').concat("/")); + if (attr != null) { + specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE); + specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION); + specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR); + implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE); + implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); + sealed = attr.getValue(Attributes.Name.SEALED); + } + + attr = man.getMainAttributes(); + if (attr != null) { + if (specTitle == null) + specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE); + if (specVersion == null) + specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION); + if (specVendor == null) + specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR); + if (implTitle == null) + implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE); + if (implVersion == null) + implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + if (implVendor == null) + implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); + if (sealed == null) + sealed = attr.getValue(Attributes.Name.SEALED); + } + + // package is sealed + if ("true".equalsIgnoreCase(sealed)) + sealBase = url; + } + return definePackage(pn, + specTitle, + specVersion, + specVendor, + implTitle, + implVersion, + implVendor, + sealBase); + } + + /** + * Returns {@code true} if the specified package name is sealed according to + * the given manifest. + * + * @throws SecurityException if the package name is untrusted in the manifest + */ + private boolean isSealed(String pn, Manifest man) { + Attributes attr = SharedSecrets.javaUtilJarAccess() + .getTrustedAttributes(man, pn.replace('.', '/').concat("/")); + String sealed = null; + if (attr != null) + sealed = attr.getValue(Attributes.Name.SEALED); + if (sealed == null && (attr = man.getMainAttributes()) != null) + sealed = attr.getValue(Attributes.Name.SEALED); + return "true".equalsIgnoreCase(sealed); + } + + // -- permissions + + /** + * Returns the permissions for the given CodeSource. + */ + @Override + protected PermissionCollection getPermissions(CodeSource cs) { + return new LazyCodeSourcePermissionCollection(super.getPermissions(cs), cs); + } + + // -- miscellaneous supporting methods + + /** + * Returns the ModuleReader for the given module, creating it if needed. + */ + private ModuleReader moduleReaderFor(ModuleReference mref) { + ModuleReader reader = moduleToReader.get(mref); + if (reader == null) { + // avoid method reference during startup + Function create = new Function<>() { + public ModuleReader apply(ModuleReference moduleReference) { + try { + return mref.open(); + } catch (IOException e) { + // Return a null module reader to avoid a future class + // load attempting to open the module again. + return new NullModuleReader(); + } + } + }; + reader = moduleToReader.computeIfAbsent(mref, create); + } + return reader; + } + + /** + * A ModuleReader that doesn't read any resources. + */ + private static class NullModuleReader implements ModuleReader { + @Override + public Optional find(String name) { + return Optional.empty(); + } + @Override + public Stream list() { + return Stream.empty(); + } + @Override + public void close() { + throw new InternalError("Should not get here"); + } + }; + + /** + * Returns true if the given module opens the given package + * unconditionally. + * + * @implNote This method currently iterates over each of the open + * packages. This will be replaced once the ModuleDescriptor.Opens + * API is updated. + */ + private boolean isOpen(ModuleReference mref, String pn) { + ModuleDescriptor descriptor = mref.descriptor(); + if (descriptor.isOpen() || descriptor.isAutomatic()) + return true; + for (ModuleDescriptor.Opens opens : descriptor.opens()) { + String source = opens.source(); + if (!opens.isQualified() && source.equals(pn)) { + return true; + } + } + return false; + } + + /** + * Checks access to the given URL. We use URLClassPath for consistent + * checking with java.net.URLClassLoader. + */ + private static URL checkURL(URL url) { + return URLClassPath.checkURL(url); + } + + // Called from VM only, during -Xshare:dump + private void resetArchivedStates() { + ucp = null; + resourceCache = null; + } +} diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaderHelper.class b/tests/test_data/std/jdk/internal/loader/ClassLoaderHelper.class new file mode 100644 index 00000000..8522e56e Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ClassLoaderHelper.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaderHelper.java b/tests/test_data/std/jdk/internal/loader/ClassLoaderHelper.java new file mode 100644 index 00000000..a780146f --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/ClassLoaderHelper.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.io.File; +import java.util.ArrayList; + +import jdk.internal.util.OSVersion; + +class ClassLoaderHelper { + + // SDK 10.15 and earlier always reports 10.16 instead of 11.x.x + private static final boolean hasDynamicLoaderCache = OSVersion.current() + .compareTo(new OSVersion(10, 16)) >= 0; + + private ClassLoaderHelper() {} + + /** + * Returns true if loading a native library only if + * it's present on the file system. + * + * @implNote + * On macOS 11.x or later which supports dynamic linker cache, + * the dynamic library is not present on the filesystem. The + * library cannot determine if a dynamic library exists on a + * given path or not and so this method returns false. + */ + static boolean loadLibraryOnlyIfPresent() { + return !hasDynamicLoaderCache; + } + + /** + * Returns an alternate path name for the given file + * such that if the original pathname did not exist, then the + * file may be located at the alternate location. + * For mac, this replaces the final .dylib suffix with .jnilib + */ + static File mapAlternativeName(File lib) { + String name = lib.toString(); + int index = name.lastIndexOf('.'); + if (index < 0) { + return null; + } + return new File(name.substring(0, index) + ".jnilib"); + } + + /** + * Parse a PATH env variable. + * + * Empty elements will be replaced by dot. + */ + static String[] parsePath(String ldPath) { + char ps = File.pathSeparatorChar; + ArrayList paths = new ArrayList<>(); + int pathStart = 0; + int pathEnd; + while ((pathEnd = ldPath.indexOf(ps, pathStart)) >= 0) { + paths.add((pathStart < pathEnd) ? + ldPath.substring(pathStart, pathEnd) : "."); + pathStart = pathEnd + 1; + } + int ldLen = ldPath.length(); + paths.add((pathStart < ldLen) ? + ldPath.substring(pathStart, ldLen) : "."); + return paths.toArray(new String[paths.size()]); + } +} diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaderValue.class b/tests/test_data/std/jdk/internal/loader/ClassLoaderValue.class new file mode 100644 index 00000000..4d566fa0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ClassLoaderValue.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaderValue.java b/tests/test_data/std/jdk/internal/loader/ClassLoaderValue.java new file mode 100644 index 00000000..f4b2d2a3 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/ClassLoaderValue.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.util.Objects; +import java.util.function.BiFunction; + +/** + * root-ClassLoaderValue. Each instance defines a separate namespace for + * associated values. + *

+ * ClassLoaderValue allows associating a + * {@link #computeIfAbsent(ClassLoader, BiFunction) computed} non-null value with + * a {@code (ClassLoader, keys...)} tuple. The associated value, as well as the + * keys are strongly reachable from the associated ClassLoader so care should be + * taken to use such keys and values that only reference types resolvable from + * the associated ClassLoader. Failing that, ClassLoader leaks are inevitable. + *

+ * Example usage: + *

{@code
+ * // create a root instance which represents a namespace and declares the type of
+ * // associated values (Class instances in this example)
+ * static final ClassLoaderValue> proxyClasses = new ClassLoaderValue<>();
+ *
+ * // create a compound key composed of a Module and a list of interfaces
+ * Module module = ...;
+ * List> interfaces = ...;
+ * ClassLoaderValue>.Sub.Sub>> key =
+ *     proxyClasses.sub(module).sub(interfaces);
+ *
+ * // use the compound key together with ClassLoader to lazily associate
+ * // the value with tuple (loader, module, interfaces) and return it
+ * ClassLoader loader = ...;
+ * Class proxyClass = key.computeIfAbsent(loader, (ld, ky) -> {
+ *     List> intfcs = ky.key();
+ *     Module m = ky.parent().key();
+ *     Class clazz = defineProxyClass(ld, m, intfcs);
+ *     return clazz;
+ * });
+ * }
+ *

+ * {@code classLoaderValue.(classLoader, ...)} represents an operation + * to {@link #get}, {@link #putIfAbsent}, {@link #computeIfAbsent} or {@link #remove} + * a value associated with a (classLoader, classLoaderValue) tuple. ClassLoader + * instances and root-{@link ClassLoaderValue} instances are compared using + * identity equality while {@link Sub sub}-ClassLoaderValue instances define + * {@link #equals(Object) equality} in terms of equality of its + * {@link Sub#parent() parent} ClassLoaderValue and its + * {@link #key() key} component. + * + * @param the type of value(s) associated with the root-ClassLoaderValue and + * all its {@link #sub(Object) descendants}. + * @author Peter Levart + * @since 9 + */ +public final class ClassLoaderValue + extends AbstractClassLoaderValue, V> { + + /** + * Constructs new root-ClassLoaderValue representing its own namespace. + */ + public ClassLoaderValue() {} + + /** + * @return the key component of this root-ClassLoaderValue (itself). + */ + @Override + public ClassLoaderValue key() { + return this; + } + + /** + * root-ClassLoaderValue can only be equal to itself and has no predecessors. + */ + @Override + public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { + return equals(Objects.requireNonNull(clv)); + } +} diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaders$AppClassLoader.class b/tests/test_data/std/jdk/internal/loader/ClassLoaders$AppClassLoader.class new file mode 100644 index 00000000..aaa0c04d Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ClassLoaders$AppClassLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaders$BootClassLoader.class b/tests/test_data/std/jdk/internal/loader/ClassLoaders$BootClassLoader.class new file mode 100644 index 00000000..93b6ae95 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ClassLoaders$BootClassLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaders$PlatformClassLoader.class b/tests/test_data/std/jdk/internal/loader/ClassLoaders$PlatformClassLoader.class new file mode 100644 index 00000000..5a5fc330 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ClassLoaders$PlatformClassLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaders.class b/tests/test_data/std/jdk/internal/loader/ClassLoaders.class new file mode 100644 index 00000000..44925f85 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/ClassLoaders.class differ diff --git a/tests/test_data/std/jdk/internal/loader/ClassLoaders.java b/tests/test_data/std/jdk/internal/loader/ClassLoaders.java new file mode 100644 index 00000000..7b0f4d13 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/ClassLoaders.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.util.jar.Manifest; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.VM; +import jdk.internal.module.ServicesCatalog; + +/** + * Creates and provides access to the built-in platform and application class + * loaders. It also creates the class loader that is used to locate resources + * in modules defined to the boot class loader. + */ + +public class ClassLoaders { + + private ClassLoaders() { } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + // the built-in class loaders + private static final BootClassLoader BOOT_LOADER; + private static final PlatformClassLoader PLATFORM_LOADER; + private static final AppClassLoader APP_LOADER; + + // Sets the ServicesCatalog for the specified loader using archived objects. + private static void setArchivedServicesCatalog(ClassLoader loader) { + ServicesCatalog catalog = ArchivedClassLoaders.get().servicesCatalog(loader); + ServicesCatalog.putServicesCatalog(loader, catalog); + } + + // Creates the built-in class loaders. + static { + ArchivedClassLoaders archivedClassLoaders = ArchivedClassLoaders.get(); + // -Xbootclasspath/a or -javaagent with Boot-Class-Path attribute + String append = VM.getSavedProperty("jdk.boot.class.path.append"); + URLClassPath bootUcp = (append != null && !append.isEmpty()) + ? new URLClassPath(append, true) + : null; + if (archivedClassLoaders != null) { + BOOT_LOADER = (BootClassLoader) archivedClassLoaders.bootLoader(); + BOOT_LOADER.setClassPath(bootUcp); + setArchivedServicesCatalog(BOOT_LOADER); + PLATFORM_LOADER = (PlatformClassLoader) archivedClassLoaders.platformLoader(); + setArchivedServicesCatalog(PLATFORM_LOADER); + } else { + BOOT_LOADER = new BootClassLoader(bootUcp); + PLATFORM_LOADER = new PlatformClassLoader(BOOT_LOADER); + } + // A class path is required when no initial module is specified. + // In this case the class path defaults to "", meaning the current + // working directory. When an initial module is specified, on the + // contrary, we drop this historic interpretation of the empty + // string and instead treat it as unspecified. + String cp = System.getProperty("java.class.path"); + if (cp == null || cp.isEmpty()) { + String initialModuleName = System.getProperty("jdk.module.main"); + cp = (initialModuleName == null) ? "" : null; + } + URLClassPath ucp = new URLClassPath(cp, false); + if (archivedClassLoaders != null) { + APP_LOADER = (AppClassLoader) archivedClassLoaders.appLoader(); + setArchivedServicesCatalog(APP_LOADER); + APP_LOADER.setClassPath(ucp); + } else { + APP_LOADER = new AppClassLoader(PLATFORM_LOADER, ucp); + ArchivedClassLoaders.archive(); + } + } + + /** + * Returns the class loader that is used to find resources in modules + * defined to the boot class loader. + * + * @apiNote This method is not public, it should instead be used via + * the BootLoader class that provides a restricted API to this class + * loader. + */ + static BuiltinClassLoader bootLoader() { + return BOOT_LOADER; + } + + /** + * Returns the platform class loader. + */ + public static ClassLoader platformClassLoader() { + return PLATFORM_LOADER; + } + + /** + * Returns the application class loader. + */ + public static ClassLoader appClassLoader() { + return APP_LOADER; + } + + /** + * The class loader that is used to find resources in modules defined to + * the boot class loader. It is not used for class loading. + */ + private static class BootClassLoader extends BuiltinClassLoader { + BootClassLoader(URLClassPath bcp) { + super(null, null, bcp); + } + + @Override + protected Class loadClassOrNull(String cn, boolean resolve) { + return JLA.findBootstrapClassOrNull(cn); + } + }; + + /** + * The platform class loader, a unique type to make it easier to distinguish + * from the application class loader. + */ + private static class PlatformClassLoader extends BuiltinClassLoader { + static { + if (!ClassLoader.registerAsParallelCapable()) + throw new InternalError(); + } + + PlatformClassLoader(BootClassLoader parent) { + super("platform", parent, null); + } + } + + /** + * The application class loader that is a {@code BuiltinClassLoader} with + * customizations to be compatible with long standing behavior. + */ + private static class AppClassLoader extends BuiltinClassLoader { + static { + if (!ClassLoader.registerAsParallelCapable()) + throw new InternalError(); + } + + AppClassLoader(BuiltinClassLoader parent, URLClassPath ucp) { + super("app", parent, ucp); + } + + @Override + protected Class loadClass(String cn, boolean resolve) + throws ClassNotFoundException + { + // for compatibility reasons, say where restricted package list has + // been updated to list API packages in the unnamed module. + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + int i = cn.lastIndexOf('.'); + if (i != -1) { + sm.checkPackageAccess(cn.substring(0, i)); + } + } + + return super.loadClass(cn, resolve); + } + + @Override + protected PermissionCollection getPermissions(CodeSource cs) { + PermissionCollection perms = super.getPermissions(cs); + perms.add(new RuntimePermission("exitVM")); + return perms; + } + + /** + * Called by the VM to support dynamic additions to the class path + * + * @see java.lang.instrument.Instrumentation#appendToSystemClassLoaderSearch + */ + void appendToClassPathForInstrumentation(String path) { + appendClassPath(path); + } + + /** + * Called by the VM to support define package for AppCDS + */ + protected Package defineOrCheckPackage(String pn, Manifest man, URL url) { + return super.defineOrCheckPackage(pn, man, url); + } + + /** + * Called by the VM, during -Xshare:dump + */ + private void resetArchivedStates() { + setClassPath(null); + } + } + + /** + * Attempts to convert the given string to a file URL. + * + * @apiNote This is called by the VM + */ + @Deprecated + private static URL toFileURL(String s) { + try { + // Use an intermediate File object to construct a URI/URL without + // authority component as URLClassPath can't handle URLs with a UNC + // server name in the authority component. + return Path.of(s).toRealPath().toFile().toURI().toURL(); + } catch (InvalidPathException | IOException ignore) { + // malformed path string or class path element does not exist + return null; + } + } +} diff --git a/tests/test_data/std/jdk/internal/loader/FileURLMapper.class b/tests/test_data/std/jdk/internal/loader/FileURLMapper.class new file mode 100644 index 00000000..7610faf6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/FileURLMapper.class differ diff --git a/tests/test_data/std/jdk/internal/loader/FileURLMapper.java b/tests/test_data/std/jdk/internal/loader/FileURLMapper.java new file mode 100644 index 00000000..6507b296 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/FileURLMapper.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.net.URL; +import java.io.File; +import sun.net.www.ParseUtil; + +/** + * (Solaris) platform specific handling for file: URLs . + * urls must not contain a hostname in the authority field + * other than "localhost". + * + * This implementation could be updated to map such URLs + * on to /net/host/... + * + * @author Michael McMahon + */ + +public class FileURLMapper { + + URL url; + String path; + + public FileURLMapper (URL url) { + this.url = url; + } + + /** + * @return the platform specific path corresponding to the URL + * so long as the URL does not contain a hostname in the authority field. + */ + + public String getPath () { + if (path != null) { + return path; + } + String host = url.getHost(); + if (host == null || host.isEmpty() || "localhost".equalsIgnoreCase(host)) { + path = url.getFile(); + path = ParseUtil.decode(path); + } + return path; + } + + /** + * Checks whether the file identified by the URL exists. + */ + public boolean exists () { + String s = getPath (); + if (s == null) { + return false; + } else { + File f = new File (s); + return f.exists(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/loader/Loader$1.class b/tests/test_data/std/jdk/internal/loader/Loader$1.class new file mode 100644 index 00000000..f5423bf9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Loader$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Loader$2.class b/tests/test_data/std/jdk/internal/loader/Loader$2.class new file mode 100644 index 00000000..d7d41883 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Loader$2.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Loader$3.class b/tests/test_data/std/jdk/internal/loader/Loader$3.class new file mode 100644 index 00000000..73750782 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Loader$3.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Loader$LoadedModule.class b/tests/test_data/std/jdk/internal/loader/Loader$LoadedModule.class new file mode 100644 index 00000000..6687059b Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Loader$LoadedModule.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Loader$NullModuleReader.class b/tests/test_data/std/jdk/internal/loader/Loader$NullModuleReader.class new file mode 100644 index 00000000..07d3ba90 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Loader$NullModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Loader.class b/tests/test_data/std/jdk/internal/loader/Loader.class new file mode 100644 index 00000000..d2ab8aee Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Loader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Loader.java b/tests/test_data/std/jdk/internal/loader/Loader.java new file mode 100644 index 00000000..fbd16467 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/Loader.java @@ -0,0 +1,736 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.io.File; +import java.io.FilePermission; +import java.io.IOException; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.ByteBuffer; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.CodeSigner; +import java.security.CodeSource; +import java.security.Permission; +import java.security.PermissionCollection; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.SecureClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.module.Resources; + +/** + * A class loader that loads classes and resources from a collection of + * modules, or from a single module where the class loader is a member + * of a pool of class loaders. + * + *

The delegation model used by this ClassLoader differs to the regular + * delegation model. When requested to load a class then this ClassLoader first + * maps the class name to its package name. If there a module defined to the + * Loader containing the package then the class loader attempts to load from + * that module. If the package is instead defined to a module in a "remote" + * ClassLoader then this class loader delegates directly to that class loader. + * The map of package name to remote class loader is created based on the + * modules read by modules defined to this class loader. If the package is not + * local or remote then this class loader will delegate to the parent class + * loader. This allows automatic modules (for example) to link to types in the + * unnamed module of the parent class loader. + * + * @see ModuleLayer#defineModulesWithOneLoader + * @see ModuleLayer#defineModulesWithManyLoaders + */ + +public final class Loader extends SecureClassLoader { + + static { + ClassLoader.registerAsParallelCapable(); + } + + // the pool this loader is a member of; can be null + private final LoaderPool pool; + + // parent ClassLoader, can be null + private final ClassLoader parent; + + // maps a module name to a module reference + private final Map nameToModule; + + // maps package name to a module loaded by this class loader + private final Map localPackageToModule; + + // maps package name to a remote class loader, populated post initialization + private final Map remotePackageToLoader + = new ConcurrentHashMap<>(); + + // maps a module reference to a module reader, populated lazily + private final Map moduleToReader + = new ConcurrentHashMap<>(); + + // ACC used when loading classes and resources + @SuppressWarnings("removal") + private final AccessControlContext acc; + + /** + * A module defined/loaded to a {@code Loader}. + */ + private static class LoadedModule { + private final ModuleReference mref; + private final URL url; // may be null + private final CodeSource cs; + + LoadedModule(ModuleReference mref) { + URL url = null; + if (mref.location().isPresent()) { + try { + url = mref.location().get().toURL(); + } catch (MalformedURLException | IllegalArgumentException e) { } + } + this.mref = mref; + this.url = url; + this.cs = new CodeSource(url, (CodeSigner[]) null); + } + + ModuleReference mref() { return mref; } + String name() { return mref.descriptor().name(); } + URL location() { return url; } + CodeSource codeSource() { return cs; } + } + + + /** + * Creates a {@code Loader} in a loader pool that loads classes/resources + * from one module. + */ + @SuppressWarnings("removal") + public Loader(ResolvedModule resolvedModule, + LoaderPool pool, + ClassLoader parent) + { + super("Loader-" + resolvedModule.name(), parent); + + this.pool = pool; + this.parent = parent; + + ModuleReference mref = resolvedModule.reference(); + ModuleDescriptor descriptor = mref.descriptor(); + String mn = descriptor.name(); + this.nameToModule = Map.of(mn, mref); + + Map localPackageToModule = new HashMap<>(); + LoadedModule lm = new LoadedModule(mref); + descriptor.packages().forEach(pn -> localPackageToModule.put(pn, lm)); + this.localPackageToModule = localPackageToModule; + + this.acc = AccessController.getContext(); + } + + /** + * Creates a {@code Loader} that loads classes/resources from a collection + * of modules. + * + * @throws IllegalArgumentException + * If two or more modules have the same package + */ + @SuppressWarnings("removal") + public Loader(Collection modules, ClassLoader parent) { + super(parent); + + this.pool = null; + this.parent = parent; + + Map nameToModule = new HashMap<>(); + Map localPackageToModule = new HashMap<>(); + for (ResolvedModule resolvedModule : modules) { + ModuleReference mref = resolvedModule.reference(); + ModuleDescriptor descriptor = mref.descriptor(); + nameToModule.put(descriptor.name(), mref); + descriptor.packages().forEach(pn -> { + LoadedModule lm = new LoadedModule(mref); + if (localPackageToModule.put(pn, lm) != null) + throw new IllegalArgumentException("Package " + + pn + " in more than one module"); + }); + } + this.nameToModule = nameToModule; + this.localPackageToModule = localPackageToModule; + + this.acc = AccessController.getContext(); + } + + /** + * Completes initialization of this Loader. This method populates + * remotePackageToLoader with the packages of the remote modules, where + * "remote modules" are the modules read by modules defined to this loader. + * + * @param cf the Configuration containing at least modules to be defined to + * this class loader + * + * @param parentModuleLayers the parent ModuleLayers + */ + public Loader initRemotePackageMap(Configuration cf, + List parentModuleLayers) + { + for (String name : nameToModule.keySet()) { + ResolvedModule resolvedModule = cf.findModule(name).get(); + assert resolvedModule.configuration() == cf; + + for (ResolvedModule other : resolvedModule.reads()) { + String mn = other.name(); + ClassLoader loader; + + if (other.configuration() == cf) { + + // The module reads another module in the newly created + // layer. If all modules are defined to the same class + // loader then the packages are local. + if (pool == null) { + assert nameToModule.containsKey(mn); + continue; + } + + loader = pool.loaderFor(mn); + assert loader != null; + + } else { + + // find the layer for the target module + ModuleLayer layer = parentModuleLayers.stream() + .map(parent -> findModuleLayer(parent, other.configuration())) + .flatMap(Optional::stream) + .findAny() + .orElseThrow(() -> + new InternalError("Unable to find parent layer")); + + // find the class loader for the module + // For now we use the platform loader for modules defined to the + // boot loader + assert layer.findModule(mn).isPresent(); + loader = layer.findLoader(mn); + if (loader == null) + loader = ClassLoaders.platformClassLoader(); + } + + // find the packages that are exported to the target module + ModuleDescriptor descriptor = other.reference().descriptor(); + if (descriptor.isAutomatic()) { + ClassLoader l = loader; + descriptor.packages().forEach(pn -> remotePackage(pn, l)); + } else { + String target = resolvedModule.name(); + for (ModuleDescriptor.Exports e : descriptor.exports()) { + boolean delegate; + if (e.isQualified()) { + // qualified export in same configuration + delegate = (other.configuration() == cf) + && e.targets().contains(target); + } else { + // unqualified + delegate = true; + } + + if (delegate) { + remotePackage(e.source(), loader); + } + } + } + } + + } + + return this; + } + + /** + * Adds to remotePackageToLoader so that an attempt to load a class in + * the package delegates to the given class loader. + * + * @throws IllegalStateException + * if the package is already mapped to a different class loader + */ + private void remotePackage(String pn, ClassLoader loader) { + ClassLoader l = remotePackageToLoader.putIfAbsent(pn, loader); + if (l != null && l != loader) { + throw new IllegalStateException("Package " + + pn + " cannot be imported from multiple loaders"); + } + } + + + /** + * Find the layer corresponding to the given configuration in the tree + * of layers rooted at the given parent. + */ + private Optional findModuleLayer(ModuleLayer parent, Configuration cf) { + return SharedSecrets.getJavaLangAccess().layers(parent) + .filter(l -> l.configuration() == cf) + .findAny(); + } + + + /** + * Returns the loader pool that this loader is in or {@code null} if this + * loader is not in a loader pool. + */ + public LoaderPool pool() { + return pool; + } + + + // -- resources -- + + /** + * Returns a URL to a resource of the given name in a module defined to + * this class loader. + */ + @SuppressWarnings("removal") + @Override + protected URL findResource(String mn, String name) throws IOException { + ModuleReference mref = (mn != null) ? nameToModule.get(mn) : null; + if (mref == null) + return null; // not defined to this class loader + + // locate resource + URL url = null; + try { + url = AccessController.doPrivileged( + new PrivilegedExceptionAction() { + @Override + public URL run() throws IOException { + Optional ouri = moduleReaderFor(mref).find(name); + if (ouri.isPresent()) { + try { + return ouri.get().toURL(); + } catch (MalformedURLException | + IllegalArgumentException e) { } + } + return null; + } + }); + } catch (PrivilegedActionException pae) { + throw (IOException) pae.getCause(); + } + + // check access with permissions restricted by ACC + if (url != null && System.getSecurityManager() != null) { + try { + URL urlToCheck = url; + url = AccessController.doPrivileged( + new PrivilegedExceptionAction() { + @Override + public URL run() throws IOException { + return URLClassPath.checkURL(urlToCheck); + } + }, acc); + } catch (PrivilegedActionException pae) { + url = null; + } + } + + return url; + } + + @Override + public URL findResource(String name) { + String pn = Resources.toPackageName(name); + LoadedModule module = localPackageToModule.get(pn); + + if (module != null) { + try { + URL url = findResource(module.name(), name); + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + return url; + } + } catch (IOException ioe) { + // ignore + } + + } else { + for (ModuleReference mref : nameToModule.values()) { + try { + URL url = findResource(mref.descriptor().name(), name); + if (url != null) return url; + } catch (IOException ioe) { + // ignore + } + } + } + + return null; + } + + @Override + public Enumeration findResources(String name) throws IOException { + return Collections.enumeration(findResourcesAsList(name)); + } + + @Override + public URL getResource(String name) { + Objects.requireNonNull(name); + + // this loader + URL url = findResource(name); + if (url == null) { + // parent loader + if (parent != null) { + url = parent.getResource(name); + } else { + url = BootLoader.findResource(name); + } + } + return url; + } + + @Override + public Enumeration getResources(String name) throws IOException { + Objects.requireNonNull(name); + + // this loader + List urls = findResourcesAsList(name); + + // parent loader + Enumeration e; + if (parent != null) { + e = parent.getResources(name); + } else { + e = BootLoader.findResources(name); + } + + // concat the URLs with the URLs returned by the parent + return new Enumeration<>() { + final Iterator iterator = urls.iterator(); + @Override + public boolean hasMoreElements() { + return (iterator.hasNext() || e.hasMoreElements()); + } + @Override + public URL nextElement() { + if (iterator.hasNext()) { + return iterator.next(); + } else { + return e.nextElement(); + } + } + }; + } + + /** + * Finds the resources with the given name in this class loader. + */ + private List findResourcesAsList(String name) throws IOException { + String pn = Resources.toPackageName(name); + LoadedModule module = localPackageToModule.get(pn); + if (module != null) { + URL url = findResource(module.name(), name); + if (url != null + && (name.endsWith(".class") + || url.toString().endsWith("/") + || isOpen(module.mref(), pn))) { + return List.of(url); + } else { + return Collections.emptyList(); + } + } else { + List urls = new ArrayList<>(); + for (ModuleReference mref : nameToModule.values()) { + URL url = findResource(mref.descriptor().name(), name); + if (url != null) { + urls.add(url); + } + } + return urls; + } + } + + + // -- finding/loading classes + + /** + * Finds the class with the specified binary name. + */ + @Override + protected Class findClass(String cn) throws ClassNotFoundException { + Class c = null; + LoadedModule loadedModule = findLoadedModule(cn); + if (loadedModule != null) + c = findClassInModuleOrNull(loadedModule, cn); + if (c == null) + throw new ClassNotFoundException(cn); + return c; + } + + /** + * Finds the class with the specified binary name in the given module. + * This method returns {@code null} if the class cannot be found. + */ + @Override + protected Class findClass(String mn, String cn) { + Class c = null; + LoadedModule loadedModule = findLoadedModule(cn); + if (loadedModule != null && loadedModule.name().equals(mn)) + c = findClassInModuleOrNull(loadedModule, cn); + return c; + } + + /** + * Loads the class with the specified binary name. + */ + @Override + protected Class loadClass(String cn, boolean resolve) + throws ClassNotFoundException + { + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + String pn = packageName(cn); + if (!pn.isEmpty()) { + sm.checkPackageAccess(pn); + } + } + + synchronized (getClassLoadingLock(cn)) { + // check if already loaded + Class c = findLoadedClass(cn); + + if (c == null) { + + LoadedModule loadedModule = findLoadedModule(cn); + + if (loadedModule != null) { + + // class is in module defined to this class loader + c = findClassInModuleOrNull(loadedModule, cn); + + } else { + + // type in another module or visible via the parent loader + String pn = packageName(cn); + ClassLoader loader = remotePackageToLoader.get(pn); + if (loader == null) { + // type not in a module read by any of the modules + // defined to this loader, so delegate to parent + // class loader + loader = parent; + } + if (loader == null) { + c = BootLoader.loadClassOrNull(cn); + } else { + c = loader.loadClass(cn); + } + + } + } + + if (c == null) + throw new ClassNotFoundException(cn); + + if (resolve) + resolveClass(c); + + return c; + } + } + + + /** + * Finds the class with the specified binary name if in a module + * defined to this ClassLoader. + * + * @return the resulting Class or {@code null} if not found + */ + @SuppressWarnings("removal") + private Class findClassInModuleOrNull(LoadedModule loadedModule, String cn) { + PrivilegedAction> pa = () -> defineClass(cn, loadedModule); + return AccessController.doPrivileged(pa, acc); + } + + /** + * Defines the given binary class name to the VM, loading the class + * bytes from the given module. + * + * @return the resulting Class or {@code null} if an I/O error occurs + */ + private Class defineClass(String cn, LoadedModule loadedModule) { + ModuleReader reader = moduleReaderFor(loadedModule.mref()); + + try { + // read class file + String rn = cn.replace('.', '/').concat(".class"); + ByteBuffer bb = reader.read(rn).orElse(null); + if (bb == null) { + // class not found + return null; + } + + try { + return defineClass(cn, bb, loadedModule.codeSource()); + } finally { + reader.release(bb); + } + + } catch (IOException ioe) { + // TBD on how I/O errors should be propagated + return null; + } + } + + + // -- permissions + + /** + * Returns the permissions for the given CodeSource. + */ + @Override + protected PermissionCollection getPermissions(CodeSource cs) { + PermissionCollection perms = super.getPermissions(cs); + + URL url = cs.getLocation(); + if (url == null) + return perms; + + // add the permission to access the resource + try { + Permission p = url.openConnection().getPermission(); + if (p != null) { + // for directories then need recursive access + if (p instanceof FilePermission) { + String path = p.getName(); + if (path.endsWith(File.separator)) { + path += "-"; + p = new FilePermission(path, "read"); + } + } + perms.add(p); + } + } catch (IOException ioe) { } + + return perms; + } + + + // -- miscellaneous supporting methods + + /** + * Find the candidate module for the given class name. + * Returns {@code null} if none of the modules defined to this + * class loader contain the API package for the class. + */ + private LoadedModule findLoadedModule(String cn) { + String pn = packageName(cn); + return pn.isEmpty() ? null : localPackageToModule.get(pn); + } + + /** + * Returns the package name for the given class name + */ + private String packageName(String cn) { + int pos = cn.lastIndexOf('.'); + return (pos < 0) ? "" : cn.substring(0, pos); + } + + + /** + * Returns the ModuleReader for the given module. + */ + private ModuleReader moduleReaderFor(ModuleReference mref) { + return moduleToReader.computeIfAbsent(mref, m -> createModuleReader(mref)); + } + + /** + * Creates a ModuleReader for the given module. + */ + private ModuleReader createModuleReader(ModuleReference mref) { + try { + return mref.open(); + } catch (IOException e) { + // Return a null module reader to avoid a future class load + // attempting to open the module again. + return new NullModuleReader(); + } + } + + /** + * A ModuleReader that doesn't read any resources. + */ + private static class NullModuleReader implements ModuleReader { + @Override + public Optional find(String name) { + return Optional.empty(); + } + @Override + public Stream list() { + return Stream.empty(); + } + @Override + public void close() { + throw new InternalError("Should not get here"); + } + } + + /** + * Returns true if the given module opens the given package + * unconditionally. + * + * @implNote This method currently iterates over each of the open + * packages. This will be replaced once the ModuleDescriptor.Opens + * API is updated. + */ + private boolean isOpen(ModuleReference mref, String pn) { + ModuleDescriptor descriptor = mref.descriptor(); + if (descriptor.isOpen() || descriptor.isAutomatic()) + return true; + for (ModuleDescriptor.Opens opens : descriptor.opens()) { + String source = opens.source(); + if (!opens.isQualified() && source.equals(pn)) { + return true; + } + } + return false; + } +} diff --git a/tests/test_data/std/jdk/internal/loader/LoaderPool.class b/tests/test_data/std/jdk/internal/loader/LoaderPool.class new file mode 100644 index 00000000..64494efa Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/LoaderPool.class differ diff --git a/tests/test_data/std/jdk/internal/loader/LoaderPool.java b/tests/test_data/std/jdk/internal/loader/LoaderPool.java new file mode 100644 index 00000000..c0bdecf6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/LoaderPool.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.lang.module.Configuration; +import java.lang.module.ResolvedModule; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * A pool of class loaders. + * + * @see ModuleLayer#defineModulesWithManyLoaders + */ + +public final class LoaderPool { + + // maps module names to class loaders + private final Map loaders; + + + /** + * Creates a pool of class loaders. Each module in the given configuration + * is mapped to its own class loader in the pool. The class loader is + * created with the given parent class loader as its parent. + */ + public LoaderPool(Configuration cf, + List parentLayers, + ClassLoader parentLoader) + { + Map loaders = new HashMap<>(); + for (ResolvedModule resolvedModule : cf.modules()) { + Loader loader = new Loader(resolvedModule, this, parentLoader); + String mn = resolvedModule.name(); + loaders.put(mn, loader); + } + this.loaders = loaders; + + // complete the initialization + loaders.values().forEach(l -> l.initRemotePackageMap(cf, parentLayers)); + } + + + /** + * Returns the class loader for the named module + */ + public Loader loaderFor(String name) { + Loader loader = loaders.get(name); + assert loader != null; + return loader; + } + + /** + * Returns a stream of the loaders in this pool. + */ + public Stream loaders() { + return loaders.values().stream(); + } + +} diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$1.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$1.class new file mode 100644 index 00000000..00cba7d3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$2.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$2.class new file mode 100644 index 00000000..3b13d47a Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$2.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$3.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$3.class new file mode 100644 index 00000000..451c76b6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$3.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$CountedLock.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$CountedLock.class new file mode 100644 index 00000000..8a129853 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$CountedLock.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$LibraryPaths.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$LibraryPaths.class new file mode 100644 index 00000000..794c7fbd Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$LibraryPaths.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryContext$1.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryContext$1.class new file mode 100644 index 00000000..1bf7978d Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryContext$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryContext.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryContext.class new file mode 100644 index 00000000..a5e22727 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryContext.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryImpl$1.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryImpl$1.class new file mode 100644 index 00000000..ead3ced0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryImpl$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryImpl.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryImpl.class new file mode 100644 index 00000000..65530fc4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$NativeLibraryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries$Unloader.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries$Unloader.class new file mode 100644 index 00000000..648efdff Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries$Unloader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries.class b/tests/test_data/std/jdk/internal/loader/NativeLibraries.class new file mode 100644 index 00000000..4b1d5d48 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibraries.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibraries.java b/tests/test_data/std/jdk/internal/loader/NativeLibraries.java new file mode 100644 index 00000000..64a3813a --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/NativeLibraries.java @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.loader; + +import jdk.internal.misc.VM; +import jdk.internal.ref.CleanerFactory; +import jdk.internal.util.StaticProperty; + +import java.io.File; +import java.io.IOException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Native libraries are loaded via {@link System#loadLibrary(String)}, + * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and + * {@link Runtime#load(String)}. They are caller-sensitive. + * + * Each class loader has a NativeLibraries instance to register all of its + * loaded native libraries. System::loadLibrary (and other APIs) only + * allows a native library to be loaded by one class loader, i.e. one + * NativeLibraries instance. Any attempt to load a native library that + * has already been loaded by a class loader with another class loader + * will fail. + */ +public final class NativeLibraries { + private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent(); + private final Map libraries = new ConcurrentHashMap<>(); + private final ClassLoader loader; + // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary + // unless specified + private final Class caller; // may be null + private final boolean searchJavaLibraryPath; + + /** + * Creates a NativeLibraries instance for loading JNI native libraries + * via for System::loadLibrary use. + * + * 1. Support of auto-unloading. The loaded native libraries are unloaded + * when the class loader is reclaimed. + * 2. Support of linking of native method. See JNI spec. + * 3. Restriction on a native library that can only be loaded by one class loader. + * Each class loader manages its own set of native libraries. + * The same JNI native library cannot be loaded into more than one class loader. + * + * This static factory method is intended only for System::loadLibrary use. + * + * @see + * JNI Specification: Library and Version Management + */ + public static NativeLibraries newInstance(ClassLoader loader) { + return new NativeLibraries(loader, loader != null ? null : NativeLibraries.class, loader != null); + } + + private NativeLibraries(ClassLoader loader, Class caller, boolean searchJavaLibraryPath) { + this.loader = loader; + this.caller = caller; + this.searchJavaLibraryPath = searchJavaLibraryPath; + } + + /* + * Find the address of the given symbol name from the native libraries + * loaded in this NativeLibraries instance. + */ + public long find(String name) { + if (libraries.isEmpty()) + return 0; + + // the native libraries map may be updated in another thread + // when a native library is being loaded. No symbol will be + // searched from it yet. + for (NativeLibrary lib : libraries.values()) { + long entry = lib.find(name); + if (entry != 0) return entry; + } + return 0; + } + + /* + * Load a native library from the given file. Returns null if the given + * library is determined to be non-loadable, which is system-dependent. + * + * @param fromClass the caller class calling System::loadLibrary + * @param file the path of the native library + * @throws UnsatisfiedLinkError if any error in loading the native library + */ + @SuppressWarnings("removal") + public NativeLibrary loadLibrary(Class fromClass, File file) { + // Check to see if we're attempting to access a static library + String name = findBuiltinLib(file.getName()); + boolean isBuiltin = (name != null); + if (!isBuiltin) { + name = AccessController.doPrivileged(new PrivilegedAction<>() { + public String run() { + try { + if (loadLibraryOnlyIfPresent && !file.exists()) { + return null; + } + return file.getCanonicalPath(); + } catch (IOException e) { + return null; + } + } + }); + if (name == null) { + return null; + } + } + return loadLibrary(fromClass, name, isBuiltin); + } + + /** + * Returns a NativeLibrary of the given name. + * + * @param fromClass the caller class calling System::loadLibrary + * @param name library name + * @param isBuiltin built-in library + * @throws UnsatisfiedLinkError if the native library has already been loaded + * and registered in another NativeLibraries + */ + private NativeLibrary loadLibrary(Class fromClass, String name, boolean isBuiltin) { + ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); + if (this.loader != loader) { + throw new InternalError(fromClass.getName() + " not allowed to load library"); + } + + acquireNativeLibraryLock(name); + try { + // find if this library has already been loaded and registered in this NativeLibraries + NativeLibrary cached = libraries.get(name); + if (cached != null) { + return cached; + } + + // cannot be loaded by other class loaders + if (loadedLibraryNames.contains(name)) { + throw new UnsatisfiedLinkError("Native Library " + name + + " already loaded in another classloader"); + } + + /* + * When a library is being loaded, JNI_OnLoad function can cause + * another loadLibrary invocation that should succeed. + * + * Each thread maintains its own stack to hold the list of + * libraries it is loading. + * + * If there is a pending load operation for the library, we + * immediately return success; if the pending load is from + * a different class loader, we raise UnsatisfiedLinkError. + */ + for (NativeLibraryImpl lib : NativeLibraryContext.current()) { + if (name.equals(lib.name())) { + if (loader == lib.fromClass.getClassLoader()) { + return lib; + } else { + throw new UnsatisfiedLinkError("Native Library " + + name + " is being loaded in another classloader"); + } + } + } + + NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); + // load the native library + NativeLibraryContext.push(lib); + try { + if (!lib.open()) { + return null; // fail to open the native library + } + // auto unloading is only supported for JNI native libraries + // loaded by custom class loaders that can be unloaded. + // built-in class loaders are never unloaded. + boolean autoUnload = !VM.isSystemDomainLoader(loader) && loader != ClassLoaders.appClassLoader(); + if (autoUnload) { + // register the loaded native library for auto unloading + // when the class loader is reclaimed, all native libraries + // loaded that class loader will be unloaded. + // The entries in the libraries map are not removed since + // the entire map will be reclaimed altogether. + CleanerFactory.cleaner().register(loader, lib.unloader()); + } + } finally { + NativeLibraryContext.pop(); + } + // register the loaded native library + loadedLibraryNames.add(name); + libraries.put(name, lib); + return lib; + } finally { + releaseNativeLibraryLock(name); + } + } + + /** + * Loads a native library from the system library path and java library path. + * + * @param name library name + * + * @throws UnsatisfiedLinkError if the native library has already been loaded + * and registered in another NativeLibraries + */ + public NativeLibrary loadLibrary(String name) { + assert name.indexOf(File.separatorChar) < 0; + return loadLibrary(caller, name); + } + + /** + * Loads a native library from the system library path and java library path. + * + * @param name library name + * @param fromClass the caller class calling System::loadLibrary + * + * @throws UnsatisfiedLinkError if the native library has already been loaded + * and registered in another NativeLibraries + */ + public NativeLibrary loadLibrary(Class fromClass, String name) { + assert name.indexOf(File.separatorChar) < 0; + + NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); + if (lib == null && searchJavaLibraryPath) { + lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); + } + return lib; + } + + private NativeLibrary findFromPaths(String[] paths, Class fromClass, String name) { + for (String path : paths) { + File libfile = new File(path, System.mapLibraryName(name)); + NativeLibrary nl = loadLibrary(fromClass, libfile); + if (nl != null) { + return nl; + } + libfile = ClassLoaderHelper.mapAlternativeName(libfile); + if (libfile != null) { + nl = loadLibrary(fromClass, libfile); + if (nl != null) { + return nl; + } + } + } + return null; + } + + /** + * NativeLibraryImpl denotes a loaded native library instance. + * Each NativeLibraries contains a map of loaded native libraries in the + * private field {@code libraries}. + * + * Every native library requires a particular version of JNI. This is + * denoted by the private {@code jniVersion} field. This field is set by + * the VM when it loads the library, and used by the VM to pass the correct + * version of JNI to the native methods. + */ + static class NativeLibraryImpl extends NativeLibrary { + // the class from which the library is loaded, also indicates + // the loader this native library belongs. + final Class fromClass; + // the canonicalized name of the native library. + // or static library name + final String name; + // Indicates if the native library is linked into the VM + final boolean isBuiltin; + + // opaque handle to native library, used in native code. + long handle; + // the version of JNI environment the native library requires. + int jniVersion; + + NativeLibraryImpl(Class fromClass, String name, boolean isBuiltin) { + this.fromClass = fromClass; + this.name = name; + this.isBuiltin = isBuiltin; + } + + @Override + public String name() { + return name; + } + + @Override + public long find(String name) { + return findEntry0(handle, name); + } + + /* + * Unloader::run method is invoked to unload the native library + * when this class loader becomes phantom reachable. + */ + private Runnable unloader() { + return new Unloader(name, handle, isBuiltin); + } + + /* + * Loads the named native library + */ + boolean open() { + if (handle != 0) { + throw new InternalError("Native library " + name + " has been loaded"); + } + + return load(this, name, isBuiltin, throwExceptionIfFail()); + } + + @SuppressWarnings("removal") + private boolean throwExceptionIfFail() { + if (loadLibraryOnlyIfPresent) return true; + + // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM + // will include the error message from dlopen to provide diagnostic information + return AccessController.doPrivileged(new PrivilegedAction<>() { + public Boolean run() { + File file = new File(name); + return file.exists(); + } + }); + } + + /* + * Close this native library. + */ + void close() { + unload(name, isBuiltin, handle); + } + } + + /* + * The run() method will be invoked when this class loader becomes + * phantom reachable to unload the native library. + */ + static class Unloader implements Runnable { + // This represents the context when a native library is unloaded + // and getFromClass() will return null, + static final NativeLibraryImpl UNLOADER = + new NativeLibraryImpl(null, "dummy", false); + + final String name; + final long handle; + final boolean isBuiltin; + + Unloader(String name, long handle, boolean isBuiltin) { + if (handle == 0) { + throw new IllegalArgumentException( + "Invalid handle for native library " + name); + } + + this.name = name; + this.handle = handle; + this.isBuiltin = isBuiltin; + } + + @Override + public void run() { + acquireNativeLibraryLock(name); + try { + /* remove the native library name */ + if (!loadedLibraryNames.remove(name)) { + throw new IllegalStateException(name + " has already been unloaded"); + } + NativeLibraryContext.push(UNLOADER); + try { + unload(name, isBuiltin, handle); + } finally { + NativeLibraryContext.pop(); + } + } finally { + releaseNativeLibraryLock(name); + } + } + } + + /* + * Holds system and user library paths derived from the + * {@code java.library.path} and {@code sun.boot.library.path} system + * properties. The system properties are eagerly read at bootstrap, then + * lazily parsed on first use to avoid initialization ordering issues. + */ + static class LibraryPaths { + // The paths searched for libraries + static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); + static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); + } + + // All native libraries we've loaded. + private static final Set loadedLibraryNames = + ConcurrentHashMap.newKeySet(); + + // reentrant lock class that allows exact counting (with external synchronization) + @SuppressWarnings("serial") + private static final class CountedLock extends ReentrantLock { + + private int counter = 0; + + public void increment() { + if (counter == Integer.MAX_VALUE) { + // prevent overflow + throw new Error("Maximum lock count exceeded"); + } + ++counter; + } + + public void decrement() { + --counter; + } + + public int getCounter() { + return counter; + } + } + + // Maps native library name to the corresponding lock object + private static final Map nativeLibraryLockMap = + new ConcurrentHashMap<>(); + + private static void acquireNativeLibraryLock(String libraryName) { + nativeLibraryLockMap.compute(libraryName, + new BiFunction<>() { + public CountedLock apply(String name, CountedLock currentLock) { + if (currentLock == null) { + currentLock = new CountedLock(); + } + // safe as compute BiFunction<> is executed atomically + currentLock.increment(); + return currentLock; + } + } + ).lock(); + } + + private static void releaseNativeLibraryLock(String libraryName) { + CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName, + new BiFunction<>() { + public CountedLock apply(String name, CountedLock currentLock) { + if (currentLock.getCounter() == 1) { + // unlock and release the object if no other threads are queued + currentLock.unlock(); + // remove the element + return null; + } else { + currentLock.decrement(); + return currentLock; + } + } + } + ); + if (lock != null) { + lock.unlock(); + } + } + + // native libraries being loaded + private static final class NativeLibraryContext { + + // Maps thread object to the native library context stack, maintained by each thread + private static Map> nativeLibraryThreadContext = + new ConcurrentHashMap<>(); + + // returns a context associated with the current thread + private static Deque current() { + return nativeLibraryThreadContext.computeIfAbsent( + Thread.currentThread(), + new Function<>() { + public Deque apply(Thread t) { + return new ArrayDeque<>(8); + } + }); + } + + private static NativeLibraryImpl peek() { + return current().peek(); + } + + private static void push(NativeLibraryImpl lib) { + current().push(lib); + } + + private static void pop() { + // this does not require synchronization since each + // thread has its own context + Deque libs = current(); + libs.pop(); + if (libs.isEmpty()) { + // context can be safely removed once empty + nativeLibraryThreadContext.remove(Thread.currentThread()); + } + } + + private static boolean isEmpty() { + Deque context = + nativeLibraryThreadContext.get(Thread.currentThread()); + return (context == null || context.isEmpty()); + } + } + + // Invoked in the VM to determine the context class in JNI_OnLoad + // and JNI_OnUnload + private static Class getFromClass() { + if (NativeLibraryContext.isEmpty()) { // only default library + return Object.class; + } + return NativeLibraryContext.peek().fromClass; + } + + /* + * Return true if the given library is successfully loaded. + * If the given library cannot be loaded for any reason, + * if throwExceptionIfFail is false, then this method returns false; + * otherwise, UnsatisfiedLinkError will be thrown. + * + * JNI FindClass expects the caller class if invoked from JNI_OnLoad + * and JNI_OnUnload is NativeLibrary class. + */ + private static native boolean load(NativeLibraryImpl impl, String name, + boolean isBuiltin, + boolean throwExceptionIfFail); + /* + * Unload the named library. JNI_OnUnload, if present, will be invoked + * before the native library is unloaded. + */ + private static native void unload(String name, boolean isBuiltin, long handle); + private static native String findBuiltinLib(String name); +} diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibrary.class b/tests/test_data/std/jdk/internal/loader/NativeLibrary.class new file mode 100644 index 00000000..3dfa3c6e Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/NativeLibrary.class differ diff --git a/tests/test_data/std/jdk/internal/loader/NativeLibrary.java b/tests/test_data/std/jdk/internal/loader/NativeLibrary.java new file mode 100644 index 00000000..228356e0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/NativeLibrary.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +/** + * NativeLibrary represents a loaded native library instance. + */ +public abstract class NativeLibrary { + public abstract String name(); + + /** + * Finds the address of the entry of the given name. Returns 0 + * if not found. + * + * @param name the name of the symbol to be found + */ + public abstract long find(String name); + + /** + * Finds the address of the entry of the given name. + * + * @param name the name of the symbol to be found + * @throws NoSuchMethodException if the named entry is not found. + */ + public final long lookup(String name) throws NoSuchMethodException { + long addr = find(name); + if (0 == addr) { + throw new NoSuchMethodException("Cannot find symbol " + name + " in library " + name()); + } + return addr; + } + + /* + * Returns the address of the named symbol defined in the library of + * the given handle. Returns 0 if not found. + */ + static native long findEntry0(long handle, String name); +} diff --git a/tests/test_data/std/jdk/internal/loader/RawNativeLibraries$1.class b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries$1.class new file mode 100644 index 00000000..2e1d029b Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/RawNativeLibraries$RawNativeLibraryImpl.class b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries$RawNativeLibraryImpl.class new file mode 100644 index 00000000..c0259c28 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries$RawNativeLibraryImpl.class differ diff --git a/tests/test_data/std/jdk/internal/loader/RawNativeLibraries.class b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries.class new file mode 100644 index 00000000..82397688 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries.class differ diff --git a/tests/test_data/std/jdk/internal/loader/RawNativeLibraries.java b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries.java new file mode 100644 index 00000000..35ed4701 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/RawNativeLibraries.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.loader; + +import jdk.internal.misc.VM; + +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * RawNativeLibraries has the following properties: + * 1. Native libraries loaded in this RawNativeLibraries instance are + * not JNI native libraries. Hence JNI_OnLoad and JNI_OnUnload will + * be ignored. No support for linking of native method. + * 2. Native libraries not auto-unloaded. They may be explicitly unloaded + * via NativeLibraries::unload. + * 3. No relationship with class loaders. + */ +public final class RawNativeLibraries { + final Set libraries = ConcurrentHashMap.newKeySet(); + final Class caller; + + private RawNativeLibraries(MethodHandles.Lookup trustedCaller) { + this.caller = trustedCaller.lookupClass(); + } + + /** + * Creates a RawNativeLibraries instance that has no relationship with + * any class loaders and disabled auto unloading. + * + * This static factory method is restricted for JDK trusted class use. + */ + public static RawNativeLibraries newInstance(MethodHandles.Lookup trustedCaller) { + if (!trustedCaller.hasFullPrivilegeAccess() || + !VM.isSystemDomainLoader(trustedCaller.lookupClass().getClassLoader())) { + throw new InternalError(trustedCaller + " does not have access to raw native library loading"); + } + return new RawNativeLibraries(trustedCaller); + } + + /* + * Load a native library from the given path. Returns null if the given + * library is determined to be non-loadable, which is system-dependent. + * + * The library is opened with the platform-specific library loading + * mechanism. If this method is called with the same path multiple times, + * the library is opened the same number of times. To close the library + * of the given path, {@code #unload} must be called on all the + * {@code NativeLibrary} instances that load it. + * + * @param path the path of the native library + */ + @SuppressWarnings("removal") + public NativeLibrary load(Path path) { + String name = AccessController.doPrivileged(new PrivilegedAction<>() { + public String run() { + try { + return path.toRealPath().toString(); + } catch (IOException e) { + return null; + } + } + }); + if (name == null) { + return null; + } + return load(name); + } + + /** + * Load a native library of the given pathname, which is platform-specific. + * Returns null if it fails to load the given pathname. + * + * If the given pathname does not contain a name-separator character, + * for example on Unix a slash character, the library search strategy + * is system-dependent for example on Unix, see dlopen. + * + * @apiNote + * The {@code pathname} argument is platform-specific. + * {@link System#mapLibraryName} can be used to convert a name to + * a platform-specific pathname: + * {@snippet + * RawNativeLibraries libs = RawNativeLibraries.newInstance(MethodHandles.lookup()); + * NativeLibrary lib = libs.load(System.mapLibraryName("blas")); + * } + * + * The library is opened with the platform-specific library loading + * mechanism. If this method is called with the same pathname multiple times, + * the library is opened the same number of times. To close the library + * of the given path, {@code #unload} must be called on all the + * {@code NativeLibrary} instances that load it. + * + * @param pathname the pathname of the native library + * @see System#mapLibraryName(String) + */ + public NativeLibrary load(String pathname) { + RawNativeLibraryImpl lib = new RawNativeLibraryImpl(pathname); + if (!lib.open()) { + return null; + } + libraries.add(lib); + return lib; + } + + /* + * Unloads the given native library. Each {@code NativeLibrary} + * instance can be unloaded only once. + * + * The native library may remain opened after this method is called. + * Refer to the platform-specific library loading mechanism, for example, + * dlopen/dlclose on Unix or LoadLibrary/FreeLibrary on Windows. + * + * @throws IllegalArgumentException if the given library is not + * loaded by this RawNativeLibraries or has already been unloaded + */ + public void unload(NativeLibrary lib) { + Objects.requireNonNull(lib); + if (!libraries.remove(lib)) { + throw new IllegalArgumentException("can't unload " + lib.name() + " loaded from " + lib); + } + RawNativeLibraryImpl nl = (RawNativeLibraryImpl)lib; + nl.close(); + } + + static class RawNativeLibraryImpl extends NativeLibrary { + // the name of the raw native library. + final String name; + // opaque handle to raw native library, used in native code. + long handle; + + RawNativeLibraryImpl(String name) { + this.name = name; + } + + @Override + public String name() { + return name; + } + + @Override + public long find(String name) { + return findEntry0(handle, name); + } + + /* + * Loads the named native library. + */ + boolean open() { + if (handle != 0) { + throw new InternalError("Native library " + name + " has been loaded"); + } + return load0(this, name); + } + + /* + * Close this native library. + */ + void close() { + unload0(name, handle); + } + } + + private static native boolean load0(RawNativeLibraryImpl impl, String name); + private static native void unload0(String name, long handle); +} + diff --git a/tests/test_data/std/jdk/internal/loader/Resource.class b/tests/test_data/std/jdk/internal/loader/Resource.class new file mode 100644 index 00000000..4f66e52e Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/Resource.class differ diff --git a/tests/test_data/std/jdk/internal/loader/Resource.java b/tests/test_data/std/jdk/internal/loader/Resource.java new file mode 100644 index 00000000..d119c4b5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/Resource.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 1998, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.io.EOFException; +import java.net.URL; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.InputStream; +import java.security.CodeSigner; +import java.util.jar.Manifest; +import java.nio.ByteBuffer; +import java.util.Arrays; +import sun.nio.ByteBuffered; + +/** + * This class is used to represent a Resource that has been loaded + * from the class path. + * + * @author David Connelly + * @since 1.2 + */ +public abstract class Resource { + /** + * Returns the name of the Resource. + */ + public abstract String getName(); + + /** + * Returns the URL of the Resource. + */ + public abstract URL getURL(); + + /** + * Returns the CodeSource URL for the Resource. + */ + public abstract URL getCodeSourceURL(); + + /** + * Returns an InputStream for reading the Resource data. + */ + public abstract InputStream getInputStream() throws IOException; + + /** + * Returns the length of the Resource data, or -1 if unknown. + */ + public abstract int getContentLength() throws IOException; + + private InputStream cis; + + /* Cache result in case getBytes is called after getByteBuffer. */ + private synchronized InputStream cachedInputStream() throws IOException { + if (cis == null) { + cis = getInputStream(); + } + return cis; + } + + /** + * Returns the Resource data as an array of bytes. + */ + public byte[] getBytes() throws IOException { + byte[] b; + // Get stream before content length so that a FileNotFoundException + // can propagate upwards without being caught too early + InputStream in = cachedInputStream(); + + // This code has been uglified to protect against interrupts. + // Even if a thread has been interrupted when loading resources, + // the IO should not abort, so must carefully retry, failing only + // if the retry leads to some other IO exception. + + boolean isInterrupted = Thread.interrupted(); + int len; + for (;;) { + try { + len = getContentLength(); + break; + } catch (InterruptedIOException iioe) { + Thread.interrupted(); + isInterrupted = true; + } + } + + try { + b = new byte[0]; + if (len == -1) len = Integer.MAX_VALUE; + int pos = 0; + while (pos < len) { + int bytesToRead; + if (pos >= b.length) { // Only expand when there's no room + bytesToRead = Math.min(len - pos, b.length + 1024); + if (bytesToRead < 0) { + // Can overflow only due to large b.length + bytesToRead = len - pos; + } + b = Arrays.copyOf(b, pos + bytesToRead); + } else { + bytesToRead = b.length - pos; + } + int cc = 0; + try { + cc = in.read(b, pos, bytesToRead); + } catch (InterruptedIOException iioe) { + Thread.interrupted(); + isInterrupted = true; + } + if (cc < 0) { + if (len != Integer.MAX_VALUE) { + throw new EOFException("Detect premature EOF"); + } else { + if (b.length != pos) { + b = Arrays.copyOf(b, pos); + } + break; + } + } + pos += cc; + } + } finally { + try { + in.close(); + } catch (InterruptedIOException iioe) { + isInterrupted = true; + } catch (IOException ignore) {} + + if (isInterrupted) { + Thread.currentThread().interrupt(); + } + } + return b; + } + + /** + * Returns the Resource data as a ByteBuffer, but only if the input stream + * was implemented on top of a ByteBuffer. Return {@code null} otherwise. + * @return Resource data or null. + */ + public ByteBuffer getByteBuffer() throws IOException { + InputStream in = cachedInputStream(); + if (in instanceof ByteBuffered) { + return ((ByteBuffered)in).getByteBuffer(); + } + return null; + } + + /** + * Returns the Manifest for the Resource, or null if none. + */ + public Manifest getManifest() throws IOException { + return null; + } + + /** + * Returns theCertificates for the Resource, or null if none. + */ + public java.security.cert.Certificate[] getCertificates() { + return null; + } + + /** + * Returns the code signers for the Resource, or null if none. + */ + public CodeSigner[] getCodeSigners() { + return null; + } + + /** + * Returns non-fatal reading error during data retrieval if there's any. + * For example, CRC error when reading a JAR entry. + */ + public Exception getDataError() { + return null; + } +} diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$1.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$1.class new file mode 100644 index 00000000..d003c686 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$2.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$2.class new file mode 100644 index 00000000..90ccaf96 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$2.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$3.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$3.class new file mode 100644 index 00000000..35fd62c2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$3.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$FileLoader$1.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$FileLoader$1.class new file mode 100644 index 00000000..36009be7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$FileLoader$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$FileLoader.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$FileLoader.class new file mode 100644 index 00000000..1d64ea61 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$FileLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader$1.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader$1.class new file mode 100644 index 00000000..554834b2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader$2.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader$2.class new file mode 100644 index 00000000..0076d076 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader$2.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader.class new file mode 100644 index 00000000..36bc9fa5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$JarLoader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$Loader$1.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$Loader$1.class new file mode 100644 index 00000000..115f5119 Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$Loader$1.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath$Loader.class b/tests/test_data/std/jdk/internal/loader/URLClassPath$Loader.class new file mode 100644 index 00000000..ef905c9b Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath$Loader.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath.class b/tests/test_data/std/jdk/internal/loader/URLClassPath.class new file mode 100644 index 00000000..5b575c0b Binary files /dev/null and b/tests/test_data/std/jdk/internal/loader/URLClassPath.class differ diff --git a/tests/test_data/std/jdk/internal/loader/URLClassPath.java b/tests/test_data/std/jdk/internal/loader/URLClassPath.java new file mode 100644 index 00000000..dbd76e07 --- /dev/null +++ b/tests/test_data/std/jdk/internal/loader/URLClassPath.java @@ -0,0 +1,1092 @@ +/* + * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; +import java.security.AccessControlContext; +import java.security.AccessControlException; +import java.security.AccessController; +import java.security.CodeSigner; +import java.security.Permission; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.cert.Certificate; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.jar.JarFile; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.jar.JarEntry; +import java.util.jar.Manifest; +import java.util.jar.Attributes; +import java.util.jar.Attributes.Name; +import java.util.zip.ZipFile; + +import jdk.internal.access.JavaNetURLAccess; +import jdk.internal.access.JavaUtilZipFileAccess; +import jdk.internal.access.SharedSecrets; +import sun.net.util.URLUtil; +import sun.net.www.ParseUtil; +import sun.security.action.GetPropertyAction; + +/** + * This class is used to maintain a search path of URLs for loading classes + * and resources from both JAR files and directories. + * + * @author David Connelly + */ +public class URLClassPath { + private static final String USER_AGENT_JAVA_VERSION = "UA-Java-Version"; + private static final String JAVA_VERSION; + private static final boolean DEBUG; + private static final boolean DISABLE_JAR_CHECKING; + private static final boolean DISABLE_ACC_CHECKING; + private static final boolean DISABLE_CP_URL_CHECK; + private static final boolean DEBUG_CP_URL_CHECK; + + static { + Properties props = GetPropertyAction.privilegedGetProperties(); + JAVA_VERSION = props.getProperty("java.version"); + DEBUG = (props.getProperty("sun.misc.URLClassPath.debug") != null); + String p = props.getProperty("sun.misc.URLClassPath.disableJarChecking"); + DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false; + + p = props.getProperty("jdk.net.URLClassPath.disableRestrictedPermissions"); + DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.isEmpty() : false; + + // This property will be removed in a later release + p = props.getProperty("jdk.net.URLClassPath.disableClassPathURLCheck"); + DISABLE_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false; + + // Print a message for each Class-Path entry that is ignored (assuming + // the check is not disabled). + p = props.getProperty("jdk.net.URLClassPath.showIgnoredClassPathEntries"); + DEBUG_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false; + } + + /* The original search path of URLs. */ + private final ArrayList path; + + /* The deque of unopened URLs */ + private final ArrayDeque unopenedUrls; + + /* The resulting search path of Loaders */ + private final ArrayList loaders = new ArrayList<>(); + + /* Map of each URL opened to its corresponding Loader */ + private final HashMap lmap = new HashMap<>(); + + /* The jar protocol handler to use when creating new URLs */ + private final URLStreamHandler jarHandler; + + /* Whether this URLClassLoader has been closed yet */ + private boolean closed = false; + + /* The context to be used when loading classes and resources. If non-null + * this is the context that was captured during the creation of the + * URLClassLoader. null implies no additional security restrictions. */ + @SuppressWarnings("removal") + private final AccessControlContext acc; + + /** + * Creates a new URLClassPath for the given URLs. The URLs will be + * searched in the order specified for classes and resources. A URL + * ending with a '/' is assumed to refer to a directory. Otherwise, + * the URL is assumed to refer to a JAR file. + * + * @param urls the directory and JAR file URLs to search for classes + * and resources + * @param factory the URLStreamHandlerFactory to use when creating new URLs + * @param acc the context to be used when loading classes and resources, may + * be null + */ + public URLClassPath(URL[] urls, + URLStreamHandlerFactory factory, + @SuppressWarnings("removal") AccessControlContext acc) { + ArrayList path = new ArrayList<>(urls.length); + ArrayDeque unopenedUrls = new ArrayDeque<>(urls.length); + for (URL url : urls) { + path.add(url); + unopenedUrls.add(url); + } + this.path = path; + this.unopenedUrls = unopenedUrls; + + if (factory != null) { + jarHandler = factory.createURLStreamHandler("jar"); + } else { + jarHandler = null; + } + if (DISABLE_ACC_CHECKING) + this.acc = null; + else + this.acc = acc; + } + + public URLClassPath(URL[] urls, @SuppressWarnings("removal") AccessControlContext acc) { + this(urls, null, acc); + } + + /** + * Constructs a URLClassPath from a class path string. + * + * @param cp the class path string + * @param skipEmptyElements indicates if empty elements are ignored or + * treated as the current working directory + * + * @apiNote Used to create the application class path. + */ + URLClassPath(String cp, boolean skipEmptyElements) { + ArrayList path = new ArrayList<>(); + if (cp != null) { + // map each element of class path to a file URL + int off = 0, next; + do { + next = cp.indexOf(File.pathSeparator, off); + String element = (next == -1) + ? cp.substring(off) + : cp.substring(off, next); + if (!element.isEmpty() || !skipEmptyElements) { + URL url = toFileURL(element); + if (url != null) path.add(url); + } + off = next + 1; + } while (next != -1); + } + + // can't use ArrayDeque#addAll or new ArrayDeque(Collection); + // it's too early in the bootstrap to trigger use of lambdas + int size = path.size(); + ArrayDeque unopenedUrls = new ArrayDeque<>(size); + for (int i = 0; i < size; i++) + unopenedUrls.add(path.get(i)); + + this.unopenedUrls = unopenedUrls; + this.path = path; + // the application class loader uses the built-in protocol handler to avoid protocol + // handler lookup when opening JAR files on the class path. + this.jarHandler = new sun.net.www.protocol.jar.Handler(); + this.acc = null; + } + + public synchronized List closeLoaders() { + if (closed) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (Loader loader : loaders) { + try { + loader.close(); + } catch (IOException e) { + result.add(e); + } + } + closed = true; + return result; + } + + /** + * Appends the specified URL to the search path of directory and JAR + * file URLs from which to load classes and resources. + *

+ * If the URL specified is null or is already in the list of + * URLs, then invoking this method has no effect. + */ + public synchronized void addURL(URL url) { + if (closed || url == null) + return; + synchronized (unopenedUrls) { + if (! path.contains(url)) { + unopenedUrls.addLast(url); + path.add(url); + } + } + } + + /** + * Appends the specified file path as a file URL to the search path. + */ + public void addFile(String s) { + URL url = toFileURL(s); + if (url != null) { + addURL(url); + } + } + + /** + * Returns a file URL for the given file path. + */ + private static URL toFileURL(String s) { + try { + File f = new File(s).getCanonicalFile(); + return ParseUtil.fileToEncodedURL(f); + } catch (IOException e) { + return null; + } + } + + /** + * Returns the original search path of URLs. + */ + public URL[] getURLs() { + synchronized (unopenedUrls) { + return path.toArray(new URL[0]); + } + } + + /** + * Finds the resource with the specified name on the URL search path + * or null if not found or security check fails. + * + * @param name the name of the resource + * @param check whether to perform a security check + * @return a {@code URL} for the resource, or {@code null} + * if the resource could not be found. + */ + public URL findResource(String name, boolean check) { + Loader loader; + for (int i = 0; (loader = getLoader(i)) != null; i++) { + URL url = loader.findResource(name, check); + if (url != null) { + return url; + } + } + return null; + } + + /** + * Finds the first Resource on the URL search path which has the specified + * name. Returns null if no Resource could be found. + * + * @param name the name of the Resource + * @param check whether to perform a security check + * @return the Resource, or null if not found + */ + public Resource getResource(String name, boolean check) { + if (DEBUG) { + System.err.println("URLClassPath.getResource(\"" + name + "\")"); + } + + Loader loader; + for (int i = 0; (loader = getLoader(i)) != null; i++) { + Resource res = loader.getResource(name, check); + if (res != null) { + return res; + } + } + return null; + } + + /** + * Finds all resources on the URL search path with the given name. + * Returns an enumeration of the URL objects. + * + * @param name the resource name + * @return an Enumeration of all the urls having the specified name + */ + public Enumeration findResources(final String name, + final boolean check) { + return new Enumeration<>() { + private int index = 0; + private URL url = null; + + private boolean next() { + if (url != null) { + return true; + } else { + Loader loader; + while ((loader = getLoader(index++)) != null) { + url = loader.findResource(name, check); + if (url != null) { + return true; + } + } + return false; + } + } + + public boolean hasMoreElements() { + return next(); + } + + public URL nextElement() { + if (!next()) { + throw new NoSuchElementException(); + } + URL u = url; + url = null; + return u; + } + }; + } + + public Resource getResource(String name) { + return getResource(name, true); + } + + /** + * Finds all resources on the URL search path with the given name. + * Returns an enumeration of the Resource objects. + * + * @param name the resource name + * @return an Enumeration of all the resources having the specified name + */ + public Enumeration getResources(final String name, + final boolean check) { + return new Enumeration<>() { + private int index = 0; + private Resource res = null; + + private boolean next() { + if (res != null) { + return true; + } else { + Loader loader; + while ((loader = getLoader(index++)) != null) { + res = loader.getResource(name, check); + if (res != null) { + return true; + } + } + return false; + } + } + + public boolean hasMoreElements() { + return next(); + } + + public Resource nextElement() { + if (!next()) { + throw new NoSuchElementException(); + } + Resource r = res; + res = null; + return r; + } + }; + } + + public Enumeration getResources(final String name) { + return getResources(name, true); + } + + /* + * Returns the Loader at the specified position in the URL search + * path. The URLs are opened and expanded as needed. Returns null + * if the specified index is out of range. + */ + private synchronized Loader getLoader(int index) { + if (closed) { + return null; + } + // Expand URL search path until the request can be satisfied + // or unopenedUrls is exhausted. + while (loaders.size() < index + 1) { + final URL url; + synchronized (unopenedUrls) { + url = unopenedUrls.pollFirst(); + if (url == null) + return null; + } + // Skip this URL if it already has a Loader. + String urlNoFragString = URLUtil.urlNoFragString(url); + if (lmap.containsKey(urlNoFragString)) { + continue; + } + // Otherwise, create a new Loader for the URL. + Loader loader; + try { + loader = getLoader(url); + // If the loader defines a local class path then add the + // URLs as the next URLs to be opened. + URL[] urls = loader.getClassPath(); + if (urls != null) { + push(urls); + } + } catch (IOException e) { + // Silently ignore for now... + continue; + } catch (SecurityException se) { + // Always silently ignore. The context, if there is one, that + // this URLClassPath was given during construction will never + // have permission to access the URL. + if (DEBUG) { + System.err.println("Failed to access " + url + ", " + se ); + } + continue; + } + // Finally, add the Loader to the search path. + loaders.add(loader); + lmap.put(urlNoFragString, loader); + } + return loaders.get(index); + } + + /* + * Returns the Loader for the specified base URL. + */ + @SuppressWarnings("removal") + private Loader getLoader(final URL url) throws IOException { + try { + return AccessController.doPrivileged( + new PrivilegedExceptionAction<>() { + public Loader run() throws IOException { + String protocol = url.getProtocol(); // lower cased in URL + String file = url.getFile(); + if (file != null && file.endsWith("/")) { + if ("file".equals(protocol)) { + return new FileLoader(url); + } else if ("jar".equals(protocol) && + isDefaultJarHandler(url) && + file.endsWith("!/")) { + // extract the nested URL + @SuppressWarnings("deprecation") + URL nestedUrl = new URL(file.substring(0, file.length() - 2)); + return new JarLoader(nestedUrl, jarHandler, acc); + } else { + return new Loader(url); + } + } else { + return new JarLoader(url, jarHandler, acc); + } + } + }, acc); + } catch (PrivilegedActionException pae) { + throw (IOException)pae.getException(); + } + } + + private static final JavaNetURLAccess JNUA + = SharedSecrets.getJavaNetURLAccess(); + + private static boolean isDefaultJarHandler(URL u) { + URLStreamHandler h = JNUA.getHandler(u); + return h instanceof sun.net.www.protocol.jar.Handler; + } + + /* + * Pushes the specified URLs onto the head of unopened URLs. + */ + private void push(URL[] urls) { + synchronized (unopenedUrls) { + for (int i = urls.length - 1; i >= 0; --i) { + unopenedUrls.addFirst(urls[i]); + } + } + } + + /* + * Checks whether the resource URL should be returned. + * Returns null on security check failure. + * Called by java.net.URLClassLoader. + */ + public static URL checkURL(URL url) { + if (url != null) { + try { + check(url); + } catch (Exception e) { + return null; + } + } + return url; + } + + /* + * Checks whether the resource URL should be returned. + * Throws exception on failure. + * Called internally within this file. + */ + public static void check(URL url) throws IOException { + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + URLConnection urlConnection = url.openConnection(); + Permission perm = urlConnection.getPermission(); + if (perm != null) { + try { + security.checkPermission(perm); + } catch (SecurityException se) { + // fallback to checkRead/checkConnect for pre 1.2 + // security managers + if ((perm instanceof java.io.FilePermission) && + perm.getActions().contains("read")) { + security.checkRead(perm.getName()); + } else if ((perm instanceof + java.net.SocketPermission) && + perm.getActions().contains("connect")) { + URL locUrl = url; + if (urlConnection instanceof JarURLConnection) { + locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); + } + security.checkConnect(locUrl.getHost(), + locUrl.getPort()); + } else { + throw se; + } + } + } + } + } + + /** + * Nested class used to represent a loader of resources and classes + * from a base URL. + */ + private static class Loader implements Closeable { + private final URL base; + private JarFile jarfile; // if this points to a jar file + + /* + * Creates a new Loader for the specified URL. + */ + Loader(URL url) { + base = url; + } + + /* + * Returns the base URL for this Loader. + */ + final URL getBaseURL() { + return base; + } + + URL findResource(final String name, boolean check) { + URL url; + try { + @SuppressWarnings("deprecation") + var _unused = url = new URL(base, ParseUtil.encodePath(name, false)); + } catch (MalformedURLException e) { + return null; + } + + try { + if (check) { + URLClassPath.check(url); + } + + /* + * For a HTTP connection we use the HEAD method to + * check if the resource exists. + */ + URLConnection uc = url.openConnection(); + if (uc instanceof HttpURLConnection) { + HttpURLConnection hconn = (HttpURLConnection)uc; + hconn.setRequestMethod("HEAD"); + if (hconn.getResponseCode() >= HttpURLConnection.HTTP_BAD_REQUEST) { + return null; + } + } else { + // our best guess for the other cases + uc.setUseCaches(false); + InputStream is = uc.getInputStream(); + is.close(); + } + return url; + } catch (Exception e) { + return null; + } + } + + Resource getResource(final String name, boolean check) { + final URL url; + try { + @SuppressWarnings("deprecation") + var _unused = url = new URL(base, ParseUtil.encodePath(name, false)); + } catch (MalformedURLException e) { + return null; + } + final URLConnection uc; + try { + if (check) { + URLClassPath.check(url); + } + uc = url.openConnection(); + + if (uc instanceof JarURLConnection) { + /* Need to remember the jar file so it can be closed + * in a hurry. + */ + JarURLConnection juc = (JarURLConnection)uc; + jarfile = JarLoader.checkJar(juc.getJarFile()); + } + + InputStream in = uc.getInputStream(); + } catch (Exception e) { + return null; + } + return new Resource() { + public String getName() { return name; } + public URL getURL() { return url; } + public URL getCodeSourceURL() { return base; } + public InputStream getInputStream() throws IOException { + return uc.getInputStream(); + } + public int getContentLength() throws IOException { + return uc.getContentLength(); + } + }; + } + + /* + * Returns the Resource for the specified name, or null if not + * found or the caller does not have the permission to get the + * resource. + */ + Resource getResource(final String name) { + return getResource(name, true); + } + + /* + * Closes this loader and release all resources. + * Method overridden in sub-classes. + */ + @Override + public void close() throws IOException { + if (jarfile != null) { + jarfile.close(); + } + } + + /* + * Returns the local class path for this loader, or null if none. + */ + URL[] getClassPath() throws IOException { + return null; + } + } + + /* + * Nested class used to represent a Loader of resources from a JAR URL. + */ + private static class JarLoader extends Loader { + private JarFile jar; + private final URL csu; + @SuppressWarnings("removal") + private final AccessControlContext acc; + private boolean closed = false; + private static final JavaUtilZipFileAccess zipAccess = + SharedSecrets.getJavaUtilZipFileAccess(); + + /* + * Creates a new JarLoader for the specified URL referring to + * a JAR file. + */ + private JarLoader(URL url, URLStreamHandler jarHandler, + @SuppressWarnings("removal") AccessControlContext acc) + throws IOException + { + super(newURL("jar", "", -1, url + "!/", jarHandler)); + csu = url; + this.acc = acc; + + ensureOpen(); + } + + @SuppressWarnings("deprecation") + private static URL newURL(String protocol, String host, int port, String file, URLStreamHandler handler) + throws MalformedURLException + { + return new URL(protocol, host, port, file, handler); + } + + @Override + public void close () throws IOException { + // closing is synchronized at higher level + if (!closed) { + closed = true; + // in case not already open. + ensureOpen(); + jar.close(); + } + } + + private boolean isOptimizable(URL url) { + return "file".equals(url.getProtocol()); + } + + @SuppressWarnings("removal") + private void ensureOpen() throws IOException { + if (jar == null) { + try { + AccessController.doPrivileged( + new PrivilegedExceptionAction<>() { + public Void run() throws IOException { + if (DEBUG) { + System.err.println("Opening " + csu); + Thread.dumpStack(); + } + jar = getJarFile(csu); + return null; + } + }, acc); + } catch (PrivilegedActionException pae) { + throw (IOException)pae.getException(); + } + } + } + + /* Throws if the given jar file is does not start with the correct LOC */ + @SuppressWarnings("removal") + static JarFile checkJar(JarFile jar) throws IOException { + if (System.getSecurityManager() != null && !DISABLE_JAR_CHECKING + && !zipAccess.startsWithLocHeader(jar)) { + IOException x = new IOException("Invalid Jar file"); + try { + jar.close(); + } catch (IOException ex) { + x.addSuppressed(ex); + } + throw x; + } + + return jar; + } + + private JarFile getJarFile(URL url) throws IOException { + // Optimize case where url refers to a local jar file + if (isOptimizable(url)) { + FileURLMapper p = new FileURLMapper(url); + if (!p.exists()) { + throw new FileNotFoundException(p.getPath()); + } + return checkJar(new JarFile(new File(p.getPath()), true, ZipFile.OPEN_READ, + JarFile.runtimeVersion())); + } + @SuppressWarnings("deprecation") + URLConnection uc = (new URL(getBaseURL(), "#runtime")).openConnection(); + uc.setRequestProperty(USER_AGENT_JAVA_VERSION, JAVA_VERSION); + JarFile jarFile = ((JarURLConnection)uc).getJarFile(); + return checkJar(jarFile); + } + + /* + * Creates the resource and if the check flag is set to true, checks if + * is its okay to return the resource. + */ + Resource checkResource(final String name, boolean check, + final JarEntry entry) { + + final URL url; + try { + String nm; + if (jar.isMultiRelease()) { + nm = entry.getRealName(); + } else { + nm = name; + } + @SuppressWarnings("deprecation") + var _unused = url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false)); + if (check) { + URLClassPath.check(url); + } + } catch (@SuppressWarnings("removal") AccessControlException | IOException e) { + return null; + } + + return new Resource() { + private Exception dataError = null; + public String getName() { return name; } + public URL getURL() { return url; } + public URL getCodeSourceURL() { return csu; } + public InputStream getInputStream() throws IOException + { return jar.getInputStream(entry); } + public int getContentLength() + { return (int)entry.getSize(); } + public Manifest getManifest() throws IOException { + SharedSecrets.javaUtilJarAccess().ensureInitialization(jar); + return jar.getManifest(); + } + public Certificate[] getCertificates() + { return entry.getCertificates(); }; + public CodeSigner[] getCodeSigners() + { return entry.getCodeSigners(); }; + public Exception getDataError() + { return dataError; } + public byte[] getBytes() throws IOException { + byte[] bytes = super.getBytes(); + CRC32 crc32 = new CRC32(); + crc32.update(bytes); + if (crc32.getValue() != entry.getCrc()) { + dataError = new IOException( + "CRC error while extracting entry from JAR file"); + } + return bytes; + } + }; + } + + /* + * Returns the URL for a resource with the specified name + */ + @Override + URL findResource(final String name, boolean check) { + Resource rsc = getResource(name, check); + if (rsc != null) { + return rsc.getURL(); + } + return null; + } + + /* + * Returns the JAR Resource for the specified name. + */ + @Override + Resource getResource(final String name, boolean check) { + try { + ensureOpen(); + } catch (IOException e) { + throw new InternalError(e); + } + final JarEntry entry = jar.getJarEntry(name); + if (entry != null) + return checkResource(name, check, entry); + + + return null; + } + + /* + * Returns the JAR file local class path, or null if none. + */ + @Override + URL[] getClassPath() throws IOException { + ensureOpen(); + + // Only get manifest when necessary + if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { + Manifest man = jar.getManifest(); + if (man != null) { + Attributes attr = man.getMainAttributes(); + if (attr != null) { + String value = attr.getValue(Name.CLASS_PATH); + if (value != null) { + return parseClassPath(csu, value); + } + } + } + } + return null; + } + + /* + * Parses value of the Class-Path manifest attribute and returns + * an array of URLs relative to the specified base URL. + */ + private static URL[] parseClassPath(URL base, String value) + throws MalformedURLException + { + StringTokenizer st = new StringTokenizer(value); + URL[] urls = new URL[st.countTokens()]; + int i = 0; + while (st.hasMoreTokens()) { + String path = st.nextToken(); + @SuppressWarnings("deprecation") + URL url = DISABLE_CP_URL_CHECK ? new URL(base, path) : tryResolve(base, path); + if (url != null) { + urls[i] = url; + i++; + } else { + if (DEBUG_CP_URL_CHECK) { + System.err.println("Class-Path entry: \"" + path + + "\" ignored in JAR file " + base); + } + } + } + if (i == 0) { + urls = null; + } else if (i != urls.length) { + // Truncate nulls from end of array + urls = Arrays.copyOf(urls, i); + } + return urls; + } + + static URL tryResolve(URL base, String input) throws MalformedURLException { + if ("file".equalsIgnoreCase(base.getProtocol())) { + return tryResolveFile(base, input); + } else { + return tryResolveNonFile(base, input); + } + } + + /** + * Attempt to return a file URL by resolving input against a base file + * URL. + * @return the resolved URL or null if the input is an absolute URL with + * a scheme other than file (ignoring case) + * @throws MalformedURLException + */ + static URL tryResolveFile(URL base, String input) throws MalformedURLException { + @SuppressWarnings("deprecation") + URL retVal = new URL(base, input); + if (input.indexOf(':') >= 0 && + !"file".equalsIgnoreCase(retVal.getProtocol())) { + // 'input' contains a ':', which might be a scheme, or might be + // a Windows drive letter. If the protocol for the resolved URL + // isn't "file:", it should be ignored. + return null; + } + return retVal; + } + + /** + * Attempt to return a URL by resolving input against a base URL. Returns + * null if the resolved URL is not contained by the base URL. + * + * @return the resolved URL or null + * @throws MalformedURLException + */ + static URL tryResolveNonFile(URL base, String input) throws MalformedURLException { + String child = input.replace(File.separatorChar, '/'); + if (isRelative(child)) { + @SuppressWarnings("deprecation") + URL url = new URL(base, child); + String bp = base.getPath(); + String urlp = url.getPath(); + int pos = bp.lastIndexOf('/'); + if (pos == -1) { + pos = bp.length() - 1; + } + if (urlp.regionMatches(0, bp, 0, pos + 1) + && urlp.indexOf("..", pos) == -1) { + return url; + } + } + return null; + } + + /** + * Returns true if the given input is a relative URI. + */ + static boolean isRelative(String child) { + try { + return !URI.create(child).isAbsolute(); + } catch (IllegalArgumentException e) { + return false; + } + } + } + + /* + * Nested class used to represent a loader of classes and resources + * from a file URL that refers to a directory. + */ + private static class FileLoader extends Loader { + /* Canonicalized File */ + private final File dir; + private final URL normalizedBase; + + /* + * Creates a new FileLoader for the specified URL with a file protocol. + */ + private FileLoader(URL url) throws IOException { + super(url); + String path = url.getFile().replace('/', File.separatorChar); + path = ParseUtil.decode(path); + dir = (new File(path)).getCanonicalFile(); + @SuppressWarnings("deprecation") + var _unused = normalizedBase = new URL(getBaseURL(), "."); + } + + /* + * Returns the URL for a resource with the specified name + */ + @Override + URL findResource(final String name, boolean check) { + Resource rsc = getResource(name, check); + if (rsc != null) { + return rsc.getURL(); + } + return null; + } + + @Override + Resource getResource(final String name, boolean check) { + final URL url; + try { + @SuppressWarnings("deprecation") + var _unused = url = new URL(getBaseURL(), ParseUtil.encodePath(name, false)); + + if (url.getFile().startsWith(normalizedBase.getFile()) == false) { + // requested resource had ../..'s in path + return null; + } + + if (check) + URLClassPath.check(url); + + final File file; + if (name.contains("..")) { + file = (new File(dir, name.replace('/', File.separatorChar))) + .getCanonicalFile(); + if ( !((file.getPath()).startsWith(dir.getPath())) ) { + /* outside of base dir */ + return null; + } + } else { + file = new File(dir, name.replace('/', File.separatorChar)); + } + + if (file.exists()) { + return new Resource() { + public String getName() { return name; }; + public URL getURL() { return url; }; + public URL getCodeSourceURL() { return getBaseURL(); }; + public InputStream getInputStream() throws IOException + { return new FileInputStream(file); }; + public int getContentLength() throws IOException + { return (int)file.length(); }; + }; + } + } catch (Exception e) { + return null; + } + return null; + } + } +} diff --git a/tests/test_data/std/jdk/internal/logger/AbstractLoggerWrapper.class b/tests/test_data/std/jdk/internal/logger/AbstractLoggerWrapper.class new file mode 100644 index 00000000..ca5b15cf Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/AbstractLoggerWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/logger/AbstractLoggerWrapper.java b/tests/test_data/std/jdk/internal/logger/AbstractLoggerWrapper.java new file mode 100644 index 00000000..95d35ed4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/AbstractLoggerWrapper.java @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.logger; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import sun.util.logging.PlatformLogger; + +/** + * An implementation of {@link System.Logger System.Logger} + * that redirects all calls to a wrapped instance of {@link + * System.Logger System.Logger} + * + * @param Type of the wrapped Logger: {@code Logger} or + * an extension of that interface. + * + */ +abstract class AbstractLoggerWrapper + implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { + + AbstractLoggerWrapper() { } + + abstract L wrapped(); + + abstract PlatformLogger.Bridge platformProxy(); + + L getWrapped() { + return wrapped(); + } + + @Override + public final String getName() { + return wrapped().getName(); + } + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + + @Override + public boolean isLoggable(Level level) { + return wrapped().isLoggable(level); + } + + @Override + public void log(Level level, String msg) { + wrapped().log(level, msg); + } + + @Override + public void log(Level level, + Supplier msgSupplier) { + wrapped().log(level, msgSupplier); + } + + @Override + public void log(Level level, Object obj) { + wrapped().log(level, obj); + } + + @Override + public void log(Level level, + String msg, Throwable thrown) { + wrapped().log(level, msg, thrown); + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + wrapped().log(level, msgSupplier, thrown); + } + + @Override + public void log(Level level, + String format, Object... params) { + wrapped().log(level, format, params); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String key, Throwable thrown) { + wrapped().log(level, bundle, key, thrown); + } + + @Override + public void log(Level level, ResourceBundle bundle, + String format, Object... params) { + wrapped().log(level, bundle, format, params); + } + + // --------------------------------------------------------- + // Methods from PlatformLogger.Bridge + // --------------------------------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) return isLoggable(level.systemLevel()); + else return platformProxy.isLoggable(level); + } + + @Override + public boolean isEnabled() { + final PlatformLogger.Bridge platformProxy = platformProxy(); + return platformProxy == null || platformProxy.isEnabled(); + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg); + } else { + platformProxy.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg, thrown); + } else { + platformProxy.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msg, params); + } else { + platformProxy.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(),msgSupplier); + } else { + platformProxy.log(level,msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), msgSupplier, thrown); + } else { + platformProxy.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + if (sourceClass == null && sourceMethod == null) { // best effort + wrapped().log(level.systemLevel(), msg); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg)); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msgSupplier); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + final String sClass = sourceClass == null ? "" : sourceClass; + final String sMethod = sourceMethod == null ? "" : sourceMethod; + wrapped.log(systemLevel, () -> String.format("[%s %s] %s", + sClass, sMethod, msgSupplier.get())); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msg, params); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), params); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msg, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), thrown); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), msgSupplier, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + final String sClass = sourceClass == null ? "" : sourceClass; + final String sMethod = sourceMethod == null ? "" : sourceMethod; + wrapped.log(systemLevel, () -> String.format("[%s %s] %s", + sClass, sMethod, msgSupplier.get()), thrown); + } + } + } else { + platformProxy.logp(level, sourceClass, sourceMethod, + thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, + String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (bundle != null || sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), bundle, msg, params); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, bundle, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), params); + } + } + } else { + platformProxy.logrb(level, sourceClass, sourceMethod, + bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { // best effort + if (bundle != null || sourceClass == null && sourceMethod == null) { + wrapped().log(level.systemLevel(), bundle, msg, thrown); + } else { + Level systemLevel = level.systemLevel(); + Logger wrapped = wrapped(); + if (wrapped.isLoggable(systemLevel)) { + sourceClass = sourceClass == null ? "" : sourceClass; + sourceMethod = sourceMethod == null ? "" : sourceMethod; + msg = msg == null ? "" : msg; + wrapped.log(systemLevel, bundle, String.format("[%s %s] %s", + sourceClass, sourceMethod, msg), thrown); + } + } + } else { + platformProxy.logrb(level, sourceClass, sourceMethod, bundle, + msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Throwable thrown) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), bundle, msg, thrown); + } else { + platformProxy.logrb(level, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + final PlatformLogger.Bridge platformProxy = platformProxy(); + if (platformProxy == null) { + wrapped().log(level.systemLevel(), bundle, msg, params); + } else { + platformProxy.logrb(level, bundle, msg, params); + } + } + + + @Override + public LoggerConfiguration getLoggerConfiguration() { + final PlatformLogger.Bridge platformProxy = platformProxy(); + return platformProxy == null ? null + : PlatformLogger.ConfigurableBridge + .getLoggerConfiguration(platformProxy); + } + +} diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors$1.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors$1.class new file mode 100644 index 00000000..244c4550 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors$1.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors$BootstrapMessageLoggerTask.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors$BootstrapMessageLoggerTask.class new file mode 100644 index 00000000..502d6dee Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors$BootstrapMessageLoggerTask.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors.class new file mode 100644 index 00000000..6c7fe269 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$BootstrapExecutors.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$DetectBackend$1.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$DetectBackend$1.class new file mode 100644 index 00000000..304c3e3f Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$DetectBackend$1.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$DetectBackend.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$DetectBackend.class new file mode 100644 index 00000000..83d47723 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$DetectBackend.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$LogEvent.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$LogEvent.class new file mode 100644 index 00000000..a84383e3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$LogEvent.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$LoggingBackend.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$LoggingBackend.class new file mode 100644 index 00000000..f8b2a684 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$LoggingBackend.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger$RedirectedLoggers.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$RedirectedLoggers.class new file mode 100644 index 00000000..f4888506 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger$RedirectedLoggers.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger.class b/tests/test_data/std/jdk/internal/logger/BootstrapLogger.class new file mode 100644 index 00000000..4bef7e08 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/BootstrapLogger.class differ diff --git a/tests/test_data/std/jdk/internal/logger/BootstrapLogger.java b/tests/test_data/std/jdk/internal/logger/BootstrapLogger.java new file mode 100644 index 00000000..f351b529 --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/BootstrapLogger.java @@ -0,0 +1,1105 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.logger; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import jdk.internal.misc.InnocuousThread; +import jdk.internal.misc.VM; +import sun.util.logging.PlatformLogger; +import jdk.internal.logger.LazyLoggers.LazyLoggerAccessor; + +/** + * The BootstrapLogger class handles all the logic needed by Lazy Loggers + * to delay the creation of System.Logger instances until the VM is booted. + * By extension - it also contains the logic that will delay the creation + * of JUL Loggers until the LogManager is initialized by the application, in + * the common case where JUL is the default and there is no custom JUL + * configuration. + * + * A BootstrapLogger instance is both a Logger and a + * PlatformLogger.Bridge instance, which will put all Log messages in a queue + * until the VM is booted. + * Once the VM is booted, it obtain the real System.Logger instance from the + * LoggerFinder and flushes the message to the queue. + * + * There are a few caveat: + * - the queue may not be flush until the next message is logged after + * the VM is booted + * - while the BootstrapLogger is active, the default implementation + * for all convenience methods is used + * - PlatformLogger.setLevel calls are ignored + * + * + */ +public final class BootstrapLogger implements Logger, PlatformLogger.Bridge, + PlatformLogger.ConfigurableBridge { + + // We use the BootstrapExecutors class to submit delayed messages + // to an independent InnocuousThread which will ensure that + // delayed log events will be clearly identified as messages that have + // been delayed during the boot sequence. + private static class BootstrapExecutors implements ThreadFactory { + + // Maybe that should be made configurable with system properties. + static final long KEEP_EXECUTOR_ALIVE_SECONDS = 30; + + // The BootstrapMessageLoggerTask is a Runnable which keeps + // a hard ref to the ExecutorService that owns it. + // This ensure that the ExecutorService is not gc'ed until the thread + // has stopped running. + private static class BootstrapMessageLoggerTask implements Runnable { + ExecutorService owner; + Runnable run; + public BootstrapMessageLoggerTask(ExecutorService owner, Runnable r) { + this.owner = owner; + this.run = r; + } + @Override + public void run() { + try { + run.run(); + } finally { + owner = null; // allow the ExecutorService to be gced. + } + } + } + + private static volatile WeakReference executorRef; + private static ExecutorService getExecutor() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (executor != null) return executor; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + if (executor == null) { + executor = new ThreadPoolExecutor(0, 1, + KEEP_EXECUTOR_ALIVE_SECONDS, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new BootstrapExecutors()); + } + // The executor service will be elligible for gc + // KEEP_EXECUTOR_ALIVE_SECONDS seconds (30s) + // after the execution of its last pending task. + executorRef = new WeakReference<>(executor); + return executorRef.get(); + } + } + + @Override + public Thread newThread(Runnable r) { + ExecutorService owner = getExecutor(); + @SuppressWarnings("removal") + Thread thread = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Thread run() { + Thread t = InnocuousThread.newThread(new BootstrapMessageLoggerTask(owner, r)); + t.setName("BootstrapMessageLoggerTask-"+t.getName()); + return t; + } + }, null, new RuntimePermission("enableContextClassLoaderOverride")); + thread.setDaemon(true); + return thread; + } + + static void submit(Runnable r) { + getExecutor().execute(r); + } + + // This is used by tests. + static void join(Runnable r) { + try { + getExecutor().submit(r).get(); + } catch (InterruptedException | ExecutionException ex) { + // should not happen + throw new RuntimeException(ex); + } + } + + // This is used by tests. + static void awaitPendingTasks() { + WeakReference ref = executorRef; + ExecutorService executor = ref == null ? null : ref.get(); + if (ref == null) { + synchronized(BootstrapExecutors.class) { + ref = executorRef; + executor = ref == null ? null : ref.get(); + } + } + if (executor != null) { + // since our executor uses a FIFO and has a single thread + // then awaiting the execution of its pending tasks can be done + // simply by registering a new task and waiting until it + // completes. This of course would not work if we were using + // several threads, but we don't. + join(()->{}); + } + } + + // This is used by tests. + static boolean isAlive() { + WeakReference ref = executorRef; + if (ref != null && !ref.refersTo(null)) return true; + synchronized (BootstrapExecutors.class) { + ref = executorRef; + return ref != null && !ref.refersTo(null); + } + } + + // The pending log event queue. The first event is the head, and + // new events are added at the tail + static LogEvent head, tail; + + static void enqueue(LogEvent event) { + if (event.next != null) return; + synchronized (BootstrapExecutors.class) { + if (event.next != null) return; + event.next = event; + if (tail == null) { + head = tail = event; + } else { + tail.next = event; + tail = event; + } + } + } + + static void flush() { + LogEvent event; + // drain the whole queue + synchronized(BootstrapExecutors.class) { + event = head; + head = tail = null; + } + while(event != null) { + LogEvent.log(event); + synchronized(BootstrapExecutors.class) { + LogEvent prev = event; + event = (event.next == event ? null : event.next); + prev.next = null; + } + } + } + } + + // The accessor in which this logger is temporarily set. + final LazyLoggerAccessor holder; + // tests whether the logger is invoked by the loading thread before + // the LoggerFinder is loaded; can be null; + final BooleanSupplier isLoadingThread; + + // returns true if the logger is invoked by the loading thread before the + // LoggerFinder service is loaded + boolean isLoadingThread() { + return isLoadingThread != null && isLoadingThread.getAsBoolean(); + } + + BootstrapLogger(LazyLoggerAccessor holder, BooleanSupplier isLoadingThread) { + this.holder = holder; + this.isLoadingThread = isLoadingThread; + } + + // Temporary data object storing log events + // It would be nice to use a Consumer instead of a LogEvent. + // This way we could simply do things like: + // push((logger) -> logger.log(level, msg)); + // Unfortunately, if we come to here it means we are in the bootsraping + // phase where using lambdas is not safe yet - so we have to use + // a data object instead... + // + static final class LogEvent { + // only one of these two levels should be non null + final Level level; + final PlatformLogger.Level platformLevel; + final BootstrapLogger bootstrap; + + final ResourceBundle bundle; + final String msg; + final Throwable thrown; + final Object[] params; + final Supplier msgSupplier; + final String sourceClass; + final String sourceMethod; + final long timeMillis; + final long nanoAdjustment; + + // because logging a message may entail calling toString() on + // the parameters etc... we need to store the context of the + // caller who logged the message - so that we can reuse it when + // we finally log the message. + @SuppressWarnings("removal") + final AccessControlContext acc; + + // The next event in the queue + LogEvent next; + + @SuppressWarnings("removal") + private LogEvent(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + this.bootstrap = bootstrap; + } + + @SuppressWarnings("removal") + private LogEvent(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = level; + this.platformLevel = null; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = null; + this.sourceMethod = null; + this.bootstrap = bootstrap; + } + + @SuppressWarnings("removal") + private LogEvent(BootstrapLogger bootstrap, + PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = bundle; + this.msg = msg; + this.msgSupplier = null; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + this.bootstrap = bootstrap; + } + + @SuppressWarnings("removal") + private LogEvent(BootstrapLogger bootstrap, + PlatformLogger.Level platformLevel, + String sourceClass, String sourceMethod, + Supplier msgSupplier, + Throwable thrown, Object[] params) { + this.acc = AccessController.getContext(); + this.timeMillis = System.currentTimeMillis(); + this.nanoAdjustment = VM.getNanoTimeAdjustment(timeMillis); + this.level = null; + this.platformLevel = platformLevel; + this.bundle = null; + this.msg = null; + this.msgSupplier = msgSupplier; + this.thrown = thrown; + this.params = params; + this.sourceClass = sourceClass; + this.sourceMethod = sourceMethod; + this.bootstrap = bootstrap; + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.log(LogEvent, logger) instead. + private void log(Logger logger) { + assert platformLevel == null && level != null; + //new Exception("logging delayed message").printStackTrace(); + if (msgSupplier != null) { + if (thrown != null) { + logger.log(level, msgSupplier, thrown); + } else { + logger.log(level, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.log(level, bundle, msg, thrown); + } else { + logger.log(level, bundle, msg, params); + } + } + } + + // Log this message in the given logger. Do not call directly. + // Use LogEvent.doLog(LogEvent, logger) instead. + private void log(PlatformLogger.Bridge logger) { + assert platformLevel != null && level == null; + if (sourceClass == null) { + if (msgSupplier != null) { + if (thrown != null) { + logger.log(platformLevel, thrown, msgSupplier); + } else { + logger.log(platformLevel, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, bundle, msg, params); + } + } + } else { + if (msgSupplier != null) { + if (thrown != null) { + logger.logp(platformLevel, sourceClass, sourceMethod, thrown, msgSupplier); + } else { + logger.logp(platformLevel, sourceClass, sourceMethod, msgSupplier); + } + } else { + // BootstrapLoggers are never localized so we can safely + // use the method that takes a ResourceBundle parameter + // even when that resource bundle is null. + if (thrown != null) { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, thrown); + } else { + logger.logrb(platformLevel, sourceClass, sourceMethod, bundle, msg, params); + } + } + } + } + + // non default methods from Logger interface + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String key, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), bundle, key, + thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + ResourceBundle bundle, String format, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), bundle, format, + null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), + Objects.requireNonNull(msgSupplier), thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, Level level, + Supplier msgSupplier) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), + Objects.requireNonNull(msgSupplier), null, null); + } + @SuppressWarnings("removal") + static void log(LogEvent log, Logger logger) { + final SecurityManager sm = System.getSecurityManager(); + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + if (sm == null || log.acc == null) { + BootstrapExecutors.submit(() -> log.log(logger)); + } else { + BootstrapExecutors.submit(() -> + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc)); + } + } + + // non default methods from PlatformLogger.Bridge interface + static LogEvent valueOf(BootstrapLogger bootstrap, + PlatformLogger.Level level, String msg) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, + msg, null, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String msg, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, msg, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String msg, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, null, msg, null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + Supplier msgSupplier) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, msgSupplier, null, null); + } + static LogEvent vaueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + Supplier msgSupplier, + Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), null, null, + msgSupplier, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Object[] params) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, bundle, msg, null, params); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, bundle, msg, thrown, null); + } + static LogEvent valueOf(BootstrapLogger bootstrap, PlatformLogger.Level level, + String sourceClass, String sourceMethod, + Supplier msgSupplier, Throwable thrown) { + return new LogEvent(Objects.requireNonNull(bootstrap), + Objects.requireNonNull(level), sourceClass, + sourceMethod, msgSupplier, thrown, null); + } + @SuppressWarnings("removal") + static void log(LogEvent log, PlatformLogger.Bridge logger) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null || log.acc == null) { + BootstrapExecutors.submit(() -> log.log(logger)); + } else { + // not sure we can actually use lambda here. We may need to create + // an anonymous class. Although if we reach here, then it means + // the VM is booted. + BootstrapExecutors.submit(() -> + AccessController.doPrivileged((PrivilegedAction) () -> { + log.log(logger); return null; + }, log.acc)); + } + } + + static void log(LogEvent event) { + event.bootstrap.flush(event); + } + + } + + // Push a log event at the end of the pending LogEvent queue. + void push(LogEvent log) { + BootstrapExecutors.enqueue(log); + // if the queue has been flushed just before we entered + // the synchronized block we need to flush it again. + checkBootstrapping(); + } + + // Flushes the queue of pending LogEvents to the logger. + void flush(LogEvent event) { + assert event.bootstrap == this; + if (event.platformLevel != null) { + PlatformLogger.Bridge concrete = holder.getConcretePlatformLogger(this); + LogEvent.log(event, concrete); + } else { + Logger concrete = holder.getConcreteLogger(this); + LogEvent.log(event, concrete); + } + } + + /** + * The name of this logger. This is the name of the actual logger for which + * this logger acts as a temporary proxy. + * @return The logger name. + */ + @Override + public String getName() { + return holder.name; + } + + /** + * Check whether the VM is still bootstrapping, and if not, arranges + * for this logger's holder to create the real logger and flush the + * pending event queue. + * @return true if the VM is still bootstrapping. + */ + boolean checkBootstrapping() { + if (isBooted() && !isLoadingThread()) { + BootstrapExecutors.flush(); + holder.getConcreteLogger(this); + return false; + } + return true; + } + + // ---------------------------------- + // Methods from Logger + // ---------------------------------- + + @Override + public boolean isLoggable(Level level) { + if (checkBootstrapping()) { + return level.getSeverity() >= Level.INFO.getSeverity(); + } else { + final Logger spi = holder.wrapped(); + return spi.isLoggable(level); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, bundle, key, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, key, thrown); + } + } + + @Override + public void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, bundle, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, bundle, format, params); + } + } + + @Override + public void log(Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, msg, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(Level level, String format, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, format, params)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, format, params); + } + } + + @Override + public void log(Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(Level level, Object obj) { + if (checkBootstrapping()) { + Logger.super.log(level, obj); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, obj); + } + } + + @Override + public void log(Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, msg, (Object[])null)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msg); + } + } + + @Override + public void log(Level level, Supplier msgSupplier, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier, thrown)); + } else { + final Logger spi = holder.wrapped(); + spi.log(level, msgSupplier, thrown); + } + } + + // ---------------------------------- + // Methods from PlatformLogger.Bridge + // ---------------------------------- + + @Override + public boolean isLoggable(PlatformLogger.Level level) { + if (checkBootstrapping()) { + return level.intValue() >= PlatformLogger.Level.INFO.intValue(); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return spi.isLoggable(level); + } + } + + @Override + public boolean isEnabled() { + if (checkBootstrapping()) { + return true; + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return spi.isEnabled(); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg, thrown); + } + } + + @Override + public void log(PlatformLogger.Level level, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msg, params); + } + } + + @Override + public void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, msgSupplier)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, msgSupplier); + } + } + + @Override + public void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.vaueOf(this, level, msgSupplier, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.log(level, thrown, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, + msg, (Object[])null)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, null)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msgSupplier); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, params); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, null, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, msg, thrown); + } + } + + @Override + public void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, msgSupplier, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logp(level, sourceClass, sourceMethod, thrown, msgSupplier); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, sourceClass, sourceMethod, bundle, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, sourceClass, sourceMethod, bundle, msg, thrown); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String msg, Object... params) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, null, bundle, msg, params)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, bundle, msg, params); + } + } + + @Override + public void logrb(PlatformLogger.Level level, ResourceBundle bundle, String msg, Throwable thrown) { + if (checkBootstrapping()) { + push(LogEvent.valueOf(this, level, null, null, bundle, msg, thrown)); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + spi.logrb(level, bundle, msg, thrown); + } + } + + @Override + public LoggerConfiguration getLoggerConfiguration() { + if (checkBootstrapping()) { + // This practically means that PlatformLogger.setLevel() + // calls will be ignored if the VM is still bootstrapping. We could + // attempt to fix that but is it worth it? + return PlatformLogger.ConfigurableBridge.super.getLoggerConfiguration(); + } else { + final PlatformLogger.Bridge spi = holder.platform(); + return PlatformLogger.ConfigurableBridge.getLoggerConfiguration(spi); + } + } + + // This BooleanSupplier is a hook for tests - so that we can simulate + // what would happen before the VM is booted. + private static volatile BooleanSupplier isBooted; + public static boolean isBooted() { + if (isBooted != null) return isBooted.getAsBoolean(); + else return VM.isBooted(); + } + + // A bit of magic. We try to find out the nature of the logging + // backend without actually loading it. + private static enum LoggingBackend { + // There is no LoggerFinder and JUL is not present + NONE(true), + + // There is no LoggerFinder, but we have found a + // JdkLoggerFinder installed (which means JUL is present), + // and we haven't found any custom configuration for JUL. + // Until LogManager is initialized we can use a simple console + // logger. + JUL_DEFAULT(false), + + // Same as above, except that we have found a custom configuration + // for JUL. We cannot use the simple console logger in this case. + JUL_WITH_CONFIG(true), + + // We have found a custom LoggerFinder. + CUSTOM(true); + + final boolean useLoggerFinder; + private LoggingBackend(boolean useLoggerFinder) { + this.useLoggerFinder = useLoggerFinder; + } + }; + + // The purpose of this class is to delay the initialization of + // the detectedBackend field until it is actually read. + // We do not want this field to get initialized if VM.isBooted() is false. + @SuppressWarnings("removal") + private static final class DetectBackend { + static final LoggingBackend detectedBackend; + static { + detectedBackend = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public LoggingBackend run() { + final Iterator iterator = + ServiceLoader.load(LoggerFinder.class, ClassLoader.getSystemClassLoader()) + .iterator(); + if (iterator.hasNext()) { + return LoggingBackend.CUSTOM; // Custom Logger Provider is registered + } + // No custom logger provider: we will be using the default + // backend. + final Iterator iterator2 = + ServiceLoader.loadInstalled(DefaultLoggerFinder.class) + .iterator(); + if (iterator2.hasNext()) { + // LoggingProviderImpl is registered. The default + // implementation is java.util.logging + String cname = System.getProperty("java.util.logging.config.class"); + String fname = System.getProperty("java.util.logging.config.file"); + return (cname != null || fname != null) + ? LoggingBackend.JUL_WITH_CONFIG + : LoggingBackend.JUL_DEFAULT; + } else { + // SimpleConsoleLogger is used + return LoggingBackend.NONE; + } + } + }); + + } + } + + // We will use a temporary SurrogateLogger if + // the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + private static boolean useSurrogateLoggers() { + // being paranoid: this should already have been checked + if (!isBooted()) return true; + return DetectBackend.detectedBackend == LoggingBackend.JUL_DEFAULT + && !logManagerConfigured; + } + + // We will use lazy loggers if: + // - the VM is not yet booted + // - the logging backend is a custom backend + // - the logging backend is JUL, there is no custom config, + // and the LogManager has not been initialized yet. + public static boolean useLazyLoggers() { + // Note: avoid triggering the initialization of the DetectBackend class + // while holding the BootstrapLogger class monitor + if (!BootstrapLogger.isBooted() || + DetectBackend.detectedBackend == LoggingBackend.CUSTOM) { + return true; + } + synchronized (BootstrapLogger.class) { + return useSurrogateLoggers(); + } + } + + // Called by LazyLoggerAccessor. This method will determine whether + // to create a BootstrapLogger (if the VM is not yet booted), + // a SurrogateLogger (if JUL is the default backend and there + // is no custom JUL configuration and LogManager is not yet initialized), + // or a logger returned by the loaded LoggerFinder (all other cases). + static Logger getLogger(LazyLoggerAccessor accessor, BooleanSupplier isLoading) { + if (!BootstrapLogger.isBooted() || isLoading != null && isLoading.getAsBoolean()) { + return new BootstrapLogger(accessor, isLoading); + } else { + if (useSurrogateLoggers()) { + // JUL is the default backend, there is no custom configuration, + // LogManager has not been used. + synchronized(BootstrapLogger.class) { + if (useSurrogateLoggers()) { + return createSurrogateLogger(accessor); + } + } + } + // Already booted. Return the real logger. + return accessor.createLogger(); + } + } + + // trigger class initialization outside of holding lock + static void ensureBackendDetected() { + assert VM.isBooted() : "VM is not booted"; + // triggers detection of the backend + var backend = DetectBackend.detectedBackend; + } + + // If the backend is JUL, and there is no custom configuration, and + // nobody has attempted to call LogManager.getLogManager() yet, then + // we can temporarily substitute JUL Logger with SurrogateLoggers, + // which avoids the cost of actually loading up the LogManager... + // The RedirectedLoggers class has the logic to create such surrogate + // loggers, and to possibly replace them with real JUL loggers if + // someone calls LogManager.getLogManager(). + static final class RedirectedLoggers implements + Function { + + // all accesses must be synchronized on the outer BootstrapLogger.class + final Map redirectedLoggers = + new HashMap<>(); + + // all accesses must be synchronized on the outer BootstrapLogger.class + // The redirectLoggers map will be cleared when LogManager is initialized. + boolean cleared; + + @Override + // all accesses must be synchronized on the outer BootstrapLogger.class + public SurrogateLogger apply(LazyLoggerAccessor t) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return SurrogateLogger.makeSurrogateLogger(t.getLoggerName()); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + SurrogateLogger get(LazyLoggerAccessor a) { + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + return redirectedLoggers.computeIfAbsent(a, this); + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + Map drainLoggersMap() { + if (redirectedLoggers.isEmpty()) return null; + if (cleared) throw new IllegalStateException("LoggerFinder already initialized"); + final Map accessors = new HashMap<>(redirectedLoggers); + redirectedLoggers.clear(); + cleared = true; + return accessors; + } + + static void replaceSurrogateLoggers(Map accessors) { + // When the backend is JUL we want to force the creation of + // JUL loggers here: some tests are expecting that the + // PlatformLogger will create JUL loggers as soon as the + // LogManager is initialized. + // + // If the backend is not JUL then we can delay the re-creation + // of the wrapped logger until they are next accessed. + // + final LoggingBackend detectedBackend = DetectBackend.detectedBackend; + final boolean lazy = detectedBackend != LoggingBackend.JUL_DEFAULT + && detectedBackend != LoggingBackend.JUL_WITH_CONFIG; + for (Map.Entry a : accessors.entrySet()) { + a.getKey().release(a.getValue(), !lazy); + } + } + + // all accesses must be synchronized on the outer BootstrapLogger.class + static final RedirectedLoggers INSTANCE = new RedirectedLoggers(); + } + + static synchronized Logger createSurrogateLogger(LazyLoggerAccessor a) { + // accesses to RedirectedLoggers is synchronized on BootstrapLogger.class + return RedirectedLoggers.INSTANCE.get(a); + } + + private static volatile boolean logManagerConfigured; + + private static synchronized Map + releaseSurrogateLoggers() { + // first check whether there's a chance that we have used + // surrogate loggers; Will be false if logManagerConfigured is already + // true. + final boolean releaseSurrogateLoggers = useSurrogateLoggers(); + + // then sets the flag that tells that the log manager is configured + logManagerConfigured = true; + + // finally retrieves all surrogate loggers that should be replaced + // by real JUL loggers, and return them in the form of a redirected + // loggers map. + if (releaseSurrogateLoggers) { + // accesses to RedirectedLoggers is synchronized on BootstrapLogger.class + return RedirectedLoggers.INSTANCE.drainLoggersMap(); + } else { + return null; + } + } + + public static void redirectTemporaryLoggers() { + // This call is synchronized on BootstrapLogger.class. + final Map accessors = + releaseSurrogateLoggers(); + + // We will now reset the logger accessors, triggering the + // (possibly lazy) replacement of any temporary surrogate logger by the + // real logger returned from the loaded LoggerFinder. + if (accessors != null) { + RedirectedLoggers.replaceSurrogateLoggers(accessors); + } + + BootstrapExecutors.flush(); + } + + // Hook for tests which need to wait until pending messages + // are processed. + static void awaitPendingTasks() { + BootstrapExecutors.awaitPendingTasks(); + } + static boolean isAlive() { + return BootstrapExecutors.isAlive(); + } + +} diff --git a/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder$1.class b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder$1.class new file mode 100644 index 00000000..b9250169 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder$1.class differ diff --git a/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder$SharedLoggers.class b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder$SharedLoggers.class new file mode 100644 index 00000000..0e811b94 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder$SharedLoggers.class differ diff --git a/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder.class b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder.class new file mode 100644 index 00000000..be2ffd5a Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder.class differ diff --git a/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder.java b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder.java new file mode 100644 index 00000000..1d077f15 --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/DefaultLoggerFinder.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.logger; + +import jdk.internal.misc.VM; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.Objects; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.ReferenceQueue; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.ResourceBundle; + +/** + * Internal Service Provider Interface (SPI) that makes it possible to use + * {@code java.util.logging} as backend when the {@link + * sun.util.logging.internal.LoggingProviderImpl + * sun.util.logging.internal.LoggingProviderImpl} is present. + *

+ * The JDK default implementation of the {@link LoggerFinder} will + * attempt to locate and load an {@linkplain + * java.util.ServiceLoader#loadInstalled(java.lang.Class) installed} + * implementation of the {@code DefaultLoggerFinder}. If {@code java.util.logging} + * is present, this will usually resolve to an instance of {@link + * sun.util.logging.internal.LoggingProviderImpl sun.util.logging.internal.LoggingProviderImpl}. + * Otherwise, if no concrete service provider is declared for + * {@code DefaultLoggerFinder}, the default implementation provided by this class + * will be used. + *

+ * When the {@link sun.util.logging.internal.LoggingProviderImpl + * sun.util.logging.internal.LoggingProviderImpl} is not present then the + * default implementation provided by this class is to use a simple logger + * that will log messages whose level is INFO and above to the console. + * These simple loggers are not configurable. + *

+ * When configuration is needed, an application should either link with + * {@code java.util.logging} - and use the {@code java.util.logging} for + * configuration, or link with {@link LoggerFinder another implementation} + * of the {@link LoggerFinder} + * that provides the necessary configuration. + * + * @apiNote Programmers are not expected to call this class directly. + * Instead they should rely on the static methods defined by {@link + * java.lang.System java.lang.System} or {@link sun.util.logging.PlatformLogger + * sun.util.logging.PlatformLogger}. + * + * @see java.lang.System.LoggerFinder + * @see jdk.internal.logger + * @see sun.util.logging.internal + * + */ +public class DefaultLoggerFinder extends LoggerFinder { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + /** + * Creates a new instance of DefaultLoggerFinder. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")} + */ + protected DefaultLoggerFinder() { + this(checkPermission()); + } + + private DefaultLoggerFinder(Void unused) { + // nothing to do. + } + + private static Void checkPermission() { + @SuppressWarnings("removal") + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return null; + } + + // SharedLoggers is a default cache of loggers used when JUL is not + // present - in that case we use instances of SimpleConsoleLogger which + // cannot be directly configure through public APIs. + // + // We can therefore afford to simply maintain two domains - one for the + // system, and one for the application. + // + static final class SharedLoggers { + private final Map> loggers = + new HashMap<>(); + private final ReferenceQueue queue = new ReferenceQueue<>(); + + synchronized Logger get(Function loggerSupplier, final String name) { + Reference ref = loggers.get(name); + Logger w = ref == null ? null : ref.get(); + if (w == null) { + w = loggerSupplier.apply(name); + loggers.put(name, new WeakReference<>(w, queue)); + } + + // Remove stale mapping... + Collection> values = null; + while ((ref = queue.poll()) != null) { + if (values == null) values = loggers.values(); + values.remove(ref); + } + return w; + } + + static final SharedLoggers system = new SharedLoggers(); + static final SharedLoggers application = new SharedLoggers(); + } + + @SuppressWarnings("removal") + public static boolean isSystem(Module m) { + return AccessController.doPrivileged(new PrivilegedAction<>() { + @Override + public Boolean run() { + // returns true if moduleCL is the platform class loader + // or one of its ancestors. + return VM.isSystemDomainLoader(m.getClassLoader()); + } + }); + } + + @Override + public final Logger getLogger(String name, Module module) { + Objects.requireNonNull(name, "name"); + Objects.requireNonNull(module, "module"); + checkPermission(); + return demandLoggerFor(name, module); + } + + @Override + public final Logger getLocalizedLogger(String name, ResourceBundle bundle, + Module module) { + return super.getLocalizedLogger(name, bundle, module); + } + + /** + * Returns a {@link Logger logger} suitable for use within the + * given {@code module}. + * + * @implSpec The default implementation for this method is to return a + * simple logger that will print all messages of INFO level and above + * to the console. That simple logger is not configurable. + * + * @param name The name of the logger. + * @param module The module on behalf of which the logger is created. + * @return A {@link Logger logger} suitable for the application usage. + * @throws SecurityException if the calling code does not have the + * {@code RuntimePermission("loggerFinder")}. + */ + protected Logger demandLoggerFor(String name, Module module) { + checkPermission(); + if (isSystem(module)) { + return SharedLoggers.system.get(SimpleConsoleLogger::makeSimpleLogger, name); + } else { + return SharedLoggers.application.get(SimpleConsoleLogger::makeSimpleLogger, name); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers$1.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers$1.class new file mode 100644 index 00000000..76511ce7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers$1.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers$JdkLazyLogger.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers$JdkLazyLogger.class new file mode 100644 index 00000000..be6d80b5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers$JdkLazyLogger.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerAccessor.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerAccessor.class new file mode 100644 index 00000000..405ccf29 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerAccessor.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerFactories.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerFactories.class new file mode 100644 index 00000000..df96b068 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerFactories.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerWrapper.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerWrapper.class new file mode 100644 index 00000000..2a65b1f1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LazyLoggerWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers$LoggerAccessor.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LoggerAccessor.class new file mode 100644 index 00000000..03058b70 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers$LoggerAccessor.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers.class b/tests/test_data/std/jdk/internal/logger/LazyLoggers.class new file mode 100644 index 00000000..e7874df4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LazyLoggers.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LazyLoggers.java b/tests/test_data/std/jdk/internal/logger/LazyLoggers.java new file mode 100644 index 00000000..2c624962 --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/LazyLoggers.java @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.logger; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.BiFunction; +import java.lang.System.LoggerFinder; +import java.lang.System.Logger; +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.function.BooleanSupplier; + +import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder; +import jdk.internal.misc.VM; +import sun.util.logging.PlatformLogger; + +/** + * This class is a factory for Lazy Loggers; only system loggers can be + * Lazy Loggers. + */ +public final class LazyLoggers { + + static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + private LazyLoggers() { + throw new InternalError(); + } + + /** + * This class is used to hold the factories that a Lazy Logger will use + * to create (or map) its wrapped logger. + * @param {@link Logger} or a subclass of {@link Logger}. + */ + private static final class LazyLoggerFactories { + + /** + * A factory method to create an SPI logger. + * Usually, this will be something like LazyLoggers::getSystemLogger. + */ + final BiFunction loggerSupplier; + + + public LazyLoggerFactories(BiFunction loggerSupplier) { + this(Objects.requireNonNull(loggerSupplier), + (Void)null); + } + + private LazyLoggerFactories(BiFunction loggerSupplier, + Void unused) { + this.loggerSupplier = loggerSupplier; + } + + } + + static interface LoggerAccessor { + /** + * The logger name. + * @return The name of the logger that is / will be lazily created. + */ + public String getLoggerName(); + + /** + * Returns the wrapped logger object. + * @return the wrapped logger object. + */ + public Logger wrapped(); + + /** + * A PlatformLogger.Bridge view of the wrapped logger object. + * @return A PlatformLogger.Bridge view of the wrapped logger object. + */ + public PlatformLogger.Bridge platform(); + } + + /** + * The LazyLoggerAccessor class holds all the logic that delays the creation + * of the SPI logger until such a time that the VM is booted and the logger + * is actually used for logging. + * + * This class uses the services of the BootstrapLogger class to instantiate + * temporary loggers if appropriate. + */ + static final class LazyLoggerAccessor implements LoggerAccessor { + + // The factories that will be used to create the logger lazyly + final LazyLoggerFactories factories; + + // We need to pass the actual caller module when creating the logger. + private final WeakReference moduleRef; + + // whether this is the loading thread, can be null + private final BooleanSupplier isLoadingThread; + + // The name of the logger that will be created lazyly + final String name; + // The plain logger SPI object - null until it is accessed for the + // first time. + private volatile Logger w; + // A PlatformLogger.Bridge view of w. + private volatile PlatformLogger.Bridge p; + + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Module module) { + this(name, factories, module, null); + } + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Module module, BooleanSupplier isLoading) { + + this(Objects.requireNonNull(name), Objects.requireNonNull(factories), + Objects.requireNonNull(module), isLoading, null); + } + + private LazyLoggerAccessor(String name, + LazyLoggerFactories factories, + Module module, BooleanSupplier isLoading, Void unused) { + this.name = name; + this.factories = factories; + this.moduleRef = new WeakReference<>(module); + this.isLoadingThread = isLoading; + } + + /** + * The logger name. + * @return The name of the logger that is / will be lazily created. + */ + @Override + public String getLoggerName() { + return name; + } + + // must be called in synchronized block + // set wrapped logger if not set + private void setWrappedIfNotSet(Logger wrapped) { + if (w == null) { + w = wrapped; + } + } + + /** + * Returns the logger SPI object, creating it if 'w' is still null. + * @return the logger SPI object. + */ + public Logger wrapped() { + Logger wrapped = w; + if (wrapped != null) return wrapped; + // Wrapped logger not created yet: create it. + // BootstrapLogger has the logic to decide whether to invoke the + // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger) + // logger. + wrapped = BootstrapLogger.getLogger(this, isLoadingThread); + synchronized(this) { + // if w has already been in between, simply drop 'wrapped'. + setWrappedIfNotSet(wrapped); + return w; + } + } + + /** + * A PlatformLogger.Bridge view of the wrapped logger. + * @return A PlatformLogger.Bridge view of the wrapped logger. + */ + public PlatformLogger.Bridge platform() { + // We can afford to return the platform view of the previous + // logger - if that view is not null. + // Because that view will either be the BootstrapLogger, which + // will redirect to the new wrapper properly, or the temporary + // logger - which in effect is equivalent to logging something + // just before the application initialized LogManager. + PlatformLogger.Bridge platform = p; + if (platform != null) return platform; + synchronized (this) { + if (w != null) { + if (p == null) p = PlatformLogger.Bridge.convert(w); + return p; + } + } + // If we reach here it means that the wrapped logger may not + // have been created yet: attempt to create it. + // BootstrapLogger has the logic to decide whether to invoke the + // SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger) + // logger. + final Logger wrapped = BootstrapLogger.getLogger(this, isLoadingThread); + synchronized(this) { + // if w has already been set, simply drop 'wrapped'. + setWrappedIfNotSet(wrapped); + if (p == null) p = PlatformLogger.Bridge.convert(w); + return p; + } + } + + /** + * Makes this accessor release a temporary logger. + * This method is called + * by BootstrapLogger when JUL is the default backend and LogManager + * is initialized, in order to replace temporary SimpleConsoleLoggers by + * real JUL loggers. See BootstrapLogger for more details. + * If {@code replace} is {@code true}, then this method will force + * the accessor to eagerly recreate its wrapped logger. + * Note: passing {@code replace=false} is no guarantee that the + * method will not actually replace the released logger. + * @param temporary The temporary logger too be released. + * @param replace Whether the released logger should be eagerly + * replaced. + */ + void release(SimpleConsoleLogger temporary, boolean replace) { + PlatformLogger.ConfigurableBridge.LoggerConfiguration conf = + PlatformLogger.ConfigurableBridge.getLoggerConfiguration(temporary); + PlatformLogger.Level level = conf != null + ? conf.getPlatformLevel() + : null; + synchronized (this) { + if (this.w == temporary) { + this.w = null; this.p = null; + } + } + PlatformLogger.Bridge platform = replace || level != null + ? this.platform() : null; + + if (level != null) { + conf = (platform != null && platform != temporary) + ? PlatformLogger.ConfigurableBridge.getLoggerConfiguration(platform) + : null; + if (conf != null) conf.setPlatformLevel(level); + } + } + + /** + * Replace 'w' by the real SPI logger and flush the log messages pending + * in the temporary 'bootstrap' Logger. Called by BootstrapLogger when + * this accessor's bootstrap logger is accessed and BootstrapLogger + * notices that the VM is no longer booting. + * @param bootstrap This accessor's bootstrap logger (usually this is 'w'). + */ + Logger getConcreteLogger(BootstrapLogger bootstrap) { + assert VM.isBooted(); + synchronized(this) { + // another thread may have already invoked flush() + if (this.w == bootstrap) { + this.w = null; this.p = null; + } + } + return this.wrapped(); + } + + PlatformLogger.Bridge getConcretePlatformLogger(BootstrapLogger bootstrap) { + assert VM.isBooted(); + synchronized(this) { + // another thread may have already invoked flush() + if (this.w == bootstrap) { + this.w = null; this.p = null; + } + } + return this.platform(); + } + + // Creates the wrapped logger by invoking the SPI. + Logger createLogger() { + final Module module = moduleRef.get(); + if (module == null) { + throw new IllegalStateException("The module for which this logger" + + " was created has been garbage collected"); + } + return this.factories.loggerSupplier.apply(name, module); + } + + /** + * Creates a new lazy logger accessor for the named logger. The given + * factories will be use when it becomes necessary to actually create + * the logger. + * @param name The logger name. + * @param factories The factories that should be used to create the + * wrapped logger. + * @param module The module for which the logger is being created + * @return A new LazyLoggerAccessor. + */ + public static LazyLoggerAccessor makeAccessor(String name, + LazyLoggerFactories factories, Module module) { + return new LazyLoggerAccessor(name, factories, module); + } + + } + + /** + * An implementation of {@link Logger} that redirects all calls to a wrapped + * instance of {@code Logger}. + */ + private static class LazyLoggerWrapper + extends AbstractLoggerWrapper { + + final LoggerAccessor loggerAccessor; + + public LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier) { + this(Objects.requireNonNull(loggerSinkSupplier), (Void)null); + } + + private LazyLoggerWrapper(LazyLoggerAccessor loggerSinkSupplier, + Void unused) { + this.loggerAccessor = loggerSinkSupplier; + } + + @Override + final Logger wrapped() { + return loggerAccessor.wrapped(); + } + + @Override + PlatformLogger.Bridge platformProxy() { + return loggerAccessor.platform(); + } + + } + + // Do not expose this outside of this package. + private static volatile LoggerFinder provider; + @SuppressWarnings("removal") + private static LoggerFinder accessLoggerFinder() { + LoggerFinder prov = provider; + if (prov == null) { + // no need to lock: it doesn't matter if we call + // getLoggerFinder() twice - since LoggerFinder already caches + // the result. + // This is just an optimization to avoid the cost of calling + // doPrivileged every time. + final SecurityManager sm = System.getSecurityManager(); + prov = sm == null ? LoggerFinder.getLoggerFinder() : + AccessController.doPrivileged( + (PrivilegedAction)LoggerFinder::getLoggerFinder); + if (prov instanceof TemporaryLoggerFinder) return prov; + provider = prov; + } + return prov; + } + + // Avoid using lambda here as lazy loggers could be created early + // in the bootstrap sequence... + private static final BiFunction loggerSupplier = + new BiFunction<>() { + @Override + public Logger apply(String name, Module module) { + return LazyLoggers.getLoggerFromFinder(name, module); + } + }; + + private static final LazyLoggerFactories factories = + new LazyLoggerFactories<>(loggerSupplier); + + + // A concrete implementation of Logger that delegates to a System.Logger, + // but only creates the System.Logger instance lazily when it's used for + // the first time. + // The JdkLazyLogger uses a LazyLoggerAccessor objects, which relies + // on the logic embedded in BootstrapLogger to avoid loading the concrete + // logger provider until the VM has finished booting. + // + private static final class JdkLazyLogger extends LazyLoggerWrapper { + JdkLazyLogger(String name, Module module) { + this(LazyLoggerAccessor.makeAccessor(name, factories, module), + (Void)null); + } + private JdkLazyLogger(LazyLoggerAccessor holder, Void unused) { + super(holder); + } + } + + static Logger makeLazyLogger(String name, Module module, BooleanSupplier isLoading) { + final LazyLoggerAccessor holder = new LazyLoggerAccessor(name, factories, module, isLoading); + return new JdkLazyLogger(holder, null); + } + + /** + * Gets a logger from the LoggerFinder. Creates the actual concrete + * logger. + * @param name name of the logger + * @param module module on behalf of which the logger is created + * @return The logger returned by the LoggerFinder. + */ + @SuppressWarnings("removal") + static Logger getLoggerFromFinder(String name, Module module) { + final SecurityManager sm = System.getSecurityManager(); + if (sm == null) { + return accessLoggerFinder().getLogger(name, module); + } else { + return AccessController.doPrivileged((PrivilegedAction) + () -> {return accessLoggerFinder().getLogger(name, module);}, + null, LOGGERFINDER_PERMISSION); + } + } + + /** + * Returns a (possibly lazy) Logger for the caller. + * + * @param name the logger name + * @param module The module on behalf of which the logger is created. + * If the module is not loaded from the Boot ClassLoader, + * the LoggerFinder is accessed and the logger returned + * by {@link LoggerFinder#getLogger(java.lang.String, java.lang.Module)} + * is returned to the caller directly. + * Otherwise, the logger returned by + * {@link #getLazyLogger(java.lang.String, java.lang.Module)} + * is returned to the caller. + * + * @return a (possibly lazy) Logger instance. + */ + public static final Logger getLogger(String name, Module module) { + if (DefaultLoggerFinder.isSystem(module)) { + return getLazyLogger(name, module); + } else { + return getLoggerFromFinder(name, module); + } + } + + /** + * Returns a (possibly lazy) Logger suitable for system classes. + * Whether the returned logger is lazy or not depend on the result + * returned by {@link BootstrapLogger#useLazyLoggers()}. + * + * @param name the logger name + * @param module the module on behalf of which the logger is created. + * @return a (possibly lazy) Logger instance. + */ + public static final Logger getLazyLogger(String name, Module module) { + + // BootstrapLogger has the logic to determine whether a LazyLogger + // should be used. Usually, it is worth it only if: + // - the VM is not yet booted + // - or, the backend is JUL and there is no configuration + // - or, the backend is a custom backend, as we don't know what + // that is going to load... + // So if for instance the VM is booted and we use JUL with a custom + // configuration, we're not going to delay the creation of loggers... + final boolean useLazyLogger = BootstrapLogger.useLazyLoggers(); + if (useLazyLogger) { + return new JdkLazyLogger(name, module); + } else { + // Directly invoke the LoggerFinder. + return getLoggerFromFinder(name, module); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/logger/LocalizedLoggerWrapper.class b/tests/test_data/std/jdk/internal/logger/LocalizedLoggerWrapper.class new file mode 100644 index 00000000..7c3d645c Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LocalizedLoggerWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LocalizedLoggerWrapper.java b/tests/test_data/std/jdk/internal/logger/LocalizedLoggerWrapper.java new file mode 100644 index 00000000..361ebc30 --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/LocalizedLoggerWrapper.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package jdk.internal.logger; + +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.lang.System.Logger; +import java.lang.System.Logger.Level; + +/** + * This implementation of {@link Logger} redirects all logging method + * calls to calls to {@code log(Level, String, ResourceBundle, ...)} + * methods, passing the Logger's ResourceBundle as parameter. + * So for instance a call to {@link Logger#log(Level, String) + * log(Level.INFO, msg)} will be redirected + * to a call to {@link #log(java.lang.System.Logger.Level, + * java.util.ResourceBundle, java.lang.String, java.lang.Object...) + * this.log(Level.INFO, this.bundle, msg, (Object[]) null)}. + *

+ * Note that methods that take a {@link Supplier Supplier<String>} + * or an Object are not redirected. It is assumed that a string returned + * by a {@code Supplier} is already localized, or cannot be localized. + * + * @param Type of the wrapped Logger: {@code Logger} or an + * extension of the {@code Logger} interface. + */ +public class LocalizedLoggerWrapper extends LoggerWrapper { + + private final ResourceBundle bundle; + + public LocalizedLoggerWrapper(L wrapped, ResourceBundle bundle) { + super(wrapped); + this.bundle = bundle; + } + + public final ResourceBundle getBundle() { + return bundle; + } + + // We assume that messages returned by Supplier and Object are + // either already localized or not localizable. To be evaluated. + + // ----------------------------------------------------------------- + // Generic methods taking a Level as parameter + // ----------------------------------------------------------------- + + @Override + public final void log(Level level, String msg) { + log(level, bundle, msg, (Object[]) null); + } + + @Override + public final void log(Level level, + String msg, Throwable thrown) { + log(level, bundle, msg, thrown); + } + + @Override + public final void log(Level level, + String format, Object... params) { + log(level, bundle, format, params); + } + + @Override + public final void log(Level level, Object obj) { + wrapped.log(level, obj); + } + + @Override + public final void log(Level level, Supplier msgSupplier) { + wrapped.log(level, msgSupplier); + } + + @Override + public final void log(Level level, Supplier msgSupplier, Throwable thrown) { + wrapped.log(level, msgSupplier, thrown); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String format, Object... params) { + wrapped.log(level, bundle, format, params); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + wrapped.log(level, bundle, key, thrown); + } + + @Override + public final boolean isLoggable(Level level) { + return wrapped.isLoggable(level); + } + + // Override methods from PlatformLogger.Bridge that don't take a + // resource bundle... + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key) { + logrb(level, sourceClass, sourceMethod, bundle, key, (Object[]) null); + } + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key, Throwable thrown) { + logrb(level, sourceClass, sourceMethod, bundle, key, thrown); + } + + @Override + public final void logp(sun.util.logging.PlatformLogger.Level level, String sourceClass, String sourceMethod, + String key, Object... params) { + logrb(level, sourceClass, sourceMethod, bundle, key, params); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String msg, Throwable thrown) { + logrb(level, bundle, msg, thrown); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String msg) { + logrb(level, bundle, msg, (Object[]) null); + } + + @Override + public final void log(sun.util.logging.PlatformLogger.Level level, String format, Object... params) { + logrb(level, bundle, format, params); + } + + +} diff --git a/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$ErrorPolicy.class b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$ErrorPolicy.class new file mode 100644 index 00000000..21ec17c8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$ErrorPolicy.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$TemporaryLoggerFinder$1.class b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$TemporaryLoggerFinder$1.class new file mode 100644 index 00000000..6747d592 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$TemporaryLoggerFinder$1.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$TemporaryLoggerFinder.class b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$TemporaryLoggerFinder.class new file mode 100644 index 00000000..da2c877f Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader$TemporaryLoggerFinder.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader.class b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader.class new file mode 100644 index 00000000..fb3f8ef3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader.java b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader.java new file mode 100644 index 00000000..932de4ef --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/LoggerFinderLoader.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.logger; + +import java.io.FilePermission; +import java.lang.System.Logger; +import java.lang.System.LoggerFinder; +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.util.Iterator; +import java.util.Locale; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.function.BooleanSupplier; + +import jdk.internal.vm.annotation.Stable; +import sun.security.util.SecurityConstants; +import sun.security.action.GetBooleanAction; +import sun.security.action.GetPropertyAction; + +/** + * Helper class used to load the {@link java.lang.System.LoggerFinder}. + */ +public final class LoggerFinderLoader { + private static volatile System.LoggerFinder service; + private static final Object lock = new int[0]; + static final Permission CLASSLOADER_PERMISSION = + SecurityConstants.GET_CLASSLOADER_PERMISSION; + static final Permission READ_PERMISSION = + new FilePermission("<>", + SecurityConstants.FILE_READ_ACTION); + public static final RuntimePermission LOGGERFINDER_PERMISSION = + new RuntimePermission("loggerFinder"); + + // This is used to control how the LoggerFinderLoader handles + // errors when instantiating the LoggerFinder provider. + // ERROR => throws ServiceConfigurationError + // WARNING => Do not fail, use plain default (simple logger) implementation, + // prints warning on console. (this is the default) + // DEBUG => Do not fail, use plain default (simple logger) implementation, + // prints warning and exception stack trace on console. + // QUIET => Do not fail and stay silent. + private static enum ErrorPolicy { ERROR, WARNING, DEBUG, QUIET }; + + // This class is static and cannot be instantiated. + private LoggerFinderLoader() { + throw new InternalError("LoggerFinderLoader cannot be instantiated"); + } + + // record the loadingThread while loading the backend + static volatile Thread loadingThread; + // Return the loaded LoggerFinder, or load it if not already loaded. + private static System.LoggerFinder service() { + if (service != null) return service; + // ensure backend is detected before attempting to load the finder + BootstrapLogger.ensureBackendDetected(); + synchronized(lock) { + if (service != null) return service; + Thread currentThread = Thread.currentThread(); + if (loadingThread == currentThread) { + // recursive attempt to load the backend while loading the backend + // use a temporary logger finder that returns special BootstrapLogger + // which will wait until loading is finished + return TemporaryLoggerFinder.INSTANCE; + } + loadingThread = currentThread; + try { + service = loadLoggerFinder(); + } finally { + loadingThread = null; + } + } + // Since the LoggerFinder is already loaded - we can stop using + // temporary loggers. + BootstrapLogger.redirectTemporaryLoggers(); + return service; + } + + // returns true if called by the thread that loads the LoggerFinder, while + // loading the LoggerFinder. + static boolean isLoadingThread() { + return loadingThread != null && loadingThread == Thread.currentThread(); + } + + // Get configuration error policy + private static ErrorPolicy configurationErrorPolicy() { + String errorPolicy = + GetPropertyAction.privilegedGetProperty("jdk.logger.finder.error"); + if (errorPolicy == null || errorPolicy.isEmpty()) { + return ErrorPolicy.WARNING; + } + try { + return ErrorPolicy.valueOf(errorPolicy.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException x) { + return ErrorPolicy.WARNING; + } + } + + // Whether multiple provider should be considered as an error. + // This is further submitted to the configuration error policy. + private static boolean ensureSingletonProvider() { + return GetBooleanAction.privilegedGetProperty + ("jdk.logger.finder.singleton"); + } + + @SuppressWarnings("removal") + private static Iterator findLoggerFinderProviders() { + final Iterator iterator; + if (System.getSecurityManager() == null) { + iterator = ServiceLoader.load(System.LoggerFinder.class, + ClassLoader.getSystemClassLoader()).iterator(); + } else { + final PrivilegedAction> pa = + () -> ServiceLoader.load(System.LoggerFinder.class, + ClassLoader.getSystemClassLoader()).iterator(); + iterator = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION, + READ_PERMISSION); + } + return iterator; + } + + public static final class TemporaryLoggerFinder extends LoggerFinder { + private TemporaryLoggerFinder() {} + @Stable + private LoggerFinder loadedService; + + private static final BooleanSupplier isLoadingThread = new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + return LoggerFinderLoader.isLoadingThread(); + } + }; + private static final TemporaryLoggerFinder INSTANCE = new TemporaryLoggerFinder(); + + @Override + public Logger getLogger(String name, Module module) { + if (loadedService == null) { + loadedService = service; + if (loadedService == null) { + return LazyLoggers.makeLazyLogger(name, module, isLoadingThread); + } + } + assert loadedService != null; + assert !LoggerFinderLoader.isLoadingThread(); + assert loadedService != this; + return LazyLoggers.getLogger(name, module); + } + } + + // Loads the LoggerFinder using ServiceLoader. If no LoggerFinder + // is found returns the default (possibly JUL based) implementation + private static System.LoggerFinder loadLoggerFinder() { + System.LoggerFinder result; + try { + // Iterator iterates with the access control context stored + // at ServiceLoader creation time. + final Iterator iterator = + findLoggerFinderProviders(); + if (iterator.hasNext()) { + result = iterator.next(); + if (iterator.hasNext() && ensureSingletonProvider()) { + throw new ServiceConfigurationError( + "More than one LoggerFinder implementation"); + } + } else { + result = loadDefaultImplementation(); + } + } catch (Error | RuntimeException x) { + // next caller will get the plain default impl (not linked + // to java.util.logging) + service = result = new DefaultLoggerFinder(); + ErrorPolicy errorPolicy = configurationErrorPolicy(); + if (errorPolicy == ErrorPolicy.ERROR) { + // rethrow any exception as a ServiceConfigurationError. + if (x instanceof Error) { + throw x; + } else { + throw new ServiceConfigurationError( + "Failed to instantiate LoggerFinder provider; Using default.", x); + } + } else if (errorPolicy != ErrorPolicy.QUIET) { + // if QUIET just silently use the plain default impl + // otherwise, log a warning, possibly adding the exception + // stack trace (if DEBUG is specified). + SimpleConsoleLogger logger = + new SimpleConsoleLogger("jdk.internal.logger", false); + logger.log(System.Logger.Level.WARNING, + "Failed to instantiate LoggerFinder provider; Using default."); + if (errorPolicy == ErrorPolicy.DEBUG) { + logger.log(System.Logger.Level.WARNING, + "Exception raised trying to instantiate LoggerFinder", x); + } + } + } + return result; + } + + @SuppressWarnings("removal") + private static System.LoggerFinder loadDefaultImplementation() { + final SecurityManager sm = System.getSecurityManager(); + final Iterator iterator; + if (sm == null) { + iterator = ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator(); + } else { + // We use limited do privileged here - the minimum set of + // permissions required to 'see' the META-INF/services resources + // seems to be CLASSLOADER_PERMISSION and READ_PERMISSION. + // Note that do privileged is required because + // otherwise the SecurityManager will prevent the ServiceLoader + // from seeing the installed provider. + PrivilegedAction> pa = () -> + ServiceLoader.loadInstalled(DefaultLoggerFinder.class).iterator(); + iterator = AccessController.doPrivileged(pa, null, + LOGGERFINDER_PERMISSION, CLASSLOADER_PERMISSION, + READ_PERMISSION); + } + DefaultLoggerFinder result = null; + try { + // Iterator iterates with the access control context stored + // at ServiceLoader creation time. + if (iterator.hasNext()) { + result = iterator.next(); + } + } catch (RuntimeException x) { + throw new ServiceConfigurationError( + "Failed to instantiate default LoggerFinder", x); + } + if (result == null) { + result = new DefaultLoggerFinder(); + } + return result; + } + + public static System.LoggerFinder getLoggerFinder() { + @SuppressWarnings("removal") + final SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(LOGGERFINDER_PERMISSION); + } + return service(); + } + +} diff --git a/tests/test_data/std/jdk/internal/logger/LoggerWrapper.class b/tests/test_data/std/jdk/internal/logger/LoggerWrapper.class new file mode 100644 index 00000000..e3fe71fc Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/LoggerWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/logger/LoggerWrapper.java b/tests/test_data/std/jdk/internal/logger/LoggerWrapper.java new file mode 100644 index 00000000..09ba3a50 --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/LoggerWrapper.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package jdk.internal.logger; + +import java.util.Objects; +import java.lang.System.Logger; +import sun.util.logging.PlatformLogger; + +/** + * An implementation of {@link Logger} that redirects all calls to a wrapped + * instance of Logger. + * + * @param Type of the wrapped Logger: {@code Logger} or an + * extension of that interface. + */ +public class LoggerWrapper extends AbstractLoggerWrapper { + + final L wrapped; + final PlatformLogger.Bridge platformProxy; + + public LoggerWrapper(L wrapped) { + this(Objects.requireNonNull(wrapped), (Void)null); + } + + LoggerWrapper(L wrapped, Void unused) { + this.wrapped = wrapped; + this.platformProxy = (wrapped instanceof PlatformLogger.Bridge) ? + (PlatformLogger.Bridge) wrapped : null; + } + + @Override + public final L wrapped() { + return wrapped; + } + + @Override + public final PlatformLogger.Bridge platformProxy() { + return platformProxy; + } + +} diff --git a/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$CallerFinder$1.class b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$CallerFinder$1.class new file mode 100644 index 00000000..413d797d Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$CallerFinder$1.class differ diff --git a/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$CallerFinder.class b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$CallerFinder.class new file mode 100644 index 00000000..9e7fa864 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$CallerFinder.class differ diff --git a/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$Formatting.class b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$Formatting.class new file mode 100644 index 00000000..a23b3107 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger$Formatting.class differ diff --git a/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger.class b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger.class new file mode 100644 index 00000000..08b15465 Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger.class differ diff --git a/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger.java b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger.java new file mode 100644 index 00000000..317e475d --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/SimpleConsoleLogger.java @@ -0,0 +1,557 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.logger; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.StackWalker.StackFrame; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.function.Function; +import java.lang.System.Logger; +import java.util.function.Predicate; +import java.util.function.Supplier; +import sun.security.action.GetPropertyAction; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLogger.ConfigurableBridge.LoggerConfiguration; + +/** + * A simple console logger to emulate the behavior of JUL loggers when + * in the default configuration. SimpleConsoleLoggers are also used when + * JUL is not present and no DefaultLoggerFinder is installed. + */ +public class SimpleConsoleLogger extends LoggerConfiguration + implements Logger, PlatformLogger.Bridge, PlatformLogger.ConfigurableBridge { + + static final Level DEFAULT_LEVEL = getDefaultLevel(); + static final PlatformLogger.Level DEFAULT_PLATFORM_LEVEL = + PlatformLogger.toPlatformLevel(DEFAULT_LEVEL); + + static Level getDefaultLevel() { + String levelName = GetPropertyAction + .privilegedGetProperty("jdk.system.logger.level", "INFO"); + try { + return Level.valueOf(levelName); + } catch (IllegalArgumentException iae) { + return Level.INFO; + } + } + + final String name; + volatile PlatformLogger.Level level; + final boolean usePlatformLevel; + SimpleConsoleLogger(String name, boolean usePlatformLevel) { + this.name = name; + this.usePlatformLevel = usePlatformLevel; + } + + String getSimpleFormatString() { + return Formatting.SIMPLE_CONSOLE_LOGGER_FORMAT; + } + + PlatformLogger.Level defaultPlatformLevel() { + return DEFAULT_PLATFORM_LEVEL; + } + + @Override + public final String getName() { + return name; + } + + private Enum logLevel(PlatformLogger.Level level) { + return usePlatformLevel ? level : level.systemLevel(); + } + + private Enum logLevel(Level level) { + return usePlatformLevel ? PlatformLogger.toPlatformLevel(level) : level; + } + + // --------------------------------------------------- + // From Logger + // --------------------------------------------------- + + @Override + public final boolean isLoggable(Level level) { + return isLoggable(PlatformLogger.toPlatformLevel(level)); + } + + @Override + public final void log(Level level, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + if (bundle != null) { + key = getString(bundle, key); + } + publish(getCallerInfo(), logLevel(level), key, thrown); + } + } + + @Override + public final void log(Level level, ResourceBundle bundle, String format, Object... params) { + if (isLoggable(level)) { + if (bundle != null) { + format = getString(bundle, format); + } + publish(getCallerInfo(), logLevel(level), format, params); + } + } + + // --------------------------------------------------- + // From PlatformLogger.Bridge + // --------------------------------------------------- + + @Override + public final boolean isLoggable(PlatformLogger.Level level) { + final PlatformLogger.Level effectiveLevel = effectiveLevel(); + return level != PlatformLogger.Level.OFF + && level.ordinal() >= effectiveLevel.ordinal(); + } + + @Override + public final boolean isEnabled() { + return level != PlatformLogger.Level.OFF; + } + + @Override + public final void log(PlatformLogger.Level level, String msg) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg); + } + } + + @Override + public final void log(PlatformLogger.Level level, String msg, Throwable thrown) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg, thrown); + } + } + + @Override + public final void log(PlatformLogger.Level level, String msg, Object... params) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msg, params); + } + } + + private PlatformLogger.Level effectiveLevel() { + if (level == null) return defaultPlatformLevel(); + return level; + } + + @Override + public final PlatformLogger.Level getPlatformLevel() { + return level; + } + + @Override + public final void setPlatformLevel(PlatformLogger.Level newLevel) { + level = newLevel; + } + + @Override + public final LoggerConfiguration getLoggerConfiguration() { + return this; + } + + /** + * Default platform logging support - output messages to System.err - + * equivalent to ConsoleHandler with SimpleFormatter. + */ + static PrintStream outputStream() { + return System.err; + } + + // Returns the caller's class and method's name; best effort + // if cannot infer, return the logger's name. + private String getCallerInfo() { + Optional frame = new CallerFinder().get(); + if (frame.isPresent()) { + return frame.get().getClassName() + " " + frame.get().getMethodName(); + } else { + return name; + } + } + + /* + * CallerFinder is a stateful predicate. + */ + @SuppressWarnings("removal") + static final class CallerFinder implements Predicate { + private static final StackWalker WALKER; + static { + final PrivilegedAction action = new PrivilegedAction<>() { + @Override + public StackWalker run() { + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + } + }; + WALKER = AccessController.doPrivileged(action); + } + + /** + * Returns StackFrame of the caller's frame. + * @return StackFrame of the caller's frame. + */ + Optional get() { + return WALKER.walk((s) -> s.filter(this).findFirst()); + } + + private boolean lookingForLogger = true; + /** + * Returns true if we have found the caller's frame, false if the frame + * must be skipped. + * + * @param t The frame info. + * @return true if we have found the caller's frame, false if the frame + * must be skipped. + */ + @Override + public boolean test(StackWalker.StackFrame t) { + final String cname = t.getClassName(); + // We should skip all frames until we have found the logger, + // because these frames could be frames introduced by e.g. custom + // sub classes of Handler. + if (lookingForLogger) { + // Skip all frames until we have found the first logger frame. + lookingForLogger = !isLoggerImplFrame(cname); + return false; + } + // Continue walking until we've found the relevant calling frame. + // Skips logging/logger infrastructure. + return !Formatting.isFilteredFrame(t); + } + + private boolean isLoggerImplFrame(String cname) { + return (cname.equals("sun.util.logging.PlatformLogger") || + cname.equals("jdk.internal.logger.SimpleConsoleLogger")); + } + } + + private String getCallerInfo(String sourceClassName, String sourceMethodName) { + if (sourceClassName == null) return name; + if (sourceMethodName == null) return sourceClassName; + return sourceClassName + " " + sourceMethodName; + } + + private String toString(Throwable thrown) { + String throwable = ""; + if (thrown != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(); + thrown.printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + return throwable; + } + + private synchronized String format(Enum level, + String msg, Throwable thrown, String callerInfo) { + + ZonedDateTime zdt = ZonedDateTime.now(); + String throwable = toString(thrown); + + return String.format(getSimpleFormatString(), + zdt, + callerInfo, + name, + level.name(), + msg, + throwable); + } + + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg) { + outputStream().print(format(level, msg, null, callerInfo)); + } + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg, Throwable thrown) { + outputStream().print(format(level, msg, thrown, callerInfo)); + } + // publish accepts both PlatformLogger Levels and LoggerFinder Levels. + private void publish(String callerInfo, Enum level, String msg, Object... params) { + msg = params == null || params.length == 0 ? msg + : Formatting.formatMessage(msg, params); + outputStream().print(format(level, msg, null, callerInfo)); + } + + public static SimpleConsoleLogger makeSimpleLogger(String name) { + return new SimpleConsoleLogger(name, false); + } + + @Override + public final void log(PlatformLogger.Level level, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msgSupplier.get()); + } + } + + @Override + public final void log(PlatformLogger.Level level, Throwable thrown, + Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(), logLevel(level), msgSupplier.get(), thrown); + } + } + + @Override + public final void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg); + } + } + + @Override + public final void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get()); + } + } + + @Override + public final void logp(PlatformLogger.Level level, String sourceClass, String sourceMethod, + String msg, Object... params) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); + } + } + + @Override + public final void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); + } + } + + @Override + public final void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, Supplier msgSupplier) { + if (isLoggable(level)) { + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msgSupplier.get(), thrown); + } + } + + @Override + public final void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String key, Object... params) { + if (isLoggable(level)) { + String msg = bundle == null ? key : getString(bundle, key); + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, params); + } + } + + @Override + public final void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String key, Throwable thrown) { + if (isLoggable(level)) { + String msg = bundle == null ? key : getString(bundle, key); + publish(getCallerInfo(sourceClass, sourceMethod), logLevel(level), msg, thrown); + } + } + + @Override + public final void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String key, Object... params) { + if (isLoggable(level)) { + String msg = bundle == null ? key : getString(bundle,key); + publish(getCallerInfo(), logLevel(level), msg, params); + } + } + + @Override + public final void logrb(PlatformLogger.Level level, ResourceBundle bundle, + String key, Throwable thrown) { + if (isLoggable(level)) { + String msg = bundle == null ? key : getString(bundle,key); + publish(getCallerInfo(), logLevel(level), msg, thrown); + } + } + + static String getString(ResourceBundle bundle, String key) { + if (bundle == null || key == null) return key; + try { + return bundle.getString(key); + } catch (MissingResourceException x) { + // Emulate what java.util.logging Formatters do + // We don't want unchecked exception to propagate up to + // the caller's code. + return key; + } + } + + static final class Formatting { + // The default simple log format string. + // Used both by SimpleConsoleLogger when java.logging is not present, + // and by SurrogateLogger and java.util.logging.SimpleFormatter when + // java.logging is present. + static final String DEFAULT_FORMAT = + "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s%n%4$s: %5$s%6$s%n"; + + // The system property key that allows to change the default log format + // when java.logging is not present. This is used to control the formatting + // of the SimpleConsoleLogger. + static final String DEFAULT_FORMAT_PROP_KEY = + "jdk.system.logger.format"; + + // The system property key that allows to change the default log format + // when java.logging is present. This is used to control the formatting + // of the SurrogateLogger (used before java.util.logging.LogManager is + // initialized) and the java.util.logging.SimpleFormatter (used after + // java.util.logging.LogManager is initialized). + static final String JUL_FORMAT_PROP_KEY = + "java.util.logging.SimpleFormatter.format"; + + // The simple console logger format string + static final String SIMPLE_CONSOLE_LOGGER_FORMAT = + getSimpleFormat(DEFAULT_FORMAT_PROP_KEY, null); + + // Make it easier to wrap Logger... + private static final String[] skips; + static { + String additionalPkgs = + GetPropertyAction.privilegedGetProperty("jdk.logger.packages"); + skips = additionalPkgs == null ? new String[0] : additionalPkgs.split(","); + } + + static boolean isFilteredFrame(StackFrame st) { + // skip logging/logger infrastructure + if (System.Logger.class.isAssignableFrom(st.getDeclaringClass())) { + return true; + } + + // fast escape path: all the prefixes below start with 's' or 'j' and + // have more than 12 characters. + final String cname = st.getClassName(); + char c = cname.length() < 12 ? 0 : cname.charAt(0); + if (c == 's') { + // skip internal machinery classes + if (cname.startsWith("sun.util.logging.")) return true; + if (cname.startsWith("sun.rmi.runtime.Log")) return true; + } else if (c == 'j') { + // Message delayed at Bootstrap: no need to go further up. + if (cname.startsWith("jdk.internal.logger.BootstrapLogger$LogEvent")) return false; + // skip public machinery classes + if (cname.startsWith("jdk.internal.logger.")) return true; + if (cname.startsWith("java.util.logging.")) return true; + if (cname.startsWith("java.lang.invoke.MethodHandle")) return true; + if (cname.startsWith("java.security.AccessController")) return true; + } + + // check additional prefixes if any are specified. + if (skips.length > 0) { + for (int i=0; i defaultPropertyGetter) { + // Double check that 'key' is one of the expected property names: + // - DEFAULT_FORMAT_PROP_KEY is used to control the + // SimpleConsoleLogger format when java.logging is + // not present. + // - JUL_FORMAT_PROP_KEY is used when this method is called + // from the SurrogateLogger subclass. It is used to control the + // SurrogateLogger format and java.util.logging.SimpleFormatter + // format when java.logging is present. + // This method should not be called with any other key. + if (!DEFAULT_FORMAT_PROP_KEY.equals(key) + && !JUL_FORMAT_PROP_KEY.equals(key)) { + throw new IllegalArgumentException("Invalid property name: " + key); + } + + // Do not use any lambda in this method. Using a lambda here causes + // jdk/test/java/lang/invoke/lambda/LogGeneratedClassesTest.java + // to fail - because that test has a testcase which somehow references + // PlatformLogger and counts the number of generated lambda classes. + String format = GetPropertyAction.privilegedGetProperty(key); + + if (format == null && defaultPropertyGetter != null) { + format = defaultPropertyGetter.apply(key); + } + if (format != null) { + try { + // validate the user-defined format string + String.format(format, ZonedDateTime.now(), "", "", "", "", ""); + } catch (IllegalArgumentException e) { + // illegal syntax; fall back to the default format + format = DEFAULT_FORMAT; + } + } else { + format = DEFAULT_FORMAT; + } + return format; + } + + + // Copied from java.util.logging.Formatter.formatMessage + static String formatMessage(String format, Object... parameters) { + // Do the formatting. + try { + if (parameters == null || parameters.length == 0) { + // No parameters. Just return format string. + return format; + } + // Is it a java.text style format? + // Ideally we could match with + // Pattern.compile("\\{\\d").matcher(format).find()) + // However the cost is 14% higher, so we cheaply check for + // + boolean isJavaTestFormat = false; + final int len = format.length(); + for (int i=0; i= '0' && d <= '9') { + isJavaTestFormat = true; + break; + } + } + } + if (isJavaTestFormat) { + return java.text.MessageFormat.format(format, parameters); + } + return format; + } catch (Exception ex) { + // Formatting failed: use format string. + return format; + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/logger/SurrogateLogger.class b/tests/test_data/std/jdk/internal/logger/SurrogateLogger.class new file mode 100644 index 00000000..c129db5b Binary files /dev/null and b/tests/test_data/std/jdk/internal/logger/SurrogateLogger.class differ diff --git a/tests/test_data/std/jdk/internal/logger/SurrogateLogger.java b/tests/test_data/std/jdk/internal/logger/SurrogateLogger.java new file mode 100644 index 00000000..8507cb5d --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/SurrogateLogger.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.logger; + +import java.util.function.Function; +import sun.util.logging.PlatformLogger; + +/** + * A simple console logger used to emulate the behavior of JUL loggers when + * java.util.logging has no custom configuration. + * Surrogate loggers are usually only used temporarily, until the LogManager + * is initialized. At this point, the surrogates are replaced by an actual + * logger obtained from LogManager. + */ +public final class SurrogateLogger extends SimpleConsoleLogger { + + private static final PlatformLogger.Level JUL_DEFAULT_LEVEL = + PlatformLogger.Level.INFO; + private static volatile String simpleFormatString; + + SurrogateLogger(String name) { + super(name, true); + } + + @Override + PlatformLogger.Level defaultPlatformLevel() { + return JUL_DEFAULT_LEVEL; + } + + @Override + String getSimpleFormatString() { + if (simpleFormatString == null) { + simpleFormatString = getSimpleFormat(null); + } + return simpleFormatString; + } + + public static String getSimpleFormat(Function defaultPropertyGetter) { + return Formatting.getSimpleFormat(Formatting.JUL_FORMAT_PROP_KEY, defaultPropertyGetter); + } + + public static SurrogateLogger makeSurrogateLogger(String name) { + return new SurrogateLogger(name); + } + + public static boolean isFilteredFrame(StackWalker.StackFrame st) { + return Formatting.isFilteredFrame(st); + } +} diff --git a/tests/test_data/std/jdk/internal/logger/package-info.java b/tests/test_data/std/jdk/internal/logger/package-info.java new file mode 100644 index 00000000..46b571aa --- /dev/null +++ b/tests/test_data/std/jdk/internal/logger/package-info.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * [JDK INTERNAL] + * The {@code jdk.internal.logger} package defines an internal provider + * whose default naive implementation is replaced by the {@code java.logging} + * module when the {@code java.logging} module is present. + *

+ * Default Implementation + *

+ * The JDK default implementation of the System.LoggerFinder will attempt to + * load an installed instance of the {@link jdk.internal.logger.DefaultLoggerFinder} + * defined in this package. + * When the {@code java.util.logging} package is present, this will usually + * resolve to an instance of {@link sun.util.logging.internal.LoggingProviderImpl} - + * which provides an implementation of the Logger whose backend is a + * {@link java.util.logging.Logger java.util.logging.Logger}. + * Configuration can thus be performed by direct access to the regular + * {@code java.util.logging} APIs, + * using {@link java.util.logging.Logger java.util.logging.Logger} and + * {@link java.util.logging.LogManager} to access and configure the backend + * Loggers. + *
+ * If however {@code java.util.logging} is not linked with the application, then + * the default implementation will return a simple logger that will print out + * all log messages of INFO level and above to the console ({@code System.err}), + * as implemented by the base {@link jdk.internal.logger.DefaultLoggerFinder} class. + *

+ * Message Levels and Mapping to java.util.logging + *

+ * The {@link java.lang.System.LoggerFinder} class documentation describe how + * {@linkplain java.lang.System.Logger.Level System.Logger levels} are mapped + * to {@linkplain java.util.logging.Level JUL levels} when {@code + * java.util.logging} is the backend. + * + * @see jdk.internal.logger.DefaultLoggerFinder + * @see sun.util.logging.internal.LoggingProviderImpl + * @see java.lang.System.LoggerFinder + * @see java.lang.System.Logger + * @see sun.util.logging.PlatformLogger.Bridge + * @see sun.util.logging.internal + * + * @since 9 + */ +package jdk.internal.logger; diff --git a/tests/test_data/std/jdk/internal/math/DoubleConsts.class b/tests/test_data/std/jdk/internal/math/DoubleConsts.class new file mode 100644 index 00000000..190beae8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/DoubleConsts.class differ diff --git a/tests/test_data/std/jdk/internal/math/DoubleConsts.java b/tests/test_data/std/jdk/internal/math/DoubleConsts.java new file mode 100644 index 00000000..d3a271fd --- /dev/null +++ b/tests/test_data/std/jdk/internal/math/DoubleConsts.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.math; + +import static java.lang.Double.MIN_EXPONENT; +import static java.lang.Double.PRECISION; +import static java.lang.Double.SIZE; + +/** + * This class contains additional constants documenting limits of the + * {@code double} type. + * + * @author Joseph D. Darcy + */ + +public class DoubleConsts { + /** + * Don't let anyone instantiate this class. + */ + private DoubleConsts() {} + + /** + * The number of logical bits in the significand of a + * {@code double} number, including the implicit bit. + */ + public static final int SIGNIFICAND_WIDTH = PRECISION; + + /** + * The exponent the smallest positive {@code double} + * subnormal value would have if it could be normalized.. + */ + public static final int MIN_SUB_EXPONENT = + MIN_EXPONENT - (SIGNIFICAND_WIDTH - 1); // -1074 + + /** + * Bias used in representing a {@code double} exponent. + */ + public static final int EXP_BIAS = + (1 << (SIZE - SIGNIFICAND_WIDTH - 1)) - 1; // 1023 + + /** + * Bit mask to isolate the sign bit of a {@code double}. + */ + public static final long SIGN_BIT_MASK = 1L << (SIZE - 1); + + /** + * Bit mask to isolate the exponent field of a {@code double}. + */ + public static final long EXP_BIT_MASK = + ((1L << (SIZE - SIGNIFICAND_WIDTH)) - 1) << (SIGNIFICAND_WIDTH - 1); + + /** + * Bit mask to isolate the significand field of a {@code double}. + */ + public static final long SIGNIF_BIT_MASK = (1L << (SIGNIFICAND_WIDTH - 1)) - 1; + + /** + * Bit mask to isolate the magnitude bits (combined exponent and + * significand fields) of a {@code double}. + */ + public static final long MAG_BIT_MASK = EXP_BIT_MASK | SIGNIF_BIT_MASK; + + static { + // verify bit masks cover all bit positions and that the bit + // masks are non-overlapping + assert(((SIGN_BIT_MASK | EXP_BIT_MASK | SIGNIF_BIT_MASK) == ~0L) && + (((SIGN_BIT_MASK & EXP_BIT_MASK) == 0L) && + ((SIGN_BIT_MASK & SIGNIF_BIT_MASK) == 0L) && + ((EXP_BIT_MASK & SIGNIF_BIT_MASK) == 0L)) && + ((SIGN_BIT_MASK | MAG_BIT_MASK) == ~0L)); + } +} diff --git a/tests/test_data/std/jdk/internal/math/DoubleToDecimal.class b/tests/test_data/std/jdk/internal/math/DoubleToDecimal.class new file mode 100644 index 00000000..d8f5bae9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/DoubleToDecimal.class differ diff --git a/tests/test_data/std/jdk/internal/math/DoubleToDecimal.java b/tests/test_data/std/jdk/internal/math/DoubleToDecimal.java new file mode 100644 index 00000000..e3472d55 --- /dev/null +++ b/tests/test_data/std/jdk/internal/math/DoubleToDecimal.java @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.math; + +import java.io.IOException; + +import static java.lang.Double.*; +import static java.lang.Long.*; +import static java.lang.Math.multiplyHigh; +import static jdk.internal.math.MathUtils.*; + +/** + * This class exposes a method to render a {@code double} as a string. + */ +public final class DoubleToDecimal { + /* + * For full details about this code see the following references: + * + * [1] Giulietti, "The Schubfach way to render doubles", + * https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb + * + * [2] IEEE Computer Society, "IEEE Standard for Floating-Point Arithmetic" + * + * [3] Bouvier & Zimmermann, "Division-Free Binary-to-Decimal Conversion" + * + * Divisions are avoided altogether for the benefit of those architectures + * that do not provide specific machine instructions or where they are slow. + * This is discussed in section 10 of [1]. + */ + + /* The precision in bits */ + static final int P = PRECISION; + + /* Exponent width in bits */ + private static final int W = (Double.SIZE - 1) - (P - 1); + + /* Minimum value of the exponent: -(2^(W-1)) - P + 3 */ + static final int Q_MIN = (-1 << (W - 1)) - P + 3; + + /* Maximum value of the exponent: 2^(W-1) - P */ + static final int Q_MAX = (1 << (W - 1)) - P; + + /* 10^(E_MIN - 1) <= MIN_VALUE < 10^E_MIN */ + static final int E_MIN = -323; + + /* 10^(E_MAX - 1) <= MAX_VALUE < 10^E_MAX */ + static final int E_MAX = 309; + + /* Threshold to detect tiny values, as in section 8.2.1 of [1] */ + static final long C_TINY = 3; + + /* The minimum and maximum k, as in section 8 of [1] */ + static final int K_MIN = -324; + static final int K_MAX = 292; + + /* H is as in section 8.1 of [1] */ + static final int H = 17; + + /* Minimum value of the significand of a normal value: 2^(P-1) */ + private static final long C_MIN = 1L << (P - 1); + + /* Mask to extract the biased exponent */ + private static final int BQ_MASK = (1 << W) - 1; + + /* Mask to extract the fraction bits */ + private static final long T_MASK = (1L << (P - 1)) - 1; + + /* Used in rop() */ + private static final long MASK_63 = (1L << 63) - 1; + + /* Used for left-to-tight digit extraction */ + private static final int MASK_28 = (1 << 28) - 1; + + private static final int NON_SPECIAL = 0; + private static final int PLUS_ZERO = 1; + private static final int MINUS_ZERO = 2; + private static final int PLUS_INF = 3; + private static final int MINUS_INF = 4; + private static final int NAN = 5; + + /* + * Room for the longer of the forms + * -ddddd.dddddddddddd H + 2 characters + * -0.00ddddddddddddddddd H + 5 characters + * -d.ddddddddddddddddE-eee H + 7 characters + * where there are H digits d + */ + public static final int MAX_CHARS = H + 7; + + private final byte[] bytes; + + /* Index into bytes of rightmost valid character */ + private int index; + + private DoubleToDecimal(boolean noChars) { + bytes = noChars ? null : new byte[MAX_CHARS]; + } + + /** + * Returns a string representation of the {@code double} + * argument. All characters mentioned below are ASCII characters. + * + * @param v the {@code double} to be converted. + * @return a string representation of the argument. + * @see Double#toString(double) + */ + public static String toString(double v) { + return new DoubleToDecimal(false).toDecimalString(v); + } + + /** + * Splits the decimal d described in + * {@link Double#toString(double)} in integers f and e + * such that d = f 10e. + * + *

Further, determines integer n such that n = 0 when + * f = 0, and + * 10n-1f < 10n + * otherwise. + * + *

The argument {@code v} is assumed to be a positive finite value or + * positive zero. + * Further, {@code fd} must not be {@code null}. + * + * @param v the finite {@code double} to be split. + * @param fd the object that will carry f, e, and n. + */ + public static void split(double v, FormattedFPDecimal fd) { + new DoubleToDecimal(true).toDecimal(v, fd); + } + + /** + * Appends the rendering of the {@code v} to {@code app}. + * + *

The outcome is the same as if {@code v} were first + * {@link #toString(double) rendered} and the resulting string were then + * {@link Appendable#append(CharSequence) appended} to {@code app}. + * + * @param v the {@code double} whose rendering is appended. + * @param app the {@link Appendable} to append to. + * @throws IOException If an I/O error occurs + */ + public static Appendable appendTo(double v, Appendable app) + throws IOException { + return new DoubleToDecimal(false).appendDecimalTo(v, app); + } + + private String toDecimalString(double v) { + return switch (toDecimal(v, null)) { + case NON_SPECIAL -> charsToString(); + case PLUS_ZERO -> "0.0"; + case MINUS_ZERO -> "-0.0"; + case PLUS_INF -> "Infinity"; + case MINUS_INF -> "-Infinity"; + default -> "NaN"; + }; + } + + private Appendable appendDecimalTo(double v, Appendable app) + throws IOException { + switch (toDecimal(v, null)) { + case NON_SPECIAL: + char[] chars = new char[index + 1]; + for (int i = 0; i < chars.length; ++i) { + chars[i] = (char) bytes[i]; + } + if (app instanceof StringBuilder builder) { + return builder.append(chars); + } + if (app instanceof StringBuffer buffer) { + return buffer.append(chars); + } + for (char c : chars) { + app.append(c); + } + return app; + case PLUS_ZERO: return app.append("0.0"); + case MINUS_ZERO: return app.append("-0.0"); + case PLUS_INF: return app.append("Infinity"); + case MINUS_INF: return app.append("-Infinity"); + default: return app.append("NaN"); + } + } + + /* + * Returns + * PLUS_ZERO iff v is 0.0 + * MINUS_ZERO iff v is -0.0 + * PLUS_INF iff v is POSITIVE_INFINITY + * MINUS_INF iff v is NEGATIVE_INFINITY + * NAN iff v is NaN + */ + private int toDecimal(double v, FormattedFPDecimal fd) { + /* + * For full details see references [2] and [1]. + * + * For finite v != 0, determine integers c and q such that + * |v| = c 2^q and + * Q_MIN <= q <= Q_MAX and + * either 2^(P-1) <= c < 2^P (normal) + * or 0 < c < 2^(P-1) and q = Q_MIN (subnormal) + */ + long bits = doubleToRawLongBits(v); + long t = bits & T_MASK; + int bq = (int) (bits >>> P - 1) & BQ_MASK; + if (bq < BQ_MASK) { + index = -1; + if (bits < 0) { + /* + * fd != null implies bytes == null and bits >= 0 + * Thus, when fd != null, control never reaches here. + */ + append('-'); + } + if (bq != 0) { + /* normal value. Here mq = -q */ + int mq = -Q_MIN + 1 - bq; + long c = C_MIN | t; + /* The fast path discussed in section 8.3 of [1] */ + if (0 < mq & mq < P) { + long f = c >> mq; + if (f << mq == c) { + return toChars(f, 0, fd); + } + } + return toDecimal(-mq, c, 0, fd); + } + if (t != 0) { + /* subnormal value */ + return t < C_TINY + ? toDecimal(Q_MIN, 10 * t, -1, fd) + : toDecimal(Q_MIN, t, 0, fd); + } + return bits == 0 ? PLUS_ZERO : MINUS_ZERO; + } + if (t != 0) { + return NAN; + } + return bits > 0 ? PLUS_INF : MINUS_INF; + } + + private int toDecimal(int q, long c, int dk, FormattedFPDecimal fd) { + /* + * The skeleton corresponds to figure 7 of [1]. + * The efficient computations are those summarized in figure 9. + * + * Here's a correspondence between Java names and names in [1], + * expressed as approximate LaTeX source code and informally. + * Other names are identical. + * cb: \bar{c} "c-bar" + * cbr: \bar{c}_r "c-bar-r" + * cbl: \bar{c}_l "c-bar-l" + * + * vb: \bar{v} "v-bar" + * vbr: \bar{v}_r "v-bar-r" + * vbl: \bar{v}_l "v-bar-l" + * + * rop: r_o' "r-o-prime" + */ + int out = (int) c & 0x1; + long cb = c << 2; + long cbr = cb + 2; + long cbl; + int k; + /* + * flog10pow2(e) = floor(log_10(2^e)) + * flog10threeQuartersPow2(e) = floor(log_10(3/4 2^e)) + * flog2pow10(e) = floor(log_2(10^e)) + */ + if (c != C_MIN | q == Q_MIN) { + /* regular spacing */ + cbl = cb - 2; + k = flog10pow2(q); + } else { + /* irregular spacing */ + cbl = cb - 1; + k = flog10threeQuartersPow2(q); + } + int h = q + flog2pow10(-k) + 2; + + /* g1 and g0 are as in section 9.8.3 of [1], so g = g1 2^63 + g0 */ + long g1 = g1(k); + long g0 = g0(k); + + long vb = rop(g1, g0, cb << h); + long vbl = rop(g1, g0, cbl << h); + long vbr = rop(g1, g0, cbr << h); + + long s = vb >> 2; + if (s >= 100) { + /* + * For n = 17, m = 1 the table in section 10 of [1] shows + * s' = floor(s / 10) = floor(s 115_292_150_460_684_698 / 2^60) + * = floor(s 115_292_150_460_684_698 2^4 / 2^64) + * + * sp10 = 10 s' + * tp10 = 10 t' + * upin iff u' = sp10 10^k in Rv + * wpin iff w' = tp10 10^k in Rv + * See section 9.3 of [1]. + */ + long sp10 = 10 * multiplyHigh(s, 115_292_150_460_684_698L << 4); + long tp10 = sp10 + 10; + boolean upin = vbl + out <= sp10 << 2; + boolean wpin = (tp10 << 2) + out <= vbr; + if (upin != wpin) { + return toChars(upin ? sp10 : tp10, k, fd); + } + } + + /* + * 10 <= s < 100 or s >= 100 and u', w' not in Rv + * uin iff u = s 10^k in Rv + * win iff w = t 10^k in Rv + * See section 9.3 of [1]. + */ + long t = s + 1; + boolean uin = vbl + out <= s << 2; + boolean win = (t << 2) + out <= vbr; + if (uin != win) { + /* Exactly one of u or w lies in Rv */ + return toChars(uin ? s : t, k + dk, fd); + } + /* + * Both u and w lie in Rv: determine the one closest to v. + * See section 9.3 of [1]. + */ + long cmp = vb - (s + t << 1); + return toChars(cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk, fd); + } + + /* + * Computes rop(cp g 2^(-127)), where g = g1 2^63 + g0 + * See section 9.9 and figure 8 of [1]. + */ + private static long rop(long g1, long g0, long cp) { + long x1 = multiplyHigh(g0, cp); + long y0 = g1 * cp; + long y1 = multiplyHigh(g1, cp); + long z = (y0 >>> 1) + x1; + long vbp = y1 + (z >>> 63); + return vbp | (z & MASK_63) + MASK_63 >>> 63; + } + + /* + * Formats the decimal f 10^e. + */ + private int toChars(long f, int e, FormattedFPDecimal fd) { + /* + * For details not discussed here see section 10 of [1]. + * + * Determine len such that + * 10^(len-1) <= f < 10^len + */ + int len = flog10pow2(Long.SIZE - numberOfLeadingZeros(f)); + if (f >= pow10(len)) { + len += 1; + } + if (fd != null) { + fd.set(f, e, len); + return NON_SPECIAL; + } + + /* + * Let fp and ep be the original f and e, respectively. + * Transform f and e to ensure + * 10^(H-1) <= f < 10^H + * fp 10^ep = f 10^(e-H) = 0.f 10^e + */ + f *= pow10(H - len); + e += len; + + /* + * The toChars?() methods perform left-to-right digits extraction + * using ints, provided that the arguments are limited to 8 digits. + * Therefore, split the H = 17 digits of f into: + * h = the most significant digit of f + * m = the next 8 most significant digits of f + * l = the last 8, least significant digits of f + * + * For n = 17, m = 8 the table in section 10 of [1] shows + * floor(f / 10^8) = floor(193_428_131_138_340_668 f / 2^84) = + * floor(floor(193_428_131_138_340_668 f / 2^64) / 2^20) + * and for n = 9, m = 8 + * floor(hm / 10^8) = floor(1_441_151_881 hm / 2^57) + */ + long hm = multiplyHigh(f, 193_428_131_138_340_668L) >>> 20; + int l = (int) (f - 100_000_000L * hm); + int h = (int) (hm * 1_441_151_881L >>> 57); + int m = (int) (hm - 100_000_000 * h); + + if (0 < e && e <= 7) { + return toChars1(h, m, l, e); + } + if (-3 < e && e <= 0) { + return toChars2(h, m, l, e); + } + return toChars3(h, m, l, e); + } + + private int toChars1(int h, int m, int l, int e) { + /* + * 0 < e <= 7: plain format without leading zeroes. + * Left-to-right digits extraction: + * algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + appendDigit(h); + int y = y(m); + int t; + int i = 1; + for (; i < e; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + append('.'); + for (; i <= 8; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + lowDigits(l); + return NON_SPECIAL; + } + + private int toChars2(int h, int m, int l, int e) { + /* -3 < e <= 0: plain format with leading zeroes */ + appendDigit(0); + append('.'); + for (; e < 0; ++e) { + appendDigit(0); + } + appendDigit(h); + append8Digits(m); + lowDigits(l); + return NON_SPECIAL; + } + + private int toChars3(int h, int m, int l, int e) { + /* -3 >= e | e > 7: computerized scientific notation */ + appendDigit(h); + append('.'); + append8Digits(m); + lowDigits(l); + exponent(e - 1); + return NON_SPECIAL; + } + + private void lowDigits(int l) { + if (l != 0) { + append8Digits(l); + } + removeTrailingZeroes(); + } + + private void append8Digits(int m) { + /* + * Left-to-right digits extraction: + * algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + int y = y(m); + for (int i = 0; i < 8; ++i) { + int t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + } + + private void removeTrailingZeroes() { + while (bytes[index] == '0') { + --index; + } + /* ... but do not remove the one directly to the right of '.' */ + if (bytes[index] == '.') { + ++index; + } + } + + private int y(int a) { + /* + * Algorithm 1 in [3] needs computation of + * floor((a + 1) 2^n / b^k) - 1 + * with a < 10^8, b = 10, k = 8, n = 28. + * Noting that + * (a + 1) 2^n <= 10^8 2^28 < 10^17 + * For n = 17, m = 8 the table in section 10 of [1] leads to: + */ + return (int) (multiplyHigh( + (long) (a + 1) << 28, + 193_428_131_138_340_668L) >>> 20) - 1; + } + + private void exponent(int e) { + append('E'); + if (e < 0) { + append('-'); + e = -e; + } + if (e < 10) { + appendDigit(e); + return; + } + int d; + if (e >= 100) { + /* + * For n = 3, m = 2 the table in section 10 of [1] shows + * floor(e / 100) = floor(1_311 e / 2^17) + */ + d = e * 1_311 >>> 17; + appendDigit(d); + e -= 100 * d; + } + /* + * For n = 2, m = 1 the table in section 10 of [1] shows + * floor(e / 10) = floor(103 e / 2^10) + */ + d = e * 103 >>> 10; + appendDigit(d); + appendDigit(e - 10 * d); + } + + private void append(int c) { + bytes[++index] = (byte) c; + } + + private void appendDigit(int d) { + bytes[++index] = (byte) ('0' + d); + } + + /* Using the deprecated constructor enhances performance */ + @SuppressWarnings("deprecation") + private String charsToString() { + return new String(bytes, 0, 0, index + 1); + } + +} diff --git a/tests/test_data/std/jdk/internal/math/FDBigInteger.class b/tests/test_data/std/jdk/internal/math/FDBigInteger.class new file mode 100644 index 00000000..fb16813c Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FDBigInteger.class differ diff --git a/tests/test_data/std/jdk/internal/math/FDBigInteger.java b/tests/test_data/std/jdk/internal/math/FDBigInteger.java new file mode 100644 index 00000000..f6d868df --- /dev/null +++ b/tests/test_data/std/jdk/internal/math/FDBigInteger.java @@ -0,0 +1,1521 @@ +/* + * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.math; + +import jdk.internal.misc.CDS; + +import java.math.BigInteger; +import java.util.Arrays; +//@ model import org.jmlspecs.models.JMLMath; + +/** + * A simple big integer package specifically for floating point base conversion. + */ +public /*@ spec_bigint_math @*/ class FDBigInteger { + + // + // This class contains many comments that start with "/*@" mark. + // They are behavourial specification in + // the Java Modelling Language (JML): + // http://www.eecs.ucf.edu/~leavens/JML//index.shtml + // + + /*@ + @ public pure model static \bigint UNSIGNED(int v) { + @ return v >= 0 ? v : v + (((\bigint)1) << 32); + @ } + @ + @ public pure model static \bigint UNSIGNED(long v) { + @ return v >= 0 ? v : v + (((\bigint)1) << 64); + @ } + @ + @ public pure model static \bigint AP(int[] data, int len) { + @ return (\sum int i; 0 <= 0 && i < len; UNSIGNED(data[i]) << (i*32)); + @ } + @ + @ public pure model static \bigint pow52(int p5, int p2) { + @ ghost \bigint v = 1; + @ for (int i = 0; i < p5; i++) v *= 5; + @ return v << p2; + @ } + @ + @ public pure model static \bigint pow10(int p10) { + @ return pow52(p10, p10); + @ } + @*/ + + static final int[] SMALL_5_POW; + + static final long[] LONG_5_POW; + + // Maximum size of cache of powers of 5 as FDBigIntegers. + private static final int MAX_FIVE_POW = 340; + + // Cache of big powers of 5 as FDBigIntegers. + private static final FDBigInteger POW_5_CACHE[]; + + // Zero as an FDBigInteger. + public static final FDBigInteger ZERO; + + // Archive proxy + private static Object[] archivedCaches; + + // Initialize FDBigInteger cache of powers of 5. + static { + CDS.initializeFromArchive(FDBigInteger.class); + Object[] caches = archivedCaches; + if (caches == null) { + long[] long5pow = { + 1L, + 5L, + 5L * 5, + 5L * 5 * 5, + 5L * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5L * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + }; + int[] small5pow = { + 1, + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 + }; + FDBigInteger[] pow5cache = new FDBigInteger[MAX_FIVE_POW]; + int i = 0; + while (i < small5pow.length) { + FDBigInteger pow5 = new FDBigInteger(new int[] { small5pow[i] }, 0); + pow5.makeImmutable(); + pow5cache[i] = pow5; + i++; + } + FDBigInteger prev = pow5cache[i - 1]; + while (i < MAX_FIVE_POW) { + pow5cache[i] = prev = prev.mult(5); + prev.makeImmutable(); + i++; + } + FDBigInteger zero = new FDBigInteger(new int[0], 0); + zero.makeImmutable(); + archivedCaches = caches = new Object[] {small5pow, long5pow, pow5cache, zero}; + } + SMALL_5_POW = (int[])caches[0]; + LONG_5_POW = (long[])caches[1]; + POW_5_CACHE = (FDBigInteger[])caches[2]; + ZERO = (FDBigInteger)caches[3]; + } + + // Constant for casting an int to a long via bitwise AND. + private static final long LONG_MASK = 0xffffffffL; + + //@ spec_public non_null; + private int data[]; // value: data[0] is least significant + //@ spec_public; + private int offset; // number of least significant zero padding ints + //@ spec_public; + private int nWords; // data[nWords-1]!=0, all values above are zero + // if nWords==0 -> this FDBigInteger is zero + //@ spec_public; + private boolean isImmutable = false; + + /*@ + @ public invariant 0 <= nWords && nWords <= data.length && offset >= 0; + @ public invariant nWords == 0 ==> offset == 0; + @ public invariant nWords > 0 ==> data[nWords - 1] != 0; + @ public invariant (\forall int i; nWords <= i && i < data.length; data[i] == 0); + @ public pure model \bigint value() { + @ return AP(data, nWords) << (offset*32); + @ } + @*/ + + /** + * Constructs an FDBigInteger from data and padding. The + * data parameter has the least significant int at + * the zeroth index. The offset parameter gives the number of + * zero ints to be inferred below the least significant element + * of data. + * + * @param data An array containing all non-zero ints of the value. + * @param offset An offset indicating the number of zero ints to pad + * below the least significant element of data. + */ + /*@ + @ requires data != null && offset >= 0; + @ ensures this.value() == \old(AP(data, data.length) << (offset*32)); + @ ensures this.data == \old(data); + @*/ + private FDBigInteger(int[] data, int offset) { + this.data = data; + this.offset = offset; + this.nWords = data.length; + trimLeadingZeros(); + } + + /** + * Constructs an FDBigInteger from a starting value and some + * decimal digits. + * + * @param lValue The starting value. + * @param digits The decimal digits. + * @param kDigits The initial index into digits. + * @param nDigits The final index into digits. + */ + /*@ + @ requires digits != null; + @ requires 0 <= kDigits && kDigits <= nDigits && nDigits <= digits.length; + @ requires (\forall int i; 0 <= i && i < nDigits; '0' <= digits[i] && digits[i] <= '9'); + @ ensures this.value() == \old(lValue * pow10(nDigits - kDigits) + (\sum int i; kDigits <= i && i < nDigits; (digits[i] - '0') * pow10(nDigits - i - 1))); + @*/ + public FDBigInteger(long lValue, char[] digits, int kDigits, int nDigits) { + int n = Math.max((nDigits + 8) / 9, 2); // estimate size needed. + data = new int[n]; // allocate enough space + data[0] = (int) lValue; // starting value + data[1] = (int) (lValue >>> 32); + offset = 0; + nWords = 2; + int i = kDigits; + int limit = nDigits - 5; // slurp digits 5 at a time. + int v; + while (i < limit) { + int ilim = i + 5; + v = (int) digits[i++] - (int) '0'; + while (i < ilim) { + v = 10 * v + (int) digits[i++] - (int) '0'; + } + multAddMe(100000, v); // ... where 100000 is 10^5. + } + int factor = 1; + v = 0; + while (i < nDigits) { + v = 10 * v + (int) digits[i++] - (int) '0'; + factor *= 10; + } + if (factor != 1) { + multAddMe(factor, v); + } + trimLeadingZeros(); + } + + /** + * Returns an FDBigInteger with the numerical value + * 5p5 * 2p2. + * + * @param p5 The exponent of the power-of-five factor. + * @param p2 The exponent of the power-of-two factor. + * @return 5p5 * 2p2 + */ + /*@ + @ requires p5 >= 0 && p2 >= 0; + @ assignable \nothing; + @ ensures \result.value() == \old(pow52(p5, p2)); + @*/ + public static FDBigInteger valueOfPow52(int p5, int p2) { + if (p5 != 0) { + if (p2 == 0) { + return big5pow(p5); + } else if (p5 < SMALL_5_POW.length) { + int pow5 = SMALL_5_POW[p5]; + int wordcount = p2 >> 5; + int bitcount = p2 & 0x1f; + if (bitcount == 0) { + return new FDBigInteger(new int[]{pow5}, wordcount); + } else { + return new FDBigInteger(new int[]{ + pow5 << bitcount, + pow5 >>> (32 - bitcount) + }, wordcount); + } + } else { + return big5pow(p5).leftShift(p2); + } + } else { + return valueOfPow2(p2); + } + } + + /** + * Returns an FDBigInteger with the numerical value + * value * 5p5 * 2p2. + * + * @param value The constant factor. + * @param p5 The exponent of the power-of-five factor. + * @param p2 The exponent of the power-of-two factor. + * @return value * 5p5 * 2p2 + */ + /*@ + @ requires p5 >= 0 && p2 >= 0; + @ assignable \nothing; + @ ensures \result.value() == \old(UNSIGNED(value) * pow52(p5, p2)); + @*/ + public static FDBigInteger valueOfMulPow52(long value, int p5, int p2) { + assert p5 >= 0 : p5; + assert p2 >= 0 : p2; + int v0 = (int) value; + int v1 = (int) (value >>> 32); + int wordcount = p2 >> 5; + int bitcount = p2 & 0x1f; + if (p5 != 0) { + if (p5 < SMALL_5_POW.length) { + long pow5 = SMALL_5_POW[p5] & LONG_MASK; + long carry = (v0 & LONG_MASK) * pow5; + v0 = (int) carry; + carry >>>= 32; + carry = (v1 & LONG_MASK) * pow5 + carry; + v1 = (int) carry; + int v2 = (int) (carry >>> 32); + if (bitcount == 0) { + return new FDBigInteger(new int[]{v0, v1, v2}, wordcount); + } else { + return new FDBigInteger(new int[]{ + v0 << bitcount, + (v1 << bitcount) | (v0 >>> (32 - bitcount)), + (v2 << bitcount) | (v1 >>> (32 - bitcount)), + v2 >>> (32 - bitcount) + }, wordcount); + } + } else { + FDBigInteger pow5 = big5pow(p5); + int[] r; + if (v1 == 0) { + r = new int[pow5.nWords + 1 + ((p2 != 0) ? 1 : 0)]; + mult(pow5.data, pow5.nWords, v0, r); + } else { + r = new int[pow5.nWords + 2 + ((p2 != 0) ? 1 : 0)]; + mult(pow5.data, pow5.nWords, v0, v1, r); + } + return (new FDBigInteger(r, pow5.offset)).leftShift(p2); + } + } else if (p2 != 0) { + if (bitcount == 0) { + return new FDBigInteger(new int[]{v0, v1}, wordcount); + } else { + return new FDBigInteger(new int[]{ + v0 << bitcount, + (v1 << bitcount) | (v0 >>> (32 - bitcount)), + v1 >>> (32 - bitcount) + }, wordcount); + } + } + return new FDBigInteger(new int[]{v0, v1}, 0); + } + + /** + * Returns an FDBigInteger with the numerical value + * 2p2. + * + * @param p2 The exponent of 2. + * @return 2p2 + */ + /*@ + @ requires p2 >= 0; + @ assignable \nothing; + @ ensures \result.value() == pow52(0, p2); + @*/ + private static FDBigInteger valueOfPow2(int p2) { + int wordcount = p2 >> 5; + int bitcount = p2 & 0x1f; + return new FDBigInteger(new int[]{1 << bitcount}, wordcount); + } + + /** + * Removes all leading zeros from this FDBigInteger adjusting + * the offset and number of non-zero leading words accordingly. + */ + /*@ + @ requires data != null; + @ requires 0 <= nWords && nWords <= data.length && offset >= 0; + @ requires nWords == 0 ==> offset == 0; + @ ensures nWords == 0 ==> offset == 0; + @ ensures nWords > 0 ==> data[nWords - 1] != 0; + @*/ + private /*@ helper @*/ void trimLeadingZeros() { + int i = nWords; + if (i > 0 && (data[--i] == 0)) { + //for (; i > 0 && data[i - 1] == 0; i--) ; + while(i > 0 && data[i - 1] == 0) { + i--; + } + this.nWords = i; + if (i == 0) { // all words are zero + this.offset = 0; + } + } + } + + /** + * Retrieves the normalization bias of the FDBigIntger. The + * normalization bias is a left shift such that after it the highest word + * of the value will have the 4 highest bits equal to zero: + * {@code (highestWord & 0xf0000000) == 0}, but the next bit should be 1 + * {@code (highestWord & 0x08000000) != 0}. + * + * @return The normalization bias. + */ + /*@ + @ requires this.value() > 0; + @*/ + public /*@ pure @*/ int getNormalizationBias() { + if (nWords == 0) { + throw new IllegalArgumentException("Zero value cannot be normalized"); + } + int zeros = Integer.numberOfLeadingZeros(data[nWords - 1]); + return (zeros < 4) ? 28 + zeros : zeros - 4; + } + + // TODO: Why is anticount param needed if it is always 32 - bitcount? + /** + * Left shifts the contents of one int array into another. + * + * @param src The source array. + * @param idx The initial index of the source array. + * @param result The destination array. + * @param bitcount The left shift. + * @param anticount The left anti-shift, e.g., 32-bitcount. + * @param prev The prior source value. + */ + /*@ + @ requires 0 < bitcount && bitcount < 32 && anticount == 32 - bitcount; + @ requires src.length >= idx && result.length > idx; + @ assignable result[*]; + @ ensures AP(result, \old(idx + 1)) == \old((AP(src, idx) + UNSIGNED(prev) << (idx*32)) << bitcount); + @*/ + private static void leftShift(int[] src, int idx, int result[], int bitcount, int anticount, int prev){ + for (; idx > 0; idx--) { + int v = (prev << bitcount); + prev = src[idx - 1]; + v |= (prev >>> anticount); + result[idx] = v; + } + int v = prev << bitcount; + result[0] = v; + } + + /** + * Shifts this FDBigInteger to the left. The shift is performed + * in-place unless the FDBigInteger is immutable in which case + * a new instance of FDBigInteger is returned. + * + * @param shift The number of bits to shift left. + * @return The shifted FDBigInteger. + */ + /*@ + @ requires this.value() == 0 || shift == 0; + @ assignable \nothing; + @ ensures \result == this; + @ + @ also + @ + @ requires this.value() > 0 && shift > 0 && this.isImmutable; + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() << shift); + @ + @ also + @ + @ requires this.value() > 0 && shift > 0 && this.isImmutable; + @ assignable \nothing; + @ ensures \result == this; + @ ensures \result.value() == \old(this.value() << shift); + @*/ + public FDBigInteger leftShift(int shift) { + if (shift == 0 || nWords == 0) { + return this; + } + int wordcount = shift >> 5; + int bitcount = shift & 0x1f; + if (this.isImmutable) { + if (bitcount == 0) { + return new FDBigInteger(Arrays.copyOf(data, nWords), offset + wordcount); + } else { + int anticount = 32 - bitcount; + int idx = nWords - 1; + int prev = data[idx]; + int hi = prev >>> anticount; + int[] result; + if (hi != 0) { + result = new int[nWords + 1]; + result[nWords] = hi; + } else { + result = new int[nWords]; + } + leftShift(data,idx,result,bitcount,anticount,prev); + return new FDBigInteger(result, offset + wordcount); + } + } else { + if (bitcount != 0) { + int anticount = 32 - bitcount; + if ((data[0] << bitcount) == 0) { + int idx = 0; + int prev = data[idx]; + for (; idx < nWords - 1; idx++) { + int v = (prev >>> anticount); + prev = data[idx + 1]; + v |= (prev << bitcount); + data[idx] = v; + } + int v = prev >>> anticount; + data[idx] = v; + if(v==0) { + nWords--; + } + offset++; + } else { + int idx = nWords - 1; + int prev = data[idx]; + int hi = prev >>> anticount; + int[] result = data; + int[] src = data; + if (hi != 0) { + if(nWords == data.length) { + data = result = new int[nWords + 1]; + } + result[nWords++] = hi; + } + leftShift(src,idx,result,bitcount,anticount,prev); + } + } + offset += wordcount; + return this; + } + } + + /** + * Returns the number of ints this FDBigInteger represents. + * + * @return Number of ints required to represent this FDBigInteger. + */ + /*@ + @ requires this.value() == 0; + @ ensures \result == 0; + @ + @ also + @ + @ requires this.value() > 0; + @ ensures ((\bigint)1) << (\result - 1) <= this.value() && this.value() <= ((\bigint)1) << \result; + @*/ + private /*@ pure @*/ int size() { + return nWords + offset; + } + + + /** + * Computes + *

+     * q = (int)( this / S )
+     * this = 10 * ( this mod S )
+     * Return q.
+     * 
+ * This is the iteration step of digit development for output. + * We assume that S has been normalized, as above, and that + * "this" has been left-shifted accordingly. + * Also assumed, of course, is that the result, q, can be expressed + * as an integer, {@code 0 <= q < 10}. + * + * @param S The divisor of this FDBigInteger. + * @return q = (int)(this / S). + */ + /*@ + @ requires !this.isImmutable; + @ requires this.size() <= S.size(); + @ requires this.data.length + this.offset >= S.size(); + @ requires S.value() >= ((\bigint)1) << (S.size()*32 - 4); + @ assignable this.nWords, this.offset, this.data, this.data[*]; + @ ensures \result == \old(this.value() / S.value()); + @ ensures this.value() == \old(10 * (this.value() % S.value())); + @*/ + public int quoRemIteration(FDBigInteger S) throws IllegalArgumentException { + assert !this.isImmutable : "cannot modify immutable value"; + // ensure that this and S have the same number of + // digits. If S is properly normalized and q < 10 then + // this must be so. + int thSize = this.size(); + int sSize = S.size(); + if (thSize < sSize) { + // this value is significantly less than S, result of division is zero. + // just mult this by 10. + int p = multAndCarryBy10(this.data, this.nWords, this.data); + if(p!=0) { + this.data[nWords++] = p; + } else { + trimLeadingZeros(); + } + return 0; + } else if (thSize > sSize) { + throw new IllegalArgumentException("disparate values"); + } + // estimate q the obvious way. We will usually be + // right. If not, then we're only off by a little and + // will re-add. + long q = (this.data[this.nWords - 1] & LONG_MASK) / (S.data[S.nWords - 1] & LONG_MASK); + long diff = multDiffMe(q, S); + if (diff != 0L) { + //@ assert q != 0; + //@ assert this.offset == \old(Math.min(this.offset, S.offset)); + //@ assert this.offset <= S.offset; + + // q is too big. + // add S back in until this turns +. This should + // not be very many times! + long sum = 0L; + int tStart = S.offset - this.offset; + //@ assert tStart >= 0; + int[] sd = S.data; + int[] td = this.data; + while (sum == 0L) { + for (int sIndex = 0, tIndex = tStart; tIndex < this.nWords; sIndex++, tIndex++) { + sum += (td[tIndex] & LONG_MASK) + (sd[sIndex] & LONG_MASK); + td[tIndex] = (int) sum; + sum >>>= 32; // Signed or unsigned, answer is 0 or 1 + } + // + // Originally the following line read + // "if ( sum !=0 && sum != -1 )" + // but that would be wrong, because of the + // treatment of the two values as entirely unsigned, + // it would be impossible for a carry-out to be interpreted + // as -1 -- it would have to be a single-bit carry-out, or +1. + // + assert sum == 0 || sum == 1 : sum; // carry out of division correction + q -= 1; + } + } + // finally, we can multiply this by 10. + // it cannot overflow, right, as the high-order word has + // at least 4 high-order zeros! + int p = multAndCarryBy10(this.data, this.nWords, this.data); + assert p == 0 : p; // Carry out of *10 + trimLeadingZeros(); + return (int) q; + } + + /** + * Multiplies this FDBigInteger by 10. The operation will be + * performed in place unless the FDBigInteger is immutable in + * which case a new FDBigInteger will be returned. + * + * @return The FDBigInteger multiplied by 10. + */ + /*@ + @ requires this.value() == 0; + @ assignable \nothing; + @ ensures \result == this; + @ + @ also + @ + @ requires this.value() > 0 && this.isImmutable; + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() * 10); + @ + @ also + @ + @ requires this.value() > 0 && !this.isImmutable; + @ assignable this.nWords, this.data, this.data[*]; + @ ensures \result == this; + @ ensures \result.value() == \old(this.value() * 10); + @*/ + public FDBigInteger multBy10() { + if (nWords == 0) { + return this; + } + if (isImmutable) { + int[] res = new int[nWords + 1]; + res[nWords] = multAndCarryBy10(data, nWords, res); + return new FDBigInteger(res, offset); + } else { + int p = multAndCarryBy10(this.data, this.nWords, this.data); + if (p != 0) { + if (nWords == data.length) { + if (data[0] == 0) { + System.arraycopy(data, 1, data, 0, --nWords); + offset++; + } else { + data = Arrays.copyOf(data, data.length + 1); + } + } + data[nWords++] = p; + } else { + trimLeadingZeros(); + } + return this; + } + } + + /** + * Multiplies this FDBigInteger by + * 5p5 * 2p2. The operation will be + * performed in place if possible, otherwise a new FDBigInteger + * will be returned. + * + * @param p5 The exponent of the power-of-five factor. + * @param p2 The exponent of the power-of-two factor. + * @return The multiplication result. + */ + /*@ + @ requires this.value() == 0 || p5 == 0 && p2 == 0; + @ assignable \nothing; + @ ensures \result == this; + @ + @ also + @ + @ requires this.value() > 0 && (p5 > 0 && p2 >= 0 || p5 == 0 && p2 > 0 && this.isImmutable); + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() * pow52(p5, p2)); + @ + @ also + @ + @ requires this.value() > 0 && p5 == 0 && p2 > 0 && !this.isImmutable; + @ assignable this.nWords, this.data, this.data[*]; + @ ensures \result == this; + @ ensures \result.value() == \old(this.value() * pow52(p5, p2)); + @*/ + public FDBigInteger multByPow52(int p5, int p2) { + if (this.nWords == 0) { + return this; + } + FDBigInteger res = this; + if (p5 != 0) { + int[] r; + int extraSize = (p2 != 0) ? 1 : 0; + if (p5 < SMALL_5_POW.length) { + r = new int[this.nWords + 1 + extraSize]; + mult(this.data, this.nWords, SMALL_5_POW[p5], r); + res = new FDBigInteger(r, this.offset); + } else { + FDBigInteger pow5 = big5pow(p5); + r = new int[this.nWords + pow5.size() + extraSize]; + mult(this.data, this.nWords, pow5.data, pow5.nWords, r); + res = new FDBigInteger(r, this.offset + pow5.offset); + } + } + return res.leftShift(p2); + } + + /** + * Multiplies two big integers represented as int arrays. + * + * @param s1 The first array factor. + * @param s1Len The number of elements of s1 to use. + * @param s2 The second array factor. + * @param s2Len The number of elements of s2 to use. + * @param dst The product array. + */ + /*@ + @ requires s1 != dst && s2 != dst; + @ requires s1.length >= s1Len && s2.length >= s2Len && dst.length >= s1Len + s2Len; + @ assignable dst[0 .. s1Len + s2Len - 1]; + @ ensures AP(dst, s1Len + s2Len) == \old(AP(s1, s1Len) * AP(s2, s2Len)); + @*/ + private static void mult(int[] s1, int s1Len, int[] s2, int s2Len, int[] dst) { + for (int i = 0; i < s1Len; i++) { + long v = s1[i] & LONG_MASK; + long p = 0L; + for (int j = 0; j < s2Len; j++) { + p += (dst[i + j] & LONG_MASK) + v * (s2[j] & LONG_MASK); + dst[i + j] = (int) p; + p >>>= 32; + } + dst[i + s2Len] = (int) p; + } + } + + /** + * Subtracts the supplied FDBigInteger subtrahend from this + * FDBigInteger. Assert that the result is positive. + * If the subtrahend is immutable, store the result in this(minuend). + * If this(minuend) is immutable a new FDBigInteger is created. + * + * @param subtrahend The FDBigInteger to be subtracted. + * @return This FDBigInteger less the subtrahend. + */ + /*@ + @ requires this.isImmutable; + @ requires this.value() >= subtrahend.value(); + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() - subtrahend.value()); + @ + @ also + @ + @ requires !subtrahend.isImmutable; + @ requires this.value() >= subtrahend.value(); + @ assignable this.nWords, this.offset, this.data, this.data[*]; + @ ensures \result == this; + @ ensures \result.value() == \old(this.value() - subtrahend.value()); + @*/ + public FDBigInteger leftInplaceSub(FDBigInteger subtrahend) { + assert this.size() >= subtrahend.size() : "result should be positive"; + FDBigInteger minuend; + if (this.isImmutable) { + minuend = new FDBigInteger(this.data.clone(), this.offset); + } else { + minuend = this; + } + int offsetDiff = subtrahend.offset - minuend.offset; + int[] sData = subtrahend.data; + int[] mData = minuend.data; + int subLen = subtrahend.nWords; + int minLen = minuend.nWords; + if (offsetDiff < 0) { + // need to expand minuend + int rLen = minLen - offsetDiff; + if (rLen < mData.length) { + System.arraycopy(mData, 0, mData, -offsetDiff, minLen); + Arrays.fill(mData, 0, -offsetDiff, 0); + } else { + int[] r = new int[rLen]; + System.arraycopy(mData, 0, r, -offsetDiff, minLen); + minuend.data = mData = r; + } + minuend.offset = subtrahend.offset; + minuend.nWords = minLen = rLen; + offsetDiff = 0; + } + long borrow = 0L; + int mIndex = offsetDiff; + for (int sIndex = 0; sIndex < subLen && mIndex < minLen; sIndex++, mIndex++) { + long diff = (mData[mIndex] & LONG_MASK) - (sData[sIndex] & LONG_MASK) + borrow; + mData[mIndex] = (int) diff; + borrow = diff >> 32; // signed shift + } + for (; borrow != 0 && mIndex < minLen; mIndex++) { + long diff = (mData[mIndex] & LONG_MASK) + borrow; + mData[mIndex] = (int) diff; + borrow = diff >> 32; // signed shift + } + assert borrow == 0L : borrow; // borrow out of subtract, + // result should be positive + minuend.trimLeadingZeros(); + return minuend; + } + + /** + * Subtracts the supplied FDBigInteger subtrahend from this + * FDBigInteger. Assert that the result is positive. + * If the this(minuend) is immutable, store the result in subtrahend. + * If subtrahend is immutable a new FDBigInteger is created. + * + * @param subtrahend The FDBigInteger to be subtracted. + * @return This FDBigInteger less the subtrahend. + */ + /*@ + @ requires subtrahend.isImmutable; + @ requires this.value() >= subtrahend.value(); + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() - subtrahend.value()); + @ + @ also + @ + @ requires !subtrahend.isImmutable; + @ requires this.value() >= subtrahend.value(); + @ assignable subtrahend.nWords, subtrahend.offset, subtrahend.data, subtrahend.data[*]; + @ ensures \result == subtrahend; + @ ensures \result.value() == \old(this.value() - subtrahend.value()); + @*/ + public FDBigInteger rightInplaceSub(FDBigInteger subtrahend) { + assert this.size() >= subtrahend.size() : "result should be positive"; + FDBigInteger minuend = this; + if (subtrahend.isImmutable) { + subtrahend = new FDBigInteger(subtrahend.data.clone(), subtrahend.offset); + } + int offsetDiff = minuend.offset - subtrahend.offset; + int[] sData = subtrahend.data; + int[] mData = minuend.data; + int subLen = subtrahend.nWords; + int minLen = minuend.nWords; + if (offsetDiff < 0) { + int rLen = minLen; + if (rLen < sData.length) { + System.arraycopy(sData, 0, sData, -offsetDiff, subLen); + Arrays.fill(sData, 0, -offsetDiff, 0); + } else { + int[] r = new int[rLen]; + System.arraycopy(sData, 0, r, -offsetDiff, subLen); + subtrahend.data = sData = r; + } + subtrahend.offset = minuend.offset; + subLen -= offsetDiff; + offsetDiff = 0; + } else { + int rLen = minLen + offsetDiff; + if (rLen >= sData.length) { + subtrahend.data = sData = Arrays.copyOf(sData, rLen); + } + } + //@ assert minuend == this && minuend.value() == \old(this.value()); + //@ assert mData == minuend.data && minLen == minuend.nWords; + //@ assert subtrahend.offset + subtrahend.data.length >= minuend.size(); + //@ assert sData == subtrahend.data; + //@ assert AP(subtrahend.data, subtrahend.data.length) << subtrahend.offset == \old(subtrahend.value()); + //@ assert subtrahend.offset == Math.min(\old(this.offset), minuend.offset); + //@ assert offsetDiff == minuend.offset - subtrahend.offset; + //@ assert 0 <= offsetDiff && offsetDiff + minLen <= sData.length; + int sIndex = 0; + long borrow = 0L; + for (; sIndex < offsetDiff; sIndex++) { + long diff = 0L - (sData[sIndex] & LONG_MASK) + borrow; + sData[sIndex] = (int) diff; + borrow = diff >> 32; // signed shift + } + //@ assert sIndex == offsetDiff; + for (int mIndex = 0; mIndex < minLen; sIndex++, mIndex++) { + //@ assert sIndex == offsetDiff + mIndex; + long diff = (mData[mIndex] & LONG_MASK) - (sData[sIndex] & LONG_MASK) + borrow; + sData[sIndex] = (int) diff; + borrow = diff >> 32; // signed shift + } + assert borrow == 0L : borrow; // borrow out of subtract, + // result should be positive + subtrahend.nWords = sIndex; + subtrahend.trimLeadingZeros(); + return subtrahend; + + } + + /** + * Determines whether all elements of an array are zero for all indices less + * than a given index. + * + * @param a The array to be examined. + * @param from The index strictly below which elements are to be examined. + * @return Zero if all elements in range are zero, 1 otherwise. + */ + /*@ + @ requires 0 <= from && from <= a.length; + @ ensures \result == (AP(a, from) == 0 ? 0 : 1); + @*/ + private /*@ pure @*/ static int checkZeroTail(int[] a, int from) { + while (from > 0) { + if (a[--from] != 0) { + return 1; + } + } + return 0; + } + + /** + * Compares the parameter with this FDBigInteger. Returns an + * integer accordingly as: + *
{@code
+     * > 0: this > other
+     *   0: this == other
+     * < 0: this < other
+     * }
+ * + * @param other The FDBigInteger to compare. + * @return A negative value, zero, or a positive value according to the + * result of the comparison. + */ + /*@ + @ ensures \result == (this.value() < other.value() ? -1 : this.value() > other.value() ? +1 : 0); + @*/ + public /*@ pure @*/ int cmp(FDBigInteger other) { + int aSize = nWords + offset; + int bSize = other.nWords + other.offset; + if (aSize > bSize) { + return 1; + } else if (aSize < bSize) { + return -1; + } + int aLen = nWords; + int bLen = other.nWords; + while (aLen > 0 && bLen > 0) { + int a = data[--aLen]; + int b = other.data[--bLen]; + if (a != b) { + return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; + } + } + if (aLen > 0) { + return checkZeroTail(data, aLen); + } + if (bLen > 0) { + return -checkZeroTail(other.data, bLen); + } + return 0; + } + + /** + * Compares this FDBigInteger with + * 5p5 * 2p2. + * Returns an integer accordingly as: + *
{@code
+     * > 0: this > other
+     *   0: this == other
+     * < 0: this < other
+     * }
+ * @param p5 The exponent of the power-of-five factor. + * @param p2 The exponent of the power-of-two factor. + * @return A negative value, zero, or a positive value according to the + * result of the comparison. + */ + /*@ + @ requires p5 >= 0 && p2 >= 0; + @ ensures \result == (this.value() < pow52(p5, p2) ? -1 : this.value() > pow52(p5, p2) ? +1 : 0); + @*/ + public /*@ pure @*/ int cmpPow52(int p5, int p2) { + if (p5 == 0) { + int wordcount = p2 >> 5; + int bitcount = p2 & 0x1f; + int size = this.nWords + this.offset; + if (size > wordcount + 1) { + return 1; + } else if (size < wordcount + 1) { + return -1; + } + int a = this.data[this.nWords -1]; + int b = 1 << bitcount; + if (a != b) { + return ( (a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; + } + return checkZeroTail(this.data, this.nWords - 1); + } + return this.cmp(big5pow(p5).leftShift(p2)); + } + + /** + * Compares this FDBigInteger with x + y. Returns a + * value according to the comparison as: + *
{@code
+     * -1: this <  x + y
+     *  0: this == x + y
+     *  1: this >  x + y
+     * }
+ * @param x The first addend of the sum to compare. + * @param y The second addend of the sum to compare. + * @return -1, 0, or 1 according to the result of the comparison. + */ + /*@ + @ ensures \result == (this.value() < x.value() + y.value() ? -1 : this.value() > x.value() + y.value() ? +1 : 0); + @*/ + public /*@ pure @*/ int addAndCmp(FDBigInteger x, FDBigInteger y) { + FDBigInteger big; + FDBigInteger small; + int xSize = x.size(); + int ySize = y.size(); + int bSize; + int sSize; + if (xSize >= ySize) { + big = x; + small = y; + bSize = xSize; + sSize = ySize; + } else { + big = y; + small = x; + bSize = ySize; + sSize = xSize; + } + int thSize = this.size(); + if (bSize == 0) { + return thSize == 0 ? 0 : 1; + } + if (sSize == 0) { + return this.cmp(big); + } + if (bSize > thSize) { + return -1; + } + if (bSize + 1 < thSize) { + return 1; + } + long top = (big.data[big.nWords - 1] & LONG_MASK); + if (sSize == bSize) { + top += (small.data[small.nWords - 1] & LONG_MASK); + } + if ((top >>> 32) == 0) { + if (((top + 1) >>> 32) == 0) { + // good case - no carry extension + if (bSize < thSize) { + return 1; + } + // here sum.nWords == this.nWords + long v = (this.data[this.nWords - 1] & LONG_MASK); + if (v < top) { + return -1; + } + if (v > top + 1) { + return 1; + } + } + } else { // (top>>>32)!=0 guaranteed carry extension + if (bSize + 1 > thSize) { + return -1; + } + // here sum.nWords == this.nWords + top >>>= 32; + long v = (this.data[this.nWords - 1] & LONG_MASK); + if (v < top) { + return -1; + } + if (v > top + 1) { + return 1; + } + } + return this.cmp(big.add(small)); + } + + /** + * Makes this FDBigInteger immutable. + */ + /*@ + @ assignable this.isImmutable; + @ ensures this.isImmutable; + @*/ + public void makeImmutable() { + this.isImmutable = true; + } + + /** + * Multiplies this FDBigInteger by an integer. + * + * @param i The factor by which to multiply this FDBigInteger. + * @return This FDBigInteger multiplied by an integer. + */ + /*@ + @ requires this.value() == 0; + @ assignable \nothing; + @ ensures \result == this; + @ + @ also + @ + @ requires this.value() != 0; + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() * UNSIGNED(i)); + @*/ + private FDBigInteger mult(int i) { + if (this.nWords == 0) { + return this; + } + int[] r = new int[nWords + 1]; + mult(data, nWords, i, r); + return new FDBigInteger(r, offset); + } + + /** + * Multiplies this FDBigInteger by another FDBigInteger. + * + * @param other The FDBigInteger factor by which to multiply. + * @return The product of this and the parameter FDBigIntegers. + */ + /*@ + @ requires this.value() == 0; + @ assignable \nothing; + @ ensures \result == this; + @ + @ also + @ + @ requires this.value() != 0 && other.value() == 0; + @ assignable \nothing; + @ ensures \result == other; + @ + @ also + @ + @ requires this.value() != 0 && other.value() != 0; + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() * other.value()); + @*/ + private FDBigInteger mult(FDBigInteger other) { + if (this.nWords == 0) { + return this; + } + if (this.size() == 1) { + return other.mult(data[0]); + } + if (other.nWords == 0) { + return other; + } + if (other.size() == 1) { + return this.mult(other.data[0]); + } + int[] r = new int[nWords + other.nWords]; + mult(this.data, this.nWords, other.data, other.nWords, r); + return new FDBigInteger(r, this.offset + other.offset); + } + + /** + * Adds another FDBigInteger to this FDBigInteger. + * + * @param other The FDBigInteger to add. + * @return The sum of the FDBigIntegers. + */ + /*@ + @ assignable \nothing; + @ ensures \result.value() == \old(this.value() + other.value()); + @*/ + private FDBigInteger add(FDBigInteger other) { + FDBigInteger big, small; + int bigLen, smallLen; + int tSize = this.size(); + int oSize = other.size(); + if (tSize >= oSize) { + big = this; + bigLen = tSize; + small = other; + smallLen = oSize; + } else { + big = other; + bigLen = oSize; + small = this; + smallLen = tSize; + } + int[] r = new int[bigLen + 1]; + int i = 0; + long carry = 0L; + for (; i < smallLen; i++) { + carry += (i < big.offset ? 0L : (big.data[i - big.offset] & LONG_MASK) ) + + ((i < small.offset ? 0L : (small.data[i - small.offset] & LONG_MASK))); + r[i] = (int) carry; + carry >>= 32; // signed shift. + } + for (; i < bigLen; i++) { + carry += (i < big.offset ? 0L : (big.data[i - big.offset] & LONG_MASK) ); + r[i] = (int) carry; + carry >>= 32; // signed shift. + } + r[bigLen] = (int) carry; + return new FDBigInteger(r, 0); + } + + + /** + * Multiplies a FDBigInteger by an int and adds another int. The + * result is computed in place. This method is intended only to be invoked + * from + * + * FDBigInteger(long lValue, char[] digits, int kDigits, int nDigits) + * . + * + * @param iv The factor by which to multiply this FDBigInteger. + * @param addend The value to add to the product of this + * FDBigInteger and iv. + */ + /*@ + @ requires this.value()*UNSIGNED(iv) + UNSIGNED(addend) < ((\bigint)1) << ((this.data.length + this.offset)*32); + @ assignable this.data[*]; + @ ensures this.value() == \old(this.value()*UNSIGNED(iv) + UNSIGNED(addend)); + @*/ + private /*@ helper @*/ void multAddMe(int iv, int addend) { + long v = iv & LONG_MASK; + // unroll 0th iteration, doing addition. + long p = v * (data[0] & LONG_MASK) + (addend & LONG_MASK); + data[0] = (int) p; + p >>>= 32; + for (int i = 1; i < nWords; i++) { + p += v * (data[i] & LONG_MASK); + data[i] = (int) p; + p >>>= 32; + } + if (p != 0L) { + data[nWords++] = (int) p; // will fail noisily if illegal! + } + } + + // + // original doc: + // + // do this -=q*S + // returns borrow + // + /** + * Multiplies the parameters and subtracts them from this + * FDBigInteger. + * + * @param q The integer parameter. + * @param S The FDBigInteger parameter. + * @return this - q*S. + */ + /*@ + @ ensures nWords == 0 ==> offset == 0; + @ ensures nWords > 0 ==> data[nWords - 1] != 0; + @*/ + /*@ + @ requires 0 < q && q <= (1L << 31); + @ requires data != null; + @ requires 0 <= nWords && nWords <= data.length && offset >= 0; + @ requires !this.isImmutable; + @ requires this.size() == S.size(); + @ requires this != S; + @ assignable this.nWords, this.offset, this.data, this.data[*]; + @ ensures -q <= \result && \result <= 0; + @ ensures this.size() == \old(this.size()); + @ ensures this.value() + (\result << (this.size()*32)) == \old(this.value() - q*S.value()); + @ ensures this.offset == \old(Math.min(this.offset, S.offset)); + @ ensures \old(this.offset <= S.offset) ==> this.nWords == \old(this.nWords); + @ ensures \old(this.offset <= S.offset) ==> this.offset == \old(this.offset); + @ ensures \old(this.offset <= S.offset) ==> this.data == \old(this.data); + @ + @ also + @ + @ requires q == 0; + @ assignable \nothing; + @ ensures \result == 0; + @*/ + private /*@ helper @*/ long multDiffMe(long q, FDBigInteger S) { + long diff = 0L; + if (q != 0) { + int deltaSize = S.offset - this.offset; + if (deltaSize >= 0) { + int[] sd = S.data; + int[] td = this.data; + for (int sIndex = 0, tIndex = deltaSize; sIndex < S.nWords; sIndex++, tIndex++) { + diff += (td[tIndex] & LONG_MASK) - q * (sd[sIndex] & LONG_MASK); + td[tIndex] = (int) diff; + diff >>= 32; // N.B. SIGNED shift. + } + } else { + deltaSize = -deltaSize; + int[] rd = new int[nWords + deltaSize]; + int sIndex = 0; + int rIndex = 0; + int[] sd = S.data; + for (; rIndex < deltaSize && sIndex < S.nWords; sIndex++, rIndex++) { + diff -= q * (sd[sIndex] & LONG_MASK); + rd[rIndex] = (int) diff; + diff >>= 32; // N.B. SIGNED shift. + } + int tIndex = 0; + int[] td = this.data; + for (; sIndex < S.nWords; sIndex++, tIndex++, rIndex++) { + diff += (td[tIndex] & LONG_MASK) - q * (sd[sIndex] & LONG_MASK); + rd[rIndex] = (int) diff; + diff >>= 32; // N.B. SIGNED shift. + } + this.nWords += deltaSize; + this.offset -= deltaSize; + this.data = rd; + } + } + return diff; + } + + + /** + * Multiplies by 10 a big integer represented as an array. The final carry + * is returned. + * + * @param src The array representation of the big integer. + * @param srcLen The number of elements of src to use. + * @param dst The product array. + * @return The final carry of the multiplication. + */ + /*@ + @ requires src.length >= srcLen && dst.length >= srcLen; + @ assignable dst[0 .. srcLen - 1]; + @ ensures 0 <= \result && \result < 10; + @ ensures AP(dst, srcLen) + (\result << (srcLen*32)) == \old(AP(src, srcLen) * 10); + @*/ + private static int multAndCarryBy10(int[] src, int srcLen, int[] dst) { + long carry = 0; + for (int i = 0; i < srcLen; i++) { + long product = (src[i] & LONG_MASK) * 10L + carry; + dst[i] = (int) product; + carry = product >>> 32; + } + return (int) carry; + } + + /** + * Multiplies by a constant value a big integer represented as an array. + * The constant factor is an int. + * + * @param src The array representation of the big integer. + * @param srcLen The number of elements of src to use. + * @param value The constant factor by which to multiply. + * @param dst The product array. + */ + /*@ + @ requires src.length >= srcLen && dst.length >= srcLen + 1; + @ assignable dst[0 .. srcLen]; + @ ensures AP(dst, srcLen + 1) == \old(AP(src, srcLen) * UNSIGNED(value)); + @*/ + private static void mult(int[] src, int srcLen, int value, int[] dst) { + long val = value & LONG_MASK; + long carry = 0; + for (int i = 0; i < srcLen; i++) { + long product = (src[i] & LONG_MASK) * val + carry; + dst[i] = (int) product; + carry = product >>> 32; + } + dst[srcLen] = (int) carry; + } + + /** + * Multiplies by a constant value a big integer represented as an array. + * The constant factor is a long represent as two ints. + * + * @param src The array representation of the big integer. + * @param srcLen The number of elements of src to use. + * @param v0 The lower 32 bits of the long factor. + * @param v1 The upper 32 bits of the long factor. + * @param dst The product array. + */ + /*@ + @ requires src != dst; + @ requires src.length >= srcLen && dst.length >= srcLen + 2; + @ assignable dst[0 .. srcLen + 1]; + @ ensures AP(dst, srcLen + 2) == \old(AP(src, srcLen) * (UNSIGNED(v0) + (UNSIGNED(v1) << 32))); + @*/ + private static void mult(int[] src, int srcLen, int v0, int v1, int[] dst) { + long v = v0 & LONG_MASK; + long carry = 0; + for (int j = 0; j < srcLen; j++) { + long product = v * (src[j] & LONG_MASK) + carry; + dst[j] = (int) product; + carry = product >>> 32; + } + dst[srcLen] = (int) carry; + v = v1 & LONG_MASK; + carry = 0; + for (int j = 0; j < srcLen; j++) { + long product = (dst[j + 1] & LONG_MASK) + v * (src[j] & LONG_MASK) + carry; + dst[j + 1] = (int) product; + carry = product >>> 32; + } + dst[srcLen + 1] = (int) carry; + } + + // Fails assertion for negative exponent. + /** + * Computes 5 raised to a given power. + * + * @param p The exponent of 5. + * @return 5p. + */ + private static FDBigInteger big5pow(int p) { + assert p >= 0 : p; // negative power of 5 + if (p < MAX_FIVE_POW) { + return POW_5_CACHE[p]; + } + return big5powRec(p); + } + + // slow path + /** + * Computes 5 raised to a given power. + * + * @param p The exponent of 5. + * @return 5p. + */ + private static FDBigInteger big5powRec(int p) { + if (p < MAX_FIVE_POW) { + return POW_5_CACHE[p]; + } + // construct the value. + // recursively. + int q, r; + // in order to compute 5^p, + // compute its square root, 5^(p/2) and square. + // or, let q = p / 2, r = p -q, then + // 5^p = 5^(q+r) = 5^q * 5^r + q = p >> 1; + r = p - q; + FDBigInteger bigq = big5powRec(q); + if (r < SMALL_5_POW.length) { + return bigq.mult(SMALL_5_POW[r]); + } else { + return bigq.mult(big5powRec(r)); + } + } + + // for debugging ... + /** + * Converts this FDBigInteger to a hexadecimal string. + * + * @return The hexadecimal string representation. + */ + public String toHexString(){ + if(nWords ==0) { + return "0"; + } + StringBuilder sb = new StringBuilder((nWords +offset)*8); + for(int i= nWords -1; i>=0; i--) { + String subStr = Integer.toHexString(data[i]); + for(int j = subStr.length(); j<8; j++) { + sb.append('0'); + } + sb.append(subStr); + } + for(int i=offset; i>0; i--) { + sb.append("00000000"); + } + return sb.toString(); + } + + // for debugging ... + /** + * Converts this FDBigInteger to a BigInteger. + * + * @return The BigInteger representation. + */ + public BigInteger toBigInteger() { + byte[] magnitude = new byte[nWords * 4 + 1]; + for (int i = 0; i < nWords; i++) { + int w = data[i]; + magnitude[magnitude.length - 4 * i - 1] = (byte) w; + magnitude[magnitude.length - 4 * i - 2] = (byte) (w >> 8); + magnitude[magnitude.length - 4 * i - 3] = (byte) (w >> 16); + magnitude[magnitude.length - 4 * i - 4] = (byte) (w >> 24); + } + return new BigInteger(magnitude).shiftLeft(offset * 32); + } + + // for debugging ... + /** + * Converts this FDBigInteger to a string. + * + * @return The string representation. + */ + @Override + public String toString(){ + return toBigInteger().toString(); + } +} diff --git a/tests/test_data/std/jdk/internal/math/FloatConsts.class b/tests/test_data/std/jdk/internal/math/FloatConsts.class new file mode 100644 index 00000000..3fa6e021 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatConsts.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatConsts.java b/tests/test_data/std/jdk/internal/math/FloatConsts.java new file mode 100644 index 00000000..fd304c78 --- /dev/null +++ b/tests/test_data/std/jdk/internal/math/FloatConsts.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.math; + +import static java.lang.Float.MIN_EXPONENT; +import static java.lang.Float.PRECISION; +import static java.lang.Float.SIZE; + +/** + * This class contains additional constants documenting limits of the + * {@code float} type. + * + * @author Joseph D. Darcy + */ + +public class FloatConsts { + /** + * Don't let anyone instantiate this class. + */ + private FloatConsts() {} + + /** + * The number of logical bits in the significand of a + * {@code float} number, including the implicit bit. + */ + public static final int SIGNIFICAND_WIDTH = PRECISION; + + /** + * The exponent the smallest positive {@code float} + * subnormal value would have if it could be normalized. + */ + public static final int MIN_SUB_EXPONENT = + MIN_EXPONENT - (SIGNIFICAND_WIDTH - 1); // -149 + + /** + * Bias used in representing a {@code float} exponent. + */ + public static final int EXP_BIAS = + (1 << (SIZE - SIGNIFICAND_WIDTH - 1)) - 1; // 127 + + /** + * Bit mask to isolate the sign bit of a {@code float}. + */ + public static final int SIGN_BIT_MASK = 1 << (SIZE - 1); + + /** + * Bit mask to isolate the exponent field of a {@code float}. + */ + public static final int EXP_BIT_MASK = + ((1 << (SIZE - SIGNIFICAND_WIDTH)) - 1) << (SIGNIFICAND_WIDTH - 1); + + /** + * Bit mask to isolate the significand field of a {@code float}. + */ + public static final int SIGNIF_BIT_MASK = (1 << (SIGNIFICAND_WIDTH - 1)) - 1; + + /** + * Bit mask to isolate the magnitude bits (combined exponent and + * significand fields) of a {@code float}. + */ + public static final int MAG_BIT_MASK = EXP_BIT_MASK | SIGNIF_BIT_MASK; + + static { + // verify bit masks cover all bit positions and that the bit + // masks are non-overlapping + assert(((SIGN_BIT_MASK | EXP_BIT_MASK | SIGNIF_BIT_MASK) == ~0) && + (((SIGN_BIT_MASK & EXP_BIT_MASK) == 0) && + ((SIGN_BIT_MASK & SIGNIF_BIT_MASK) == 0) && + ((EXP_BIT_MASK & SIGNIF_BIT_MASK) == 0)) && + ((SIGN_BIT_MASK | MAG_BIT_MASK) == ~0)); + } +} diff --git a/tests/test_data/std/jdk/internal/math/FloatToDecimal.class b/tests/test_data/std/jdk/internal/math/FloatToDecimal.class new file mode 100644 index 00000000..d5ecea16 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatToDecimal.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatToDecimal.java b/tests/test_data/std/jdk/internal/math/FloatToDecimal.java new file mode 100644 index 00000000..56382767 --- /dev/null +++ b/tests/test_data/std/jdk/internal/math/FloatToDecimal.java @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.math; + +import java.io.IOException; + +import static java.lang.Float.*; +import static java.lang.Integer.*; +import static java.lang.Math.multiplyHigh; +import static jdk.internal.math.MathUtils.*; + +/** + * This class exposes a method to render a {@code float} as a string. + */ +public final class FloatToDecimal { + /* + * For full details about this code see the following references: + * + * [1] Giulietti, "The Schubfach way to render doubles", + * https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb + * + * [2] IEEE Computer Society, "IEEE Standard for Floating-Point Arithmetic" + * + * [3] Bouvier & Zimmermann, "Division-Free Binary-to-Decimal Conversion" + * + * Divisions are avoided altogether for the benefit of those architectures + * that do not provide specific machine instructions or where they are slow. + * This is discussed in section 10 of [1]. + */ + + /* The precision in bits */ + static final int P = PRECISION; + + /* Exponent width in bits */ + private static final int W = (Float.SIZE - 1) - (P - 1); + + /* Minimum value of the exponent: -(2^(W-1)) - P + 3 */ + static final int Q_MIN = (-1 << (W - 1)) - P + 3; + + /* Maximum value of the exponent: 2^(W-1) - P */ + static final int Q_MAX = (1 << (W - 1)) - P; + + /* 10^(E_MIN - 1) <= MIN_VALUE < 10^E_MIN */ + static final int E_MIN = -44; + + /* 10^(E_MAX - 1) <= MAX_VALUE < 10^E_MAX */ + static final int E_MAX = 39; + + /* Threshold to detect tiny values, as in section 8.2.1 of [1] */ + static final int C_TINY = 8; + + /* The minimum and maximum k, as in section 8 of [1] */ + static final int K_MIN = -45; + static final int K_MAX = 31; + + /* H is as in section 8.1 of [1] */ + static final int H = 9; + + /* Minimum value of the significand of a normal value: 2^(P-1) */ + private static final int C_MIN = 1 << (P - 1); + + /* Mask to extract the biased exponent */ + private static final int BQ_MASK = (1 << W) - 1; + + /* Mask to extract the fraction bits */ + private static final int T_MASK = (1 << (P - 1)) - 1; + + /* Used in rop() */ + private static final long MASK_32 = (1L << 32) - 1; + + /* Used for left-to-tight digit extraction */ + private static final int MASK_28 = (1 << 28) - 1; + + private static final int NON_SPECIAL = 0; + private static final int PLUS_ZERO = 1; + private static final int MINUS_ZERO = 2; + private static final int PLUS_INF = 3; + private static final int MINUS_INF = 4; + private static final int NAN = 5; + + /* + * Room for the longer of the forms + * -ddddd.dddd H + 2 characters + * -0.00ddddddddd H + 5 characters + * -d.ddddddddE-ee H + 6 characters + * where there are H digits d + */ + public static final int MAX_CHARS = H + 6; + + private final byte[] bytes = new byte[MAX_CHARS]; + + /* Index into bytes of rightmost valid character */ + private int index; + + private FloatToDecimal() { + } + + /** + * Returns a string representation of the {@code float} + * argument. All characters mentioned below are ASCII characters. + * + * @param v the {@code float} to be converted. + * @return a string representation of the argument. + * @see Float#toString(float) + */ + public static String toString(float v) { + return new FloatToDecimal().toDecimalString(v); + } + + /** + * Appends the rendering of the {@code v} to {@code app}. + * + *

The outcome is the same as if {@code v} were first + * {@link #toString(float) rendered} and the resulting string were then + * {@link Appendable#append(CharSequence) appended} to {@code app}. + * + * @param v the {@code float} whose rendering is appended. + * @param app the {@link Appendable} to append to. + * @throws IOException If an I/O error occurs + */ + public static Appendable appendTo(float v, Appendable app) + throws IOException { + return new FloatToDecimal().appendDecimalTo(v, app); + } + + private String toDecimalString(float v) { + return switch (toDecimal(v)) { + case NON_SPECIAL -> charsToString(); + case PLUS_ZERO -> "0.0"; + case MINUS_ZERO -> "-0.0"; + case PLUS_INF -> "Infinity"; + case MINUS_INF -> "-Infinity"; + default -> "NaN"; + }; + } + + private Appendable appendDecimalTo(float v, Appendable app) + throws IOException { + switch (toDecimal(v)) { + case NON_SPECIAL: + char[] chars = new char[index + 1]; + for (int i = 0; i < chars.length; ++i) { + chars[i] = (char) bytes[i]; + } + if (app instanceof StringBuilder builder) { + return builder.append(chars); + } + if (app instanceof StringBuffer buffer) { + return buffer.append(chars); + } + for (char c : chars) { + app.append(c); + } + return app; + case PLUS_ZERO: return app.append("0.0"); + case MINUS_ZERO: return app.append("-0.0"); + case PLUS_INF: return app.append("Infinity"); + case MINUS_INF: return app.append("-Infinity"); + default: return app.append("NaN"); + } + } + + /* + * Returns + * PLUS_ZERO iff v is 0.0 + * MINUS_ZERO iff v is -0.0 + * PLUS_INF iff v is POSITIVE_INFINITY + * MINUS_INF iff v is NEGATIVE_INFINITY + * NAN iff v is NaN + */ + private int toDecimal(float v) { + /* + * For full details see references [2] and [1]. + * + * For finite v != 0, determine integers c and q such that + * |v| = c 2^q and + * Q_MIN <= q <= Q_MAX and + * either 2^(P-1) <= c < 2^P (normal) + * or 0 < c < 2^(P-1) and q = Q_MIN (subnormal) + */ + int bits = floatToRawIntBits(v); + int t = bits & T_MASK; + int bq = (bits >>> P - 1) & BQ_MASK; + if (bq < BQ_MASK) { + index = -1; + if (bits < 0) { + append('-'); + } + if (bq != 0) { + /* normal value. Here mq = -q */ + int mq = -Q_MIN + 1 - bq; + int c = C_MIN | t; + /* The fast path discussed in section 8.3 of [1] */ + if (0 < mq & mq < P) { + int f = c >> mq; + if (f << mq == c) { + return toChars(f, 0); + } + } + return toDecimal(-mq, c, 0); + } + if (t != 0) { + /* subnormal value */ + return t < C_TINY + ? toDecimal(Q_MIN, 10 * t, -1) + : toDecimal(Q_MIN, t, 0); + } + return bits == 0 ? PLUS_ZERO : MINUS_ZERO; + } + if (t != 0) { + return NAN; + } + return bits > 0 ? PLUS_INF : MINUS_INF; + } + + private int toDecimal(int q, int c, int dk) { + /* + * The skeleton corresponds to figure 7 of [1]. + * The efficient computations are those summarized in figure 9. + * Also check the appendix. + * + * Here's a correspondence between Java names and names in [1], + * expressed as approximate LaTeX source code and informally. + * Other names are identical. + * cb: \bar{c} "c-bar" + * cbr: \bar{c}_r "c-bar-r" + * cbl: \bar{c}_l "c-bar-l" + * + * vb: \bar{v} "v-bar" + * vbr: \bar{v}_r "v-bar-r" + * vbl: \bar{v}_l "v-bar-l" + * + * rop: r_o' "r-o-prime" + */ + int out = c & 0x1; + long cb = c << 2; + long cbr = cb + 2; + long cbl; + int k; + /* + * flog10pow2(e) = floor(log_10(2^e)) + * flog10threeQuartersPow2(e) = floor(log_10(3/4 2^e)) + * flog2pow10(e) = floor(log_2(10^e)) + */ + if (c != C_MIN | q == Q_MIN) { + /* regular spacing */ + cbl = cb - 2; + k = flog10pow2(q); + } else { + /* irregular spacing */ + cbl = cb - 1; + k = flog10threeQuartersPow2(q); + } + int h = q + flog2pow10(-k) + 33; + + /* g is as in the appendix */ + long g = g1(k) + 1; + + int vb = rop(g, cb << h); + int vbl = rop(g, cbl << h); + int vbr = rop(g, cbr << h); + + int s = vb >> 2; + if (s >= 100) { + /* + * For n = 9, m = 1 the table in section 10 of [1] shows + * s' = floor(s / 10) = floor(s 1_717_986_919 / 2^34) + * + * sp10 = 10 s' + * tp10 = 10 t' + * upin iff u' = sp10 10^k in Rv + * wpin iff w' = tp10 10^k in Rv + * See section 9.3 of [1]. + */ + int sp10 = 10 * (int) (s * 1_717_986_919L >>> 34); + int tp10 = sp10 + 10; + boolean upin = vbl + out <= sp10 << 2; + boolean wpin = (tp10 << 2) + out <= vbr; + if (upin != wpin) { + return toChars(upin ? sp10 : tp10, k); + } + } + + /* + * 10 <= s < 100 or s >= 100 and u', w' not in Rv + * uin iff u = s 10^k in Rv + * win iff w = t 10^k in Rv + * See section 9.3 of [1]. + */ + int t = s + 1; + boolean uin = vbl + out <= s << 2; + boolean win = (t << 2) + out <= vbr; + if (uin != win) { + /* Exactly one of u or w lies in Rv */ + return toChars(uin ? s : t, k + dk); + } + /* + * Both u and w lie in Rv: determine the one closest to v. + * See section 9.3 of [1]. + */ + int cmp = vb - (s + t << 1); + return toChars(cmp < 0 || cmp == 0 && (s & 0x1) == 0 ? s : t, k + dk); + } + + /* + * Computes rop(cp g 2^(-95)) + * See appendix and figure 11 of [1]. + */ + private static int rop(long g, long cp) { + long x1 = multiplyHigh(g, cp); + long vbp = x1 >>> 31; + return (int) (vbp | (x1 & MASK_32) + MASK_32 >>> 32); + } + + /* + * Formats the decimal f 10^e. + */ + private int toChars(int f, int e) { + /* + * For details not discussed here see section 10 of [1]. + * + * Determine len such that + * 10^(len-1) <= f < 10^len + */ + int len = flog10pow2(Integer.SIZE - numberOfLeadingZeros(f)); + if (f >= pow10(len)) { + len += 1; + } + + /* + * Let fp and ep be the original f and e, respectively. + * Transform f and e to ensure + * 10^(H-1) <= f < 10^H + * fp 10^ep = f 10^(e-H) = 0.f 10^e + */ + f *= (int)pow10(H - len); + e += len; + + /* + * The toChars?() methods perform left-to-right digits extraction + * using ints, provided that the arguments are limited to 8 digits. + * Therefore, split the H = 9 digits of f into: + * h = the most significant digit of f + * l = the last 8, least significant digits of f + * + * For n = 9, m = 8 the table in section 10 of [1] shows + * floor(f / 10^8) = floor(1_441_151_881 f / 2^57) + */ + int h = (int) (f * 1_441_151_881L >>> 57); + int l = f - 100_000_000 * h; + + if (0 < e && e <= 7) { + return toChars1(h, l, e); + } + if (-3 < e && e <= 0) { + return toChars2(h, l, e); + } + return toChars3(h, l, e); + } + + private int toChars1(int h, int l, int e) { + /* + * 0 < e <= 7: plain format without leading zeroes. + * Left-to-right digits extraction: + * algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + appendDigit(h); + int y = y(l); + int t; + int i = 1; + for (; i < e; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + append('.'); + for (; i <= 8; ++i) { + t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + removeTrailingZeroes(); + return NON_SPECIAL; + } + + private int toChars2(int h, int l, int e) { + /* -3 < e <= 0: plain format with leading zeroes */ + appendDigit(0); + append('.'); + for (; e < 0; ++e) { + appendDigit(0); + } + appendDigit(h); + append8Digits(l); + removeTrailingZeroes(); + return NON_SPECIAL; + } + + private int toChars3(int h, int l, int e) { + /* -3 >= e | e > 7: computerized scientific notation */ + appendDigit(h); + append('.'); + append8Digits(l); + removeTrailingZeroes(); + exponent(e - 1); + return NON_SPECIAL; + } + + private void append8Digits(int m) { + /* + * Left-to-right digits extraction: + * algorithm 1 in [3], with b = 10, k = 8, n = 28. + */ + int y = y(m); + for (int i = 0; i < 8; ++i) { + int t = 10 * y; + appendDigit(t >>> 28); + y = t & MASK_28; + } + } + + private void removeTrailingZeroes() { + while (bytes[index] == '0') { + --index; + } + /* ... but do not remove the one directly to the right of '.' */ + if (bytes[index] == '.') { + ++index; + } + } + + private int y(int a) { + /* + * Algorithm 1 in [3] needs computation of + * floor((a + 1) 2^n / b^k) - 1 + * with a < 10^8, b = 10, k = 8, n = 28. + * Noting that + * (a + 1) 2^n <= 10^8 2^28 < 10^17 + * For n = 17, m = 8 the table in section 10 of [1] leads to: + */ + return (int) (multiplyHigh( + (long) (a + 1) << 28, + 193_428_131_138_340_668L) >>> 20) - 1; + } + + private void exponent(int e) { + append('E'); + if (e < 0) { + append('-'); + e = -e; + } + if (e < 10) { + appendDigit(e); + return; + } + /* + * For n = 2, m = 1 the table in section 10 of [1] shows + * floor(e / 10) = floor(103 e / 2^10) + */ + int d = e * 103 >>> 10; + appendDigit(d); + appendDigit(e - 10 * d); + } + + private void append(int c) { + bytes[++index] = (byte) c; + } + + private void appendDigit(int d) { + bytes[++index] = (byte) ('0' + d); + } + + /* Using the deprecated constructor enhances performance */ + @SuppressWarnings("deprecation") + private String charsToString() { + return new String(bytes, 0, 0, index + 1); + } + +} diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$1.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$1.class new file mode 100644 index 00000000..dda8ceae Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$1.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer.class new file mode 100644 index 00000000..dbfae36d Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$ASCIIToBinaryBuffer.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter.class new file mode 100644 index 00000000..52b0dc46 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$ASCIIToBinaryConverter.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer.class new file mode 100644 index 00000000..d38e9a11 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$BinaryToASCIIConverter.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$BinaryToASCIIConverter.class new file mode 100644 index 00000000..8fabf9f1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$BinaryToASCIIConverter.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer.class new file mode 100644 index 00000000..5f471ac6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$HexFloatPattern.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$HexFloatPattern.class new file mode 100644 index 00000000..3d4f843f Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$HexFloatPattern.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer.class new file mode 100644 index 00000000..5a856fe0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal$PreparedASCIIToBinaryBuffer.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal.class b/tests/test_data/std/jdk/internal/math/FloatingDecimal.class new file mode 100644 index 00000000..10495870 Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FloatingDecimal.class differ diff --git a/tests/test_data/std/jdk/internal/math/FloatingDecimal.java b/tests/test_data/std/jdk/internal/math/FloatingDecimal.java new file mode 100644 index 00000000..408b98ac --- /dev/null +++ b/tests/test_data/std/jdk/internal/math/FloatingDecimal.java @@ -0,0 +1,2551 @@ +/* + * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.math; + +import java.util.Arrays; +import java.util.regex.*; + +/** + * A class for converting between ASCII and decimal representations of a single + * or double precision floating point number. Most conversions are provided via + * static convenience methods, although a BinaryToASCIIConverter + * instance may be obtained and reused. + */ +public class FloatingDecimal{ + // + // Constants of the implementation; + // most are IEEE-754 related. + // (There are more really boring constants at the end.) + // + static final int EXP_SHIFT = DoubleConsts.SIGNIFICAND_WIDTH - 1; + static final long FRACT_HOB = ( 1L<String. + * + * @param d The double precision value. + * @return The value converted to a String. + */ + public static String toJavaFormatString(double d) { + return getBinaryToASCIIConverter(d).toJavaFormatString(); + } + + /** + * Converts a single precision floating point value to a String. + * + * @param f The single precision value. + * @return The value converted to a String. + */ + public static String toJavaFormatString(float f) { + return getBinaryToASCIIConverter(f).toJavaFormatString(); + } + + /** + * Appends a double precision floating point value to an Appendable. + * @param d The double precision value. + * @param buf The Appendable with the value appended. + */ + public static void appendTo(double d, Appendable buf) { + getBinaryToASCIIConverter(d).appendTo(buf); + } + + /** + * Appends a single precision floating point value to an Appendable. + * @param f The single precision value. + * @param buf The Appendable with the value appended. + */ + public static void appendTo(float f, Appendable buf) { + getBinaryToASCIIConverter(f).appendTo(buf); + } + + /** + * Converts a String to a double precision floating point value. + * + * @param s The String to convert. + * @return The double precision value. + * @throws NumberFormatException If the String does not + * represent a properly formatted double precision value. + */ + public static double parseDouble(String s) throws NumberFormatException { + return readJavaFormatString(s).doubleValue(); + } + + /** + * Converts a String to a single precision floating point value. + * + * @param s The String to convert. + * @return The single precision value. + * @throws NumberFormatException If the String does not + * represent a properly formatted single precision value. + */ + public static float parseFloat(String s) throws NumberFormatException { + return readJavaFormatString(s).floatValue(); + } + + /** + * A converter which can process single or double precision floating point + * values into an ASCII String representation. + */ + public interface BinaryToASCIIConverter { + /** + * Converts a floating point value into an ASCII String. + * @return The value converted to a String. + */ + String toJavaFormatString(); + + /** + * Appends a floating point value to an Appendable. + * @param buf The Appendable to receive the value. + */ + void appendTo(Appendable buf); + + /** + * Retrieves the decimal exponent most closely corresponding to this value. + * @return The decimal exponent. + */ + int getDecimalExponent(); + + /** + * Retrieves the value as an array of digits. + * @param digits The digit array. + * @return The number of valid digits copied into the array. + */ + int getDigits(char[] digits); + + /** + * Indicates the sign of the value. + * @return {@code value < 0.0}. + */ + boolean isNegative(); + + /** + * Indicates whether the value is either infinite or not a number. + * + * @return true if and only if the value is NaN + * or infinite. + */ + boolean isExceptional(); + + /** + * Indicates whether the value was rounded up during the binary to ASCII + * conversion. + * + * @return true if and only if the value was rounded up. + */ + boolean digitsRoundedUp(); + + /** + * Indicates whether the binary to ASCII conversion was exact. + * + * @return true if any only if the conversion was exact. + */ + boolean decimalDigitsExact(); + } + + /** + * A BinaryToASCIIConverter which represents NaN + * and infinite values. + */ + private static class ExceptionalBinaryToASCIIBuffer implements BinaryToASCIIConverter { + private final String image; + private boolean isNegative; + + public ExceptionalBinaryToASCIIBuffer(String image, boolean isNegative) { + this.image = image; + this.isNegative = isNegative; + } + + @Override + public String toJavaFormatString() { + return image; + } + + @Override + public void appendTo(Appendable buf) { + if (buf instanceof StringBuilder) { + ((StringBuilder) buf).append(image); + } else if (buf instanceof StringBuffer) { + ((StringBuffer) buf).append(image); + } else { + assert false; + } + } + + @Override + public int getDecimalExponent() { + throw new IllegalArgumentException("Exceptional value does not have an exponent"); + } + + @Override + public int getDigits(char[] digits) { + throw new IllegalArgumentException("Exceptional value does not have digits"); + } + + @Override + public boolean isNegative() { + return isNegative; + } + + @Override + public boolean isExceptional() { + return true; + } + + @Override + public boolean digitsRoundedUp() { + throw new IllegalArgumentException("Exceptional value is not rounded"); + } + + @Override + public boolean decimalDigitsExact() { + throw new IllegalArgumentException("Exceptional value is not exact"); + } + } + + private static final String INFINITY_REP = "Infinity"; + private static final int INFINITY_LENGTH = INFINITY_REP.length(); + private static final String NAN_REP = "NaN"; + private static final int NAN_LENGTH = NAN_REP.length(); + + private static final BinaryToASCIIConverter B2AC_POSITIVE_INFINITY = new ExceptionalBinaryToASCIIBuffer(INFINITY_REP, false); + private static final BinaryToASCIIConverter B2AC_NEGATIVE_INFINITY = new ExceptionalBinaryToASCIIBuffer("-" + INFINITY_REP, true); + private static final BinaryToASCIIConverter B2AC_NOT_A_NUMBER = new ExceptionalBinaryToASCIIBuffer(NAN_REP, false); + private static final BinaryToASCIIConverter B2AC_POSITIVE_ZERO = new BinaryToASCIIBuffer(false, new char[]{'0'}); + private static final BinaryToASCIIConverter B2AC_NEGATIVE_ZERO = new BinaryToASCIIBuffer(true, new char[]{'0'}); + + /** + * A buffered implementation of BinaryToASCIIConverter. + */ + static class BinaryToASCIIBuffer implements BinaryToASCIIConverter { + private boolean isNegative; + private int decExponent; + private int firstDigitIndex; + private int nDigits; + private final char[] digits; + private final char[] buffer = new char[26]; + + // + // The fields below provide additional information about the result of + // the binary to decimal digits conversion done in dtoa() and roundup() + // methods. They are changed if needed by those two methods. + // + + // True if the dtoa() binary to decimal conversion was exact. + private boolean exactDecimalConversion = false; + + // True if the result of the binary to decimal conversion was rounded-up + // at the end of the conversion process, i.e. roundUp() method was called. + private boolean decimalDigitsRoundedUp = false; + + /** + * Default constructor; used for non-zero values, + * BinaryToASCIIBuffer may be thread-local and reused + */ + BinaryToASCIIBuffer(){ + this.digits = new char[20]; + } + + /** + * Creates a specialized value (positive and negative zeros). + */ + BinaryToASCIIBuffer(boolean isNegative, char[] digits){ + this.isNegative = isNegative; + this.decExponent = 0; + this.digits = digits; + this.firstDigitIndex = 0; + this.nDigits = digits.length; + } + + @Override + public String toJavaFormatString() { + int len = getChars(buffer); + return new String(buffer, 0, len); + } + + @Override + public void appendTo(Appendable buf) { + int len = getChars(buffer); + if (buf instanceof StringBuilder) { + ((StringBuilder) buf).append(buffer, 0, len); + } else if (buf instanceof StringBuffer) { + ((StringBuffer) buf).append(buffer, 0, len); + } else { + assert false; + } + } + + @Override + public int getDecimalExponent() { + return decExponent; + } + + @Override + public int getDigits(char[] digits) { + System.arraycopy(this.digits, firstDigitIndex, digits, 0, this.nDigits); + return this.nDigits; + } + + @Override + public boolean isNegative() { + return isNegative; + } + + @Override + public boolean isExceptional() { + return false; + } + + @Override + public boolean digitsRoundedUp() { + return decimalDigitsRoundedUp; + } + + @Override + public boolean decimalDigitsExact() { + return exactDecimalConversion; + } + + private void setSign(boolean isNegative) { + this.isNegative = isNegative; + } + + /** + * This is the easy subcase -- + * all the significant bits, after scaling, are held in lvalue. + * negSign and decExponent tell us what processing and scaling + * has already been done. Exceptional cases have already been + * stripped out. + * In particular: + * lvalue is a finite number (not Inf, nor NaN) + * lvalue > 0L (not zero, nor negative). + * + * The only reason that we develop the digits here, rather than + * calling on Long.toString() is that we can do it a little faster, + * and besides want to treat trailing 0s specially. If Long.toString + * changes, we should re-evaluate this strategy! + */ + private void developLongDigits( int decExponent, long lvalue, int insignificantDigits ){ + if ( insignificantDigits != 0 ){ + // Discard non-significant low-order bits, while rounding, + // up to insignificant value. + long pow10 = FDBigInteger.LONG_5_POW[insignificantDigits] << insignificantDigits; // 10^i == 5^i * 2^i; + long residue = lvalue % pow10; + lvalue /= pow10; + decExponent += insignificantDigits; + if ( residue >= (pow10>>1) ){ + // round up based on the low-order bits we're discarding + lvalue++; + } + } + int digitno = digits.length -1; + int c; + if ( lvalue <= Integer.MAX_VALUE ){ + assert lvalue > 0L : lvalue; // lvalue <= 0 + // even easier subcase! + // can do int arithmetic rather than long! + int ivalue = (int)lvalue; + c = ivalue%10; + ivalue /= 10; + while ( c == 0 ){ + decExponent++; + c = ivalue%10; + ivalue /= 10; + } + while ( ivalue != 0){ + digits[digitno--] = (char)(c+'0'); + decExponent++; + c = ivalue%10; + ivalue /= 10; + } + digits[digitno] = (char)(c+'0'); + } else { + // same algorithm as above (same bugs, too ) + // but using long arithmetic. + c = (int)(lvalue%10L); + lvalue /= 10L; + while ( c == 0 ){ + decExponent++; + c = (int)(lvalue%10L); + lvalue /= 10L; + } + while ( lvalue != 0L ){ + digits[digitno--] = (char)(c+'0'); + decExponent++; + c = (int)(lvalue%10L); + lvalue /= 10; + } + digits[digitno] = (char)(c+'0'); + } + this.decExponent = decExponent+1; + this.firstDigitIndex = digitno; + this.nDigits = this.digits.length - digitno; + } + + private void dtoa( int binExp, long fractBits, int nSignificantBits, boolean isCompatibleFormat) + { + assert fractBits > 0 ; // fractBits here can't be zero or negative + assert (fractBits & FRACT_HOB)!=0 ; // Hi-order bit should be set + // Examine number. Determine if it is an easy case, + // which we can do pretty trivially using float/long conversion, + // or whether we must do real work. + final int tailZeros = Long.numberOfTrailingZeros(fractBits); + + // number of significant bits of fractBits; + final int nFractBits = EXP_SHIFT+1-tailZeros; + + // reset flags to default values as dtoa() does not always set these + // flags and a prior call to dtoa() might have set them to incorrect + // values with respect to the current state. + decimalDigitsRoundedUp = false; + exactDecimalConversion = false; + + // number of significant bits to the right of the point. + int nTinyBits = Math.max( 0, nFractBits - binExp - 1 ); + if ( binExp <= MAX_SMALL_BIN_EXP && binExp >= MIN_SMALL_BIN_EXP ){ + // Look more closely at the number to decide if, + // with scaling by 10^nTinyBits, the result will fit in + // a long. + if ( (nTinyBits < FDBigInteger.LONG_5_POW.length) && ((nFractBits + N_5_BITS[nTinyBits]) < 64 ) ){ + // + // We can do this: + // take the fraction bits, which are normalized. + // (a) nTinyBits == 0: Shift left or right appropriately + // to align the binary point at the extreme right, i.e. + // where a long int point is expected to be. The integer + // result is easily converted to a string. + // (b) nTinyBits > 0: Shift right by EXP_SHIFT-nFractBits, + // which effectively converts to long and scales by + // 2^nTinyBits. Then multiply by 5^nTinyBits to + // complete the scaling. We know this won't overflow + // because we just counted the number of bits necessary + // in the result. The integer you get from this can + // then be converted to a string pretty easily. + // + if ( nTinyBits == 0 ) { + int insignificant; + if ( binExp > nSignificantBits ){ + insignificant = insignificantDigitsForPow2(binExp-nSignificantBits-1); + } else { + insignificant = 0; + } + if ( binExp >= EXP_SHIFT ){ + fractBits <<= (binExp-EXP_SHIFT); + } else { + fractBits >>>= (EXP_SHIFT-binExp) ; + } + developLongDigits( 0, fractBits, insignificant ); + return; + } + // + // The following causes excess digits to be printed + // out in the single-float case. Our manipulation of + // halfULP here is apparently not correct. If we + // better understand how this works, perhaps we can + // use this special case again. But for the time being, + // we do not. + // else { + // fractBits >>>= EXP_SHIFT+1-nFractBits; + // fractBits//= long5pow[ nTinyBits ]; + // halfULP = long5pow[ nTinyBits ] >> (1+nSignificantBits-nFractBits); + // developLongDigits( -nTinyBits, fractBits, insignificantDigits(halfULP) ); + // return; + // } + // + } + } + // + // This is the hard case. We are going to compute large positive + // integers B and S and integer decExp, s.t. + // d = ( B / S )// 10^decExp + // 1 <= B / S < 10 + // Obvious choices are: + // decExp = floor( log10(d) ) + // B = d// 2^nTinyBits// 10^max( 0, -decExp ) + // S = 10^max( 0, decExp)// 2^nTinyBits + // (noting that nTinyBits has already been forced to non-negative) + // I am also going to compute a large positive integer + // M = (1/2^nSignificantBits)// 2^nTinyBits// 10^max( 0, -decExp ) + // i.e. M is (1/2) of the ULP of d, scaled like B. + // When we iterate through dividing B/S and picking off the + // quotient bits, we will know when to stop when the remainder + // is <= M. + // + // We keep track of powers of 2 and powers of 5. + // + int decExp = estimateDecExp(fractBits,binExp); + int B2, B5; // powers of 2 and powers of 5, respectively, in B + int S2, S5; // powers of 2 and powers of 5, respectively, in S + int M2, M5; // powers of 2 and powers of 5, respectively, in M + + B5 = Math.max( 0, -decExp ); + B2 = B5 + nTinyBits + binExp; + + S5 = Math.max( 0, decExp ); + S2 = S5 + nTinyBits; + + M5 = B5; + M2 = B2 - nSignificantBits; + + // + // the long integer fractBits contains the (nFractBits) interesting + // bits from the mantissa of d ( hidden 1 added if necessary) followed + // by (EXP_SHIFT+1-nFractBits) zeros. In the interest of compactness, + // I will shift out those zeros before turning fractBits into a + // FDBigInteger. The resulting whole number will be + // d * 2^(nFractBits-1-binExp). + // + fractBits >>>= tailZeros; + B2 -= nFractBits-1; + int common2factor = Math.min( B2, S2 ); + B2 -= common2factor; + S2 -= common2factor; + M2 -= common2factor; + + // + // HACK!! For exact powers of two, the next smallest number + // is only half as far away as we think (because the meaning of + // ULP changes at power-of-two bounds) for this reason, we + // hack M2. Hope this works. + // + if ( nFractBits == 1 ) { + M2 -= 1; + } + + if ( M2 < 0 ){ + // oops. + // since we cannot scale M down far enough, + // we must scale the other values up. + B2 -= M2; + S2 -= M2; + M2 = 0; + } + // + // Construct, Scale, iterate. + // Some day, we'll write a stopping test that takes + // account of the asymmetry of the spacing of floating-point + // numbers below perfect powers of 2 + // 26 Sept 96 is not that day. + // So we use a symmetric test. + // + int ndigit = 0; + boolean low, high; + long lowDigitDifference; + int q; + + // + // Detect the special cases where all the numbers we are about + // to compute will fit in int or long integers. + // In these cases, we will avoid doing FDBigInteger arithmetic. + // We use the same algorithms, except that we "normalize" + // our FDBigIntegers before iterating. This is to make division easier, + // as it makes our fist guess (quotient of high-order words) + // more accurate! + // + // Some day, we'll write a stopping test that takes + // account of the asymmetry of the spacing of floating-point + // numbers below perfect powers of 2 + // 26 Sept 96 is not that day. + // So we use a symmetric test. + // + // binary digits needed to represent B, approx. + int Bbits = nFractBits + B2 + (( B5 < N_5_BITS.length )? N_5_BITS[B5] : ( B5*3 )); + + // binary digits needed to represent 10*S, approx. + int tenSbits = S2+1 + (( (S5+1) < N_5_BITS.length )? N_5_BITS[(S5+1)] : ( (S5+1)*3 )); + if ( Bbits < 64 && tenSbits < 64){ + if ( Bbits < 32 && tenSbits < 32){ + // wa-hoo! They're all ints! + int b = ((int)fractBits * FDBigInteger.SMALL_5_POW[B5] ) << B2; + int s = FDBigInteger.SMALL_5_POW[S5] << S2; + int m = FDBigInteger.SMALL_5_POW[M5] << M2; + int tens = s * 10; + // + // Unroll the first iteration. If our decExp estimate + // was too high, our first quotient will be zero. In this + // case, we discard it and decrement decExp. + // + ndigit = 0; + q = b / s; + b = 10 * ( b % s ); + m *= 10; + low = (b < m ); + high = (b+m > tens ); + assert q < 10 : q; // excessively large digit + if ( (q == 0) && ! high ){ + // oops. Usually ignore leading zero. + decExp--; + } else { + digits[ndigit++] = (char)('0' + q); + } + // + // HACK! Java spec sez that we always have at least + // one digit after the . in either F- or E-form output. + // Thus we will need more than one digit if we're using + // E-form + // + if ( !isCompatibleFormat ||decExp < -3 || decExp >= 8 ){ + high = low = false; + } + while( ! low && ! high ){ + q = b / s; + b = 10 * ( b % s ); + m *= 10; + assert q < 10 : q; // excessively large digit + if ( m > 0L ){ + low = (b < m ); + high = (b+m > tens ); + } else { + // hack -- m might overflow! + // in this case, it is certainly > b, + // which won't + // and b+m > tens, too, since that has overflowed + // either! + low = true; + high = true; + } + digits[ndigit++] = (char)('0' + q); + } + lowDigitDifference = (b<<1) - tens; + exactDecimalConversion = (b == 0); + } else { + // still good! they're all longs! + long b = (fractBits * FDBigInteger.LONG_5_POW[B5] ) << B2; + long s = FDBigInteger.LONG_5_POW[S5] << S2; + long m = FDBigInteger.LONG_5_POW[M5] << M2; + long tens = s * 10L; + // + // Unroll the first iteration. If our decExp estimate + // was too high, our first quotient will be zero. In this + // case, we discard it and decrement decExp. + // + ndigit = 0; + q = (int) ( b / s ); + b = 10L * ( b % s ); + m *= 10L; + low = (b < m ); + high = (b+m > tens ); + assert q < 10 : q; // excessively large digit + if ( (q == 0) && ! high ){ + // oops. Usually ignore leading zero. + decExp--; + } else { + digits[ndigit++] = (char)('0' + q); + } + // + // HACK! Java spec sez that we always have at least + // one digit after the . in either F- or E-form output. + // Thus we will need more than one digit if we're using + // E-form + // + if ( !isCompatibleFormat || decExp < -3 || decExp >= 8 ){ + high = low = false; + } + while( ! low && ! high ){ + q = (int) ( b / s ); + b = 10 * ( b % s ); + m *= 10; + assert q < 10 : q; // excessively large digit + if ( m > 0L ){ + low = (b < m ); + high = (b+m > tens ); + } else { + // hack -- m might overflow! + // in this case, it is certainly > b, + // which won't + // and b+m > tens, too, since that has overflowed + // either! + low = true; + high = true; + } + digits[ndigit++] = (char)('0' + q); + } + lowDigitDifference = (b<<1) - tens; + exactDecimalConversion = (b == 0); + } + } else { + // + // We really must do FDBigInteger arithmetic. + // Fist, construct our FDBigInteger initial values. + // + FDBigInteger Sval = FDBigInteger.valueOfPow52(S5, S2); + int shiftBias = Sval.getNormalizationBias(); + Sval = Sval.leftShift(shiftBias); // normalize so that division works better + + FDBigInteger Bval = FDBigInteger.valueOfMulPow52(fractBits, B5, B2 + shiftBias); + FDBigInteger Mval = FDBigInteger.valueOfPow52(M5 + 1, M2 + shiftBias + 1); + + FDBigInteger tenSval = FDBigInteger.valueOfPow52(S5 + 1, S2 + shiftBias + 1); //Sval.mult( 10 ); + // + // Unroll the first iteration. If our decExp estimate + // was too high, our first quotient will be zero. In this + // case, we discard it and decrement decExp. + // + ndigit = 0; + q = Bval.quoRemIteration( Sval ); + low = (Bval.cmp( Mval ) < 0); + high = tenSval.addAndCmp(Bval,Mval)<=0; + + assert q < 10 : q; // excessively large digit + if ( (q == 0) && ! high ){ + // oops. Usually ignore leading zero. + decExp--; + } else { + digits[ndigit++] = (char)('0' + q); + } + // + // HACK! Java spec sez that we always have at least + // one digit after the . in either F- or E-form output. + // Thus we will need more than one digit if we're using + // E-form + // + if (!isCompatibleFormat || decExp < -3 || decExp >= 8 ){ + high = low = false; + } + while( ! low && ! high ){ + q = Bval.quoRemIteration( Sval ); + assert q < 10 : q; // excessively large digit + Mval = Mval.multBy10(); //Mval = Mval.mult( 10 ); + low = (Bval.cmp( Mval ) < 0); + high = tenSval.addAndCmp(Bval,Mval)<=0; + digits[ndigit++] = (char)('0' + q); + } + if ( high && low ){ + Bval = Bval.leftShift(1); + lowDigitDifference = Bval.cmp(tenSval); + } else { + lowDigitDifference = 0L; // this here only for flow analysis! + } + exactDecimalConversion = (Bval.cmp( FDBigInteger.ZERO ) == 0); + } + this.decExponent = decExp+1; + this.firstDigitIndex = 0; + this.nDigits = ndigit; + // + // Last digit gets rounded based on stopping condition. + // + if ( high ){ + if ( low ){ + if ( lowDigitDifference == 0L ){ + // it's a tie! + // choose based on which digits we like. + if ( (digits[firstDigitIndex+nDigits-1]&1) != 0 ) { + roundup(); + } + } else if ( lowDigitDifference > 0 ){ + roundup(); + } + } else { + roundup(); + } + } + } + + // add one to the least significant digit. + // in the unlikely event there is a carry out, deal with it. + // assert that this will only happen where there + // is only one digit, e.g. (float)1e-44 seems to do it. + // + private void roundup() { + int i = (firstDigitIndex + nDigits - 1); + int q = digits[i]; + if (q == '9') { + while (q == '9' && i > firstDigitIndex) { + digits[i] = '0'; + q = digits[--i]; + } + if (q == '9') { + // carryout! High-order 1, rest 0s, larger exp. + decExponent += 1; + digits[firstDigitIndex] = '1'; + return; + } + // else fall through. + } + digits[i] = (char) (q + 1); + decimalDigitsRoundedUp = true; + } + + /** + * Estimate decimal exponent. (If it is small-ish, + * we could double-check.) + * + * First, scale the mantissa bits such that 1 <= d2 < 2. + * We are then going to estimate + * log10(d2) ~=~ (d2-1.5)/1.5 + log(1.5) + * and so we can estimate + * log10(d) ~=~ log10(d2) + binExp * log10(2) + * take the floor and call it decExp. + */ + static int estimateDecExp(long fractBits, int binExp) { + double d2 = Double.longBitsToDouble( EXP_ONE | ( fractBits & DoubleConsts.SIGNIF_BIT_MASK ) ); + double d = (d2-1.5D)*0.289529654D + 0.176091259 + (double)binExp * 0.301029995663981; + long dBits = Double.doubleToRawLongBits(d); //can't be NaN here so use raw + int exponent = (int)((dBits & DoubleConsts.EXP_BIT_MASK) >> EXP_SHIFT) - DoubleConsts.EXP_BIAS; + boolean isNegative = (dBits & DoubleConsts.SIGN_BIT_MASK) != 0; // discover sign + if(exponent>=0 && exponent<52) { // hot path + long mask = DoubleConsts.SIGNIF_BIT_MASK >> exponent; + int r = (int)(( (dBits&DoubleConsts.SIGNIF_BIT_MASK) | FRACT_HOB )>>(EXP_SHIFT-exponent)); + return isNegative ? (((mask & dBits) == 0L ) ? -r : -r-1 ) : r; + } else if (exponent < 0) { + return (((dBits&~DoubleConsts.SIGN_BIT_MASK) == 0) ? 0 : + ( (isNegative) ? -1 : 0) ); + } else { //if (exponent >= 52) + return (int)d; + } + } + + private static int insignificantDigits(long insignificant) { + int i; + for ( i = 0; insignificant >= 10L; i++ ) { + insignificant /= 10L; + } + return i; + } + + /** + * Calculates + *

+         * insignificantDigitsForPow2(v) == insignificantDigits(1L<
+         */
+        private static int insignificantDigitsForPow2(int p2) {
+            if (p2 > 1 && p2 < insignificantDigitsNumber.length) {
+                return insignificantDigitsNumber[p2];
+            }
+            return 0;
+        }
+
+        /**
+         *  If insignificant==(1L << ixd)
+         *  i = insignificantDigitsNumber[idx] is the same as:
+         *  int i;
+         *  for ( i = 0; insignificant >= 10L; i++ )
+         *         insignificant /= 10L;
+         */
+        private static final int[] insignificantDigitsNumber = {
+            0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3,
+            4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7,
+            8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, 11,
+            12, 12, 12, 12, 13, 13, 13, 14, 14, 14,
+            15, 15, 15, 15, 16, 16, 16, 17, 17, 17,
+            18, 18, 18, 19
+        };
+
+        // approximately ceil( log2( long5pow[i] ) )
+        private static final int[] N_5_BITS = {
+                0,
+                3,
+                5,
+                7,
+                10,
+                12,
+                14,
+                17,
+                19,
+                21,
+                24,
+                26,
+                28,
+                31,
+                33,
+                35,
+                38,
+                40,
+                42,
+                45,
+                47,
+                49,
+                52,
+                54,
+                56,
+                59,
+                61,
+        };
+
+        private int getChars(char[] result) {
+            assert nDigits <= 19 : nDigits; // generous bound on size of nDigits
+            int i = 0;
+            if (isNegative) {
+                result[0] = '-';
+                i = 1;
+            }
+            if (decExponent > 0 && decExponent < 8) {
+                // print digits.digits.
+                int charLength = Math.min(nDigits, decExponent);
+                System.arraycopy(digits, firstDigitIndex, result, i, charLength);
+                i += charLength;
+                if (charLength < decExponent) {
+                    charLength = decExponent - charLength;
+                    Arrays.fill(result,i,i+charLength,'0');
+                    i += charLength;
+                    result[i++] = '.';
+                    result[i++] = '0';
+                } else {
+                    result[i++] = '.';
+                    if (charLength < nDigits) {
+                        int t = nDigits - charLength;
+                        System.arraycopy(digits, firstDigitIndex+charLength, result, i, t);
+                        i += t;
+                    } else {
+                        result[i++] = '0';
+                    }
+                }
+            } else if (decExponent <= 0 && decExponent > -3) {
+                result[i++] = '0';
+                result[i++] = '.';
+                if (decExponent != 0) {
+                    Arrays.fill(result, i, i-decExponent, '0');
+                    i -= decExponent;
+                }
+                System.arraycopy(digits, firstDigitIndex, result, i, nDigits);
+                i += nDigits;
+            } else {
+                result[i++] = digits[firstDigitIndex];
+                result[i++] = '.';
+                if (nDigits > 1) {
+                    System.arraycopy(digits, firstDigitIndex+1, result, i, nDigits - 1);
+                    i += nDigits - 1;
+                } else {
+                    result[i++] = '0';
+                }
+                result[i++] = 'E';
+                int e;
+                if (decExponent <= 0) {
+                    result[i++] = '-';
+                    e = -decExponent + 1;
+                } else {
+                    e = decExponent - 1;
+                }
+                // decExponent has 1, 2, or 3, digits
+                if (e <= 9) {
+                    result[i++] = (char) (e + '0');
+                } else if (e <= 99) {
+                    result[i++] = (char) (e / 10 + '0');
+                    result[i++] = (char) (e % 10 + '0');
+                } else {
+                    result[i++] = (char) (e / 100 + '0');
+                    e %= 100;
+                    result[i++] = (char) (e / 10 + '0');
+                    result[i++] = (char) (e % 10 + '0');
+                }
+            }
+            return i;
+        }
+
+    }
+
+    private static final ThreadLocal threadLocalBinaryToASCIIBuffer =
+            new ThreadLocal() {
+                @Override
+                protected BinaryToASCIIBuffer initialValue() {
+                    return new BinaryToASCIIBuffer();
+                }
+            };
+
+    private static BinaryToASCIIBuffer getBinaryToASCIIBuffer() {
+        return threadLocalBinaryToASCIIBuffer.get();
+    }
+
+    /**
+     * A converter which can process an ASCII String representation
+     * of a single or double precision floating point value into a
+     * float or a double.
+     */
+    interface ASCIIToBinaryConverter {
+
+        double doubleValue();
+
+        float floatValue();
+
+    }
+
+    /**
+     * A ASCIIToBinaryConverter container for a double.
+     */
+    static class PreparedASCIIToBinaryBuffer implements ASCIIToBinaryConverter {
+        private final double doubleVal;
+        private final float floatVal;
+
+        public PreparedASCIIToBinaryBuffer(double doubleVal, float floatVal) {
+            this.doubleVal = doubleVal;
+            this.floatVal = floatVal;
+        }
+
+        @Override
+        public double doubleValue() {
+            return doubleVal;
+        }
+
+        @Override
+        public float floatValue() {
+            return floatVal;
+        }
+    }
+
+    static final ASCIIToBinaryConverter A2BC_POSITIVE_INFINITY = new PreparedASCIIToBinaryBuffer(Double.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
+    static final ASCIIToBinaryConverter A2BC_NEGATIVE_INFINITY = new PreparedASCIIToBinaryBuffer(Double.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+    static final ASCIIToBinaryConverter A2BC_NOT_A_NUMBER  = new PreparedASCIIToBinaryBuffer(Double.NaN, Float.NaN);
+    static final ASCIIToBinaryConverter A2BC_POSITIVE_ZERO = new PreparedASCIIToBinaryBuffer(0.0d, 0.0f);
+    static final ASCIIToBinaryConverter A2BC_NEGATIVE_ZERO = new PreparedASCIIToBinaryBuffer(-0.0d, -0.0f);
+
+    /**
+     * A buffered implementation of ASCIIToBinaryConverter.
+     */
+    static class ASCIIToBinaryBuffer implements ASCIIToBinaryConverter {
+        boolean     isNegative;
+        int         decExponent;
+        char        digits[];
+        int         nDigits;
+
+        ASCIIToBinaryBuffer( boolean negSign, int decExponent, char[] digits, int n)
+        {
+            this.isNegative = negSign;
+            this.decExponent = decExponent;
+            this.digits = digits;
+            this.nDigits = n;
+        }
+
+        /**
+         * Takes a FloatingDecimal, which we presumably just scanned in,
+         * and finds out what its value is, as a double.
+         *
+         * AS A SIDE EFFECT, SET roundDir TO INDICATE PREFERRED
+         * ROUNDING DIRECTION in case the result is really destined
+         * for a single-precision float.
+         */
+        @Override
+        public double doubleValue() {
+            int kDigits = Math.min(nDigits, MAX_DECIMAL_DIGITS + 1);
+            //
+            // convert the lead kDigits to a long integer.
+            //
+            // (special performance hack: start to do it using int)
+            int iValue = (int) digits[0] - (int) '0';
+            int iDigits = Math.min(kDigits, INT_DECIMAL_DIGITS);
+            for (int i = 1; i < iDigits; i++) {
+                iValue = iValue * 10 + (int) digits[i] - (int) '0';
+            }
+            long lValue = (long) iValue;
+            for (int i = iDigits; i < kDigits; i++) {
+                lValue = lValue * 10L + (long) ((int) digits[i] - (int) '0');
+            }
+            double dValue = (double) lValue;
+            int exp = decExponent - kDigits;
+            //
+            // lValue now contains a long integer with the value of
+            // the first kDigits digits of the number.
+            // dValue contains the (double) of the same.
+            //
+
+            if (nDigits <= MAX_DECIMAL_DIGITS) {
+                //
+                // possibly an easy case.
+                // We know that the digits can be represented
+                // exactly. And if the exponent isn't too outrageous,
+                // the whole thing can be done with one operation,
+                // thus one rounding error.
+                // Note that all our constructors trim all leading and
+                // trailing zeros, so simple values (including zero)
+                // will always end up here
+                //
+                if (exp == 0 || dValue == 0.0) {
+                    return (isNegative) ? -dValue : dValue; // small floating integer
+                }
+                else if (exp >= 0) {
+                    if (exp <= MAX_SMALL_TEN) {
+                        //
+                        // Can get the answer with one operation,
+                        // thus one roundoff.
+                        //
+                        double rValue = dValue * SMALL_10_POW[exp];
+                        return (isNegative) ? -rValue : rValue;
+                    }
+                    int slop = MAX_DECIMAL_DIGITS - kDigits;
+                    if (exp <= MAX_SMALL_TEN + slop) {
+                        //
+                        // We can multiply dValue by 10^(slop)
+                        // and it is still "small" and exact.
+                        // Then we can multiply by 10^(exp-slop)
+                        // with one rounding.
+                        //
+                        dValue *= SMALL_10_POW[slop];
+                        double rValue = dValue * SMALL_10_POW[exp - slop];
+                        return (isNegative) ? -rValue : rValue;
+                    }
+                    //
+                    // Else we have a hard case with a positive exp.
+                    //
+                } else {
+                    if (exp >= -MAX_SMALL_TEN) {
+                        //
+                        // Can get the answer in one division.
+                        //
+                        double rValue = dValue / SMALL_10_POW[-exp];
+                        return (isNegative) ? -rValue : rValue;
+                    }
+                    //
+                    // Else we have a hard case with a negative exp.
+                    //
+                }
+            }
+
+            //
+            // Harder cases:
+            // The sum of digits plus exponent is greater than
+            // what we think we can do with one error.
+            //
+            // Start by approximating the right answer by,
+            // naively, scaling by powers of 10.
+            //
+            if (exp > 0) {
+                if (decExponent > MAX_DECIMAL_EXPONENT + 1) {
+                    //
+                    // Lets face it. This is going to be
+                    // Infinity. Cut to the chase.
+                    //
+                    return (isNegative) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+                }
+                if ((exp & 15) != 0) {
+                    dValue *= SMALL_10_POW[exp & 15];
+                }
+                if ((exp >>= 4) != 0) {
+                    int j;
+                    for (j = 0; exp > 1; j++, exp >>= 1) {
+                        if ((exp & 1) != 0) {
+                            dValue *= BIG_10_POW[j];
+                        }
+                    }
+                    //
+                    // The reason for the weird exp > 1 condition
+                    // in the above loop was so that the last multiply
+                    // would get unrolled. We handle it here.
+                    // It could overflow.
+                    //
+                    double t = dValue * BIG_10_POW[j];
+                    if (Double.isInfinite(t)) {
+                        //
+                        // It did overflow.
+                        // Look more closely at the result.
+                        // If the exponent is just one too large,
+                        // then use the maximum finite as our estimate
+                        // value. Else call the result infinity
+                        // and punt it.
+                        // ( I presume this could happen because
+                        // rounding forces the result here to be
+                        // an ULP or two larger than
+                        // Double.MAX_VALUE ).
+                        //
+                        t = dValue / 2.0;
+                        t *= BIG_10_POW[j];
+                        if (Double.isInfinite(t)) {
+                            return (isNegative) ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+                        }
+                        t = Double.MAX_VALUE;
+                    }
+                    dValue = t;
+                }
+            } else if (exp < 0) {
+                exp = -exp;
+                if (decExponent < MIN_DECIMAL_EXPONENT - 1) {
+                    //
+                    // Lets face it. This is going to be
+                    // zero. Cut to the chase.
+                    //
+                    return (isNegative) ? -0.0 : 0.0;
+                }
+                if ((exp & 15) != 0) {
+                    dValue /= SMALL_10_POW[exp & 15];
+                }
+                if ((exp >>= 4) != 0) {
+                    int j;
+                    for (j = 0; exp > 1; j++, exp >>= 1) {
+                        if ((exp & 1) != 0) {
+                            dValue *= TINY_10_POW[j];
+                        }
+                    }
+                    //
+                    // The reason for the weird exp > 1 condition
+                    // in the above loop was so that the last multiply
+                    // would get unrolled. We handle it here.
+                    // It could underflow.
+                    //
+                    double t = dValue * TINY_10_POW[j];
+                    if (t == 0.0) {
+                        //
+                        // It did underflow.
+                        // Look more closely at the result.
+                        // If the exponent is just one too small,
+                        // then use the minimum finite as our estimate
+                        // value. Else call the result 0.0
+                        // and punt it.
+                        // ( I presume this could happen because
+                        // rounding forces the result here to be
+                        // an ULP or two less than
+                        // Double.MIN_VALUE ).
+                        //
+                        t = dValue * 2.0;
+                        t *= TINY_10_POW[j];
+                        if (t == 0.0) {
+                            return (isNegative) ? -0.0 : 0.0;
+                        }
+                        t = Double.MIN_VALUE;
+                    }
+                    dValue = t;
+                }
+            }
+
+            //
+            // dValue is now approximately the result.
+            // The hard part is adjusting it, by comparison
+            // with FDBigInteger arithmetic.
+            // Formulate the EXACT big-number result as
+            // bigD0 * 10^exp
+            //
+            if (nDigits > MAX_NDIGITS) {
+                nDigits = MAX_NDIGITS + 1;
+                digits[MAX_NDIGITS] = '1';
+            }
+            FDBigInteger bigD0 = new FDBigInteger(lValue, digits, kDigits, nDigits);
+            exp = decExponent - nDigits;
+
+            long ieeeBits = Double.doubleToRawLongBits(dValue); // IEEE-754 bits of double candidate
+            final int B5 = Math.max(0, -exp); // powers of 5 in bigB, value is not modified inside correctionLoop
+            final int D5 = Math.max(0, exp); // powers of 5 in bigD, value is not modified inside correctionLoop
+            bigD0 = bigD0.multByPow52(D5, 0);
+            bigD0.makeImmutable();   // prevent bigD0 modification inside correctionLoop
+            FDBigInteger bigD = null;
+            int prevD2 = 0;
+
+            correctionLoop:
+            while (true) {
+                // here ieeeBits can't be NaN, Infinity or zero
+                int binexp = (int) (ieeeBits >>> EXP_SHIFT);
+                long bigBbits = ieeeBits & DoubleConsts.SIGNIF_BIT_MASK;
+                if (binexp > 0) {
+                    bigBbits |= FRACT_HOB;
+                } else { // Normalize denormalized numbers.
+                    assert bigBbits != 0L : bigBbits; // doubleToBigInt(0.0)
+                    int leadingZeros = Long.numberOfLeadingZeros(bigBbits);
+                    int shift = leadingZeros - (63 - EXP_SHIFT);
+                    bigBbits <<= shift;
+                    binexp = 1 - shift;
+                }
+                binexp -= DoubleConsts.EXP_BIAS;
+                int lowOrderZeros = Long.numberOfTrailingZeros(bigBbits);
+                bigBbits >>>= lowOrderZeros;
+                final int bigIntExp = binexp - EXP_SHIFT + lowOrderZeros;
+                final int bigIntNBits = EXP_SHIFT + 1 - lowOrderZeros;
+
+                //
+                // Scale bigD, bigB appropriately for
+                // big-integer operations.
+                // Naively, we multiply by powers of ten
+                // and powers of two. What we actually do
+                // is keep track of the powers of 5 and
+                // powers of 2 we would use, then factor out
+                // common divisors before doing the work.
+                //
+                int B2 = B5; // powers of 2 in bigB
+                int D2 = D5; // powers of 2 in bigD
+                int Ulp2;   // powers of 2 in halfUlp.
+                if (bigIntExp >= 0) {
+                    B2 += bigIntExp;
+                } else {
+                    D2 -= bigIntExp;
+                }
+                Ulp2 = B2;
+                // shift bigB and bigD left by a number s. t.
+                // halfUlp is still an integer.
+                int hulpbias;
+                if (binexp <= -DoubleConsts.EXP_BIAS) {
+                    // This is going to be a denormalized number
+                    // (if not actually zero).
+                    // half an ULP is at 2^-(DoubleConsts.EXP_BIAS+EXP_SHIFT+1)
+                    hulpbias = binexp + lowOrderZeros + DoubleConsts.EXP_BIAS;
+                } else {
+                    hulpbias = 1 + lowOrderZeros;
+                }
+                B2 += hulpbias;
+                D2 += hulpbias;
+                // if there are common factors of 2, we might just as well
+                // factor them out, as they add nothing useful.
+                int common2 = Math.min(B2, Math.min(D2, Ulp2));
+                B2 -= common2;
+                D2 -= common2;
+                Ulp2 -= common2;
+                // do multiplications by powers of 5 and 2
+                FDBigInteger bigB = FDBigInteger.valueOfMulPow52(bigBbits, B5, B2);
+                if (bigD == null || prevD2 != D2) {
+                    bigD = bigD0.leftShift(D2);
+                    prevD2 = D2;
+                }
+                //
+                // to recap:
+                // bigB is the scaled-big-int version of our floating-point
+                // candidate.
+                // bigD is the scaled-big-int version of the exact value
+                // as we understand it.
+                // halfUlp is 1/2 an ulp of bigB, except for special cases
+                // of exact powers of 2
+                //
+                // the plan is to compare bigB with bigD, and if the difference
+                // is less than halfUlp, then we're satisfied. Otherwise,
+                // use the ratio of difference to halfUlp to calculate a fudge
+                // factor to add to the floating value, then go 'round again.
+                //
+                FDBigInteger diff;
+                int cmpResult;
+                boolean overvalue;
+                if ((cmpResult = bigB.cmp(bigD)) > 0) {
+                    overvalue = true; // our candidate is too big.
+                    diff = bigB.leftInplaceSub(bigD); // bigB is not user further - reuse
+                    if ((bigIntNBits == 1) && (bigIntExp > -DoubleConsts.EXP_BIAS + 1)) {
+                        // candidate is a normalized exact power of 2 and
+                        // is too big (larger than Double.MIN_NORMAL). We will be subtracting.
+                        // For our purposes, ulp is the ulp of the
+                        // next smaller range.
+                        Ulp2 -= 1;
+                        if (Ulp2 < 0) {
+                            // rats. Cannot de-scale ulp this far.
+                            // must scale diff in other direction.
+                            Ulp2 = 0;
+                            diff = diff.leftShift(1);
+                        }
+                    }
+                } else if (cmpResult < 0) {
+                    overvalue = false; // our candidate is too small.
+                    diff = bigD.rightInplaceSub(bigB); // bigB is not user further - reuse
+                } else {
+                    // the candidate is exactly right!
+                    // this happens with surprising frequency
+                    break correctionLoop;
+                }
+                cmpResult = diff.cmpPow52(B5, Ulp2);
+                if ((cmpResult) < 0) {
+                    // difference is small.
+                    // this is close enough
+                    break correctionLoop;
+                } else if (cmpResult == 0) {
+                    // difference is exactly half an ULP
+                    // round to some other value maybe, then finish
+                    if ((ieeeBits & 1) != 0) { // half ties to even
+                        ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp
+                    }
+                    break correctionLoop;
+                } else {
+                    // difference is non-trivial.
+                    // could scale addend by ratio of difference to
+                    // halfUlp here, if we bothered to compute that difference.
+                    // Most of the time ( I hope ) it is about 1 anyway.
+                    ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp
+                    if (ieeeBits == 0 || ieeeBits == DoubleConsts.EXP_BIT_MASK) { // 0.0 or Double.POSITIVE_INFINITY
+                        break correctionLoop; // oops. Fell off end of range.
+                    }
+                    continue; // try again.
+                }
+
+            }
+            if (isNegative) {
+                ieeeBits |= DoubleConsts.SIGN_BIT_MASK;
+            }
+            return Double.longBitsToDouble(ieeeBits);
+        }
+
+        /**
+         * Takes a FloatingDecimal, which we presumably just scanned in,
+         * and finds out what its value is, as a float.
+         * This is distinct from doubleValue() to avoid the extremely
+         * unlikely case of a double rounding error, wherein the conversion
+         * to double has one rounding error, and the conversion of that double
+         * to a float has another rounding error, IN THE WRONG DIRECTION,
+         * ( because of the preference to a zero low-order bit ).
+         */
+        @Override
+        public float floatValue() {
+            int kDigits = Math.min(nDigits, SINGLE_MAX_DECIMAL_DIGITS + 1);
+            //
+            // convert the lead kDigits to an integer.
+            //
+            int iValue = (int) digits[0] - (int) '0';
+            for (int i = 1; i < kDigits; i++) {
+                iValue = iValue * 10 + (int) digits[i] - (int) '0';
+            }
+            float fValue = (float) iValue;
+            int exp = decExponent - kDigits;
+            //
+            // iValue now contains an integer with the value of
+            // the first kDigits digits of the number.
+            // fValue contains the (float) of the same.
+            //
+
+            if (nDigits <= SINGLE_MAX_DECIMAL_DIGITS) {
+                //
+                // possibly an easy case.
+                // We know that the digits can be represented
+                // exactly. And if the exponent isn't too outrageous,
+                // the whole thing can be done with one operation,
+                // thus one rounding error.
+                // Note that all our constructors trim all leading and
+                // trailing zeros, so simple values (including zero)
+                // will always end up here.
+                //
+                if (exp == 0 || fValue == 0.0f) {
+                    return (isNegative) ? -fValue : fValue; // small floating integer
+                } else if (exp >= 0) {
+                    if (exp <= SINGLE_MAX_SMALL_TEN) {
+                        //
+                        // Can get the answer with one operation,
+                        // thus one roundoff.
+                        //
+                        fValue *= SINGLE_SMALL_10_POW[exp];
+                        return (isNegative) ? -fValue : fValue;
+                    }
+                    int slop = SINGLE_MAX_DECIMAL_DIGITS - kDigits;
+                    if (exp <= SINGLE_MAX_SMALL_TEN + slop) {
+                        //
+                        // We can multiply fValue by 10^(slop)
+                        // and it is still "small" and exact.
+                        // Then we can multiply by 10^(exp-slop)
+                        // with one rounding.
+                        //
+                        fValue *= SINGLE_SMALL_10_POW[slop];
+                        fValue *= SINGLE_SMALL_10_POW[exp - slop];
+                        return (isNegative) ? -fValue : fValue;
+                    }
+                    //
+                    // Else we have a hard case with a positive exp.
+                    //
+                } else {
+                    if (exp >= -SINGLE_MAX_SMALL_TEN) {
+                        //
+                        // Can get the answer in one division.
+                        //
+                        fValue /= SINGLE_SMALL_10_POW[-exp];
+                        return (isNegative) ? -fValue : fValue;
+                    }
+                    //
+                    // Else we have a hard case with a negative exp.
+                    //
+                }
+            } else if ((decExponent >= nDigits) && (nDigits + decExponent <= MAX_DECIMAL_DIGITS)) {
+                //
+                // In double-precision, this is an exact floating integer.
+                // So we can compute to double, then shorten to float
+                // with one round, and get the right answer.
+                //
+                // First, finish accumulating digits.
+                // Then convert that integer to a double, multiply
+                // by the appropriate power of ten, and convert to float.
+                //
+                long lValue = (long) iValue;
+                for (int i = kDigits; i < nDigits; i++) {
+                    lValue = lValue * 10L + (long) ((int) digits[i] - (int) '0');
+                }
+                double dValue = (double) lValue;
+                exp = decExponent - nDigits;
+                dValue *= SMALL_10_POW[exp];
+                fValue = (float) dValue;
+                return (isNegative) ? -fValue : fValue;
+
+            }
+            //
+            // Harder cases:
+            // The sum of digits plus exponent is greater than
+            // what we think we can do with one error.
+            //
+            // Start by approximating the right answer by,
+            // naively, scaling by powers of 10.
+            // Scaling uses doubles to avoid overflow/underflow.
+            //
+            double dValue = fValue;
+            if (exp > 0) {
+                if (decExponent > SINGLE_MAX_DECIMAL_EXPONENT + 1) {
+                    //
+                    // Lets face it. This is going to be
+                    // Infinity. Cut to the chase.
+                    //
+                    return (isNegative) ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY;
+                }
+                if ((exp & 15) != 0) {
+                    dValue *= SMALL_10_POW[exp & 15];
+                }
+                if ((exp >>= 4) != 0) {
+                    int j;
+                    for (j = 0; exp > 0; j++, exp >>= 1) {
+                        if ((exp & 1) != 0) {
+                            dValue *= BIG_10_POW[j];
+                        }
+                    }
+                }
+            } else if (exp < 0) {
+                exp = -exp;
+                if (decExponent < SINGLE_MIN_DECIMAL_EXPONENT - 1) {
+                    //
+                    // Lets face it. This is going to be
+                    // zero. Cut to the chase.
+                    //
+                    return (isNegative) ? -0.0f : 0.0f;
+                }
+                if ((exp & 15) != 0) {
+                    dValue /= SMALL_10_POW[exp & 15];
+                }
+                if ((exp >>= 4) != 0) {
+                    int j;
+                    for (j = 0; exp > 0; j++, exp >>= 1) {
+                        if ((exp & 1) != 0) {
+                            dValue *= TINY_10_POW[j];
+                        }
+                    }
+                }
+            }
+            fValue = Math.clamp((float) dValue, Float.MIN_VALUE, Float.MAX_VALUE);
+
+            //
+            // fValue is now approximately the result.
+            // The hard part is adjusting it, by comparison
+            // with FDBigInteger arithmetic.
+            // Formulate the EXACT big-number result as
+            // bigD0 * 10^exp
+            //
+            if (nDigits > SINGLE_MAX_NDIGITS) {
+                nDigits = SINGLE_MAX_NDIGITS + 1;
+                digits[SINGLE_MAX_NDIGITS] = '1';
+            }
+            FDBigInteger bigD0 = new FDBigInteger(iValue, digits, kDigits, nDigits);
+            exp = decExponent - nDigits;
+
+            int ieeeBits = Float.floatToRawIntBits(fValue); // IEEE-754 bits of float candidate
+            final int B5 = Math.max(0, -exp); // powers of 5 in bigB, value is not modified inside correctionLoop
+            final int D5 = Math.max(0, exp); // powers of 5 in bigD, value is not modified inside correctionLoop
+            bigD0 = bigD0.multByPow52(D5, 0);
+            bigD0.makeImmutable();   // prevent bigD0 modification inside correctionLoop
+            FDBigInteger bigD = null;
+            int prevD2 = 0;
+
+            correctionLoop:
+            while (true) {
+                // here ieeeBits can't be NaN, Infinity or zero
+                int binexp = ieeeBits >>> SINGLE_EXP_SHIFT;
+                int bigBbits = ieeeBits & FloatConsts.SIGNIF_BIT_MASK;
+                if (binexp > 0) {
+                    bigBbits |= SINGLE_FRACT_HOB;
+                } else { // Normalize denormalized numbers.
+                    assert bigBbits != 0 : bigBbits; // floatToBigInt(0.0)
+                    int leadingZeros = Integer.numberOfLeadingZeros(bigBbits);
+                    int shift = leadingZeros - (31 - SINGLE_EXP_SHIFT);
+                    bigBbits <<= shift;
+                    binexp = 1 - shift;
+                }
+                binexp -= FloatConsts.EXP_BIAS;
+                int lowOrderZeros = Integer.numberOfTrailingZeros(bigBbits);
+                bigBbits >>>= lowOrderZeros;
+                final int bigIntExp = binexp - SINGLE_EXP_SHIFT + lowOrderZeros;
+                final int bigIntNBits = SINGLE_EXP_SHIFT + 1 - lowOrderZeros;
+
+                //
+                // Scale bigD, bigB appropriately for
+                // big-integer operations.
+                // Naively, we multiply by powers of ten
+                // and powers of two. What we actually do
+                // is keep track of the powers of 5 and
+                // powers of 2 we would use, then factor out
+                // common divisors before doing the work.
+                //
+                int B2 = B5; // powers of 2 in bigB
+                int D2 = D5; // powers of 2 in bigD
+                int Ulp2;   // powers of 2 in halfUlp.
+                if (bigIntExp >= 0) {
+                    B2 += bigIntExp;
+                } else {
+                    D2 -= bigIntExp;
+                }
+                Ulp2 = B2;
+                // shift bigB and bigD left by a number s. t.
+                // halfUlp is still an integer.
+                int hulpbias;
+                if (binexp <= -FloatConsts.EXP_BIAS) {
+                    // This is going to be a denormalized number
+                    // (if not actually zero).
+                    // half an ULP is at 2^-(FloatConsts.EXP_BIAS+SINGLE_EXP_SHIFT+1)
+                    hulpbias = binexp + lowOrderZeros + FloatConsts.EXP_BIAS;
+                } else {
+                    hulpbias = 1 + lowOrderZeros;
+                }
+                B2 += hulpbias;
+                D2 += hulpbias;
+                // if there are common factors of 2, we might just as well
+                // factor them out, as they add nothing useful.
+                int common2 = Math.min(B2, Math.min(D2, Ulp2));
+                B2 -= common2;
+                D2 -= common2;
+                Ulp2 -= common2;
+                // do multiplications by powers of 5 and 2
+                FDBigInteger bigB = FDBigInteger.valueOfMulPow52(bigBbits, B5, B2);
+                if (bigD == null || prevD2 != D2) {
+                    bigD = bigD0.leftShift(D2);
+                    prevD2 = D2;
+                }
+                //
+                // to recap:
+                // bigB is the scaled-big-int version of our floating-point
+                // candidate.
+                // bigD is the scaled-big-int version of the exact value
+                // as we understand it.
+                // halfUlp is 1/2 an ulp of bigB, except for special cases
+                // of exact powers of 2
+                //
+                // the plan is to compare bigB with bigD, and if the difference
+                // is less than halfUlp, then we're satisfied. Otherwise,
+                // use the ratio of difference to halfUlp to calculate a fudge
+                // factor to add to the floating value, then go 'round again.
+                //
+                FDBigInteger diff;
+                int cmpResult;
+                boolean overvalue;
+                if ((cmpResult = bigB.cmp(bigD)) > 0) {
+                    overvalue = true; // our candidate is too big.
+                    diff = bigB.leftInplaceSub(bigD); // bigB is not user further - reuse
+                    if ((bigIntNBits == 1) && (bigIntExp > -FloatConsts.EXP_BIAS + 1)) {
+                        // candidate is a normalized exact power of 2 and
+                        // is too big (larger than Float.MIN_NORMAL). We will be subtracting.
+                        // For our purposes, ulp is the ulp of the
+                        // next smaller range.
+                        Ulp2 -= 1;
+                        if (Ulp2 < 0) {
+                            // rats. Cannot de-scale ulp this far.
+                            // must scale diff in other direction.
+                            Ulp2 = 0;
+                            diff = diff.leftShift(1);
+                        }
+                    }
+                } else if (cmpResult < 0) {
+                    overvalue = false; // our candidate is too small.
+                    diff = bigD.rightInplaceSub(bigB); // bigB is not user further - reuse
+                } else {
+                    // the candidate is exactly right!
+                    // this happens with surprising frequency
+                    break correctionLoop;
+                }
+                cmpResult = diff.cmpPow52(B5, Ulp2);
+                if ((cmpResult) < 0) {
+                    // difference is small.
+                    // this is close enough
+                    break correctionLoop;
+                } else if (cmpResult == 0) {
+                    // difference is exactly half an ULP
+                    // round to some other value maybe, then finish
+                    if ((ieeeBits & 1) != 0) { // half ties to even
+                        ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp
+                    }
+                    break correctionLoop;
+                } else {
+                    // difference is non-trivial.
+                    // could scale addend by ratio of difference to
+                    // halfUlp here, if we bothered to compute that difference.
+                    // Most of the time ( I hope ) it is about 1 anyway.
+                    ieeeBits += overvalue ? -1 : 1; // nextDown or nextUp
+                    if (ieeeBits == 0 || ieeeBits == FloatConsts.EXP_BIT_MASK) { // 0.0 or Float.POSITIVE_INFINITY
+                        break correctionLoop; // oops. Fell off end of range.
+                    }
+                    continue; // try again.
+                }
+
+            }
+            if (isNegative) {
+                ieeeBits |= FloatConsts.SIGN_BIT_MASK;
+            }
+            return Float.intBitsToFloat(ieeeBits);
+        }
+
+
+        /**
+         * All the positive powers of 10 that can be
+         * represented exactly in double/float.
+         */
+        private static final double[] SMALL_10_POW = {
+            1.0e0,
+            1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5,
+            1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10,
+            1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15,
+            1.0e16, 1.0e17, 1.0e18, 1.0e19, 1.0e20,
+            1.0e21, 1.0e22
+        };
+
+        private static final float[] SINGLE_SMALL_10_POW = {
+            1.0e0f,
+            1.0e1f, 1.0e2f, 1.0e3f, 1.0e4f, 1.0e5f,
+            1.0e6f, 1.0e7f, 1.0e8f, 1.0e9f, 1.0e10f
+        };
+
+        private static final double[] BIG_10_POW = {
+            1e16, 1e32, 1e64, 1e128, 1e256 };
+        private static final double[] TINY_10_POW = {
+            1e-16, 1e-32, 1e-64, 1e-128, 1e-256 };
+
+        private static final int MAX_SMALL_TEN = SMALL_10_POW.length-1;
+        private static final int SINGLE_MAX_SMALL_TEN = SINGLE_SMALL_10_POW.length-1;
+
+    }
+
+    /**
+     * Returns a BinaryToASCIIConverter for a double.
+     * The returned object is a ThreadLocal variable of this class.
+     *
+     * @param d The double precision value to convert.
+     * @return The converter.
+     */
+    public static BinaryToASCIIConverter getBinaryToASCIIConverter(double d) {
+        return getBinaryToASCIIConverter(d, true);
+    }
+
+    /**
+     * Returns a BinaryToASCIIConverter for a double.
+     * The returned object is a ThreadLocal variable of this class.
+     *
+     * @param d The double precision value to convert.
+     * @param isCompatibleFormat
+     * @return The converter.
+     */
+    static BinaryToASCIIConverter getBinaryToASCIIConverter(double d, boolean isCompatibleFormat) {
+        long dBits = Double.doubleToRawLongBits(d);
+        boolean isNegative = (dBits&DoubleConsts.SIGN_BIT_MASK) != 0; // discover sign
+        long fractBits = dBits & DoubleConsts.SIGNIF_BIT_MASK;
+        int  binExp = (int)( (dBits&DoubleConsts.EXP_BIT_MASK) >> EXP_SHIFT );
+        // Discover obvious special cases of NaN and Infinity.
+        if ( binExp == (int)(DoubleConsts.EXP_BIT_MASK>>EXP_SHIFT) ) {
+            if ( fractBits == 0L ){
+                return isNegative ? B2AC_NEGATIVE_INFINITY : B2AC_POSITIVE_INFINITY;
+            } else {
+                return B2AC_NOT_A_NUMBER;
+            }
+        }
+        // Finish unpacking
+        // Normalize denormalized numbers.
+        // Insert assumed high-order bit for normalized numbers.
+        // Subtract exponent bias.
+        int  nSignificantBits;
+        if ( binExp == 0 ){
+            if ( fractBits == 0L ){
+                // not a denorm, just a 0!
+                return isNegative ? B2AC_NEGATIVE_ZERO : B2AC_POSITIVE_ZERO;
+            }
+            int leadingZeros = Long.numberOfLeadingZeros(fractBits);
+            int shift = leadingZeros-(63-EXP_SHIFT);
+            fractBits <<= shift;
+            binExp = 1 - shift;
+            nSignificantBits =  64-leadingZeros; // recall binExp is  - shift count.
+        } else {
+            fractBits |= FRACT_HOB;
+            nSignificantBits = EXP_SHIFT+1;
+        }
+        binExp -= DoubleConsts.EXP_BIAS;
+        BinaryToASCIIBuffer buf = getBinaryToASCIIBuffer();
+        buf.setSign(isNegative);
+        // call the routine that actually does all the hard work.
+        buf.dtoa(binExp, fractBits, nSignificantBits, isCompatibleFormat);
+        return buf;
+    }
+
+    private static BinaryToASCIIConverter getBinaryToASCIIConverter(float f) {
+        int fBits = Float.floatToRawIntBits( f );
+        boolean isNegative = (fBits&FloatConsts.SIGN_BIT_MASK) != 0;
+        int fractBits = fBits&FloatConsts.SIGNIF_BIT_MASK;
+        int binExp = (fBits&FloatConsts.EXP_BIT_MASK) >> SINGLE_EXP_SHIFT;
+        // Discover obvious special cases of NaN and Infinity.
+        if ( binExp == (FloatConsts.EXP_BIT_MASK>>SINGLE_EXP_SHIFT) ) {
+            if ( fractBits == 0L ){
+                return isNegative ? B2AC_NEGATIVE_INFINITY : B2AC_POSITIVE_INFINITY;
+            } else {
+                return B2AC_NOT_A_NUMBER;
+            }
+        }
+        // Finish unpacking
+        // Normalize denormalized numbers.
+        // Insert assumed high-order bit for normalized numbers.
+        // Subtract exponent bias.
+        int  nSignificantBits;
+        if ( binExp == 0 ){
+            if ( fractBits == 0 ){
+                // not a denorm, just a 0!
+                return isNegative ? B2AC_NEGATIVE_ZERO : B2AC_POSITIVE_ZERO;
+            }
+            int leadingZeros = Integer.numberOfLeadingZeros(fractBits);
+            int shift = leadingZeros-(31-SINGLE_EXP_SHIFT);
+            fractBits <<= shift;
+            binExp = 1 - shift;
+            nSignificantBits =  32 - leadingZeros; // recall binExp is  - shift count.
+        } else {
+            fractBits |= SINGLE_FRACT_HOB;
+            nSignificantBits = SINGLE_EXP_SHIFT+1;
+        }
+        binExp -= FloatConsts.EXP_BIAS;
+        BinaryToASCIIBuffer buf = getBinaryToASCIIBuffer();
+        buf.setSign(isNegative);
+        // call the routine that actually does all the hard work.
+        buf.dtoa(binExp, ((long)fractBits)<<(EXP_SHIFT-SINGLE_EXP_SHIFT), nSignificantBits, true);
+        return buf;
+    }
+
+    @SuppressWarnings("fallthrough")
+    static ASCIIToBinaryConverter readJavaFormatString( String in ) throws NumberFormatException {
+        boolean isNegative = false;
+        boolean signSeen   = false;
+        int     decExp;
+        char    c;
+
+    parseNumber:
+        try{
+            in = in.trim(); // don't fool around with white space.
+                            // throws NullPointerException if null
+            int len = in.length();
+            if ( len == 0 ) {
+                throw new NumberFormatException("empty String");
+            }
+            int i = 0;
+            switch (in.charAt(i)){
+            case '-':
+                isNegative = true;
+                //FALLTHROUGH
+            case '+':
+                i++;
+                signSeen = true;
+            }
+            c = in.charAt(i);
+            if(c == 'N') { // Check for NaN
+                if((len-i)==NAN_LENGTH && in.indexOf(NAN_REP,i)==i) {
+                    return A2BC_NOT_A_NUMBER;
+                }
+                // something went wrong, throw exception
+                break parseNumber;
+            } else if(c == 'I') { // Check for Infinity strings
+                if((len-i)==INFINITY_LENGTH && in.indexOf(INFINITY_REP,i)==i) {
+                    return isNegative? A2BC_NEGATIVE_INFINITY : A2BC_POSITIVE_INFINITY;
+                }
+                // something went wrong, throw exception
+                break parseNumber;
+            } else if (c == '0')  { // check for hexadecimal floating-point number
+                if (len > i+1 ) {
+                    char ch = in.charAt(i+1);
+                    if (ch == 'x' || ch == 'X' ) { // possible hex string
+                        return parseHexString(in);
+                    }
+                }
+            }  // look for and process decimal floating-point string
+
+            char[] digits = new char[ len ];
+            boolean decSeen = false;
+            int nDigits = 0;
+            int decPt = 0;
+            int nLeadZero = 0;
+            int nTrailZero = 0;
+
+        skipLeadingZerosLoop:
+            while (i < len) {
+                c = in.charAt(i);
+                if (c == '0') {
+                    nLeadZero++;
+                } else if (c == '.') {
+                    if (decSeen) {
+                        // already saw one ., this is the 2nd.
+                        throw new NumberFormatException("multiple points");
+                    }
+                    decPt = i;
+                    if (signSeen) {
+                        decPt -= 1;
+                    }
+                    decSeen = true;
+                } else {
+                    break skipLeadingZerosLoop;
+                }
+                i++;
+            }
+        digitLoop:
+            while (i < len) {
+                c = in.charAt(i);
+                if (c >= '1' && c <= '9') {
+                    digits[nDigits++] = c;
+                    nTrailZero = 0;
+                } else if (c == '0') {
+                    digits[nDigits++] = c;
+                    nTrailZero++;
+                } else if (c == '.') {
+                    if (decSeen) {
+                        // already saw one ., this is the 2nd.
+                        throw new NumberFormatException("multiple points");
+                    }
+                    decPt = i;
+                    if (signSeen) {
+                        decPt -= 1;
+                    }
+                    decSeen = true;
+                } else {
+                    break digitLoop;
+                }
+                i++;
+            }
+            nDigits -=nTrailZero;
+            //
+            // At this point, we've scanned all the digits and decimal
+            // point we're going to see. Trim off leading and trailing
+            // zeros, which will just confuse us later, and adjust
+            // our initial decimal exponent accordingly.
+            // To review:
+            // we have seen i total characters.
+            // nLeadZero of them were zeros before any other digits.
+            // nTrailZero of them were zeros after any other digits.
+            // if ( decSeen ), then a . was seen after decPt characters
+            // ( including leading zeros which have been discarded )
+            // nDigits characters were neither lead nor trailing
+            // zeros, nor point
+            //
+            //
+            // special hack: if we saw no non-zero digits, then the
+            // answer is zero!
+            // Unfortunately, we feel honor-bound to keep parsing!
+            //
+            boolean isZero = (nDigits == 0);
+            if ( isZero &&  nLeadZero == 0 ){
+                // we saw NO DIGITS AT ALL,
+                // not even a crummy 0!
+                // this is not allowed.
+                break parseNumber; // go throw exception
+            }
+            //
+            // Our initial exponent is decPt, adjusted by the number of
+            // discarded zeros. Or, if there was no decPt,
+            // then its just nDigits adjusted by discarded trailing zeros.
+            //
+            if ( decSeen ){
+                decExp = decPt - nLeadZero;
+            } else {
+                decExp = nDigits + nTrailZero;
+            }
+
+            //
+            // Look for 'e' or 'E' and an optionally signed integer.
+            //
+            if ( (i < len) &&  (((c = in.charAt(i) )=='e') || (c == 'E') ) ){
+                int expSign = 1;
+                int expVal  = 0;
+                int reallyBig = Integer.MAX_VALUE / 10;
+                boolean expOverflow = false;
+                switch( in.charAt(++i) ){
+                case '-':
+                    expSign = -1;
+                    //FALLTHROUGH
+                case '+':
+                    i++;
+                }
+                int expAt = i;
+            expLoop:
+                while ( i < len  ){
+                    if ( expVal >= reallyBig ){
+                        // the next character will cause integer
+                        // overflow.
+                        expOverflow = true;
+                    }
+                    c = in.charAt(i++);
+                    if(c>='0' && c<='9') {
+                        expVal = expVal*10 + ( (int)c - (int)'0' );
+                    } else {
+                        i--;           // back up.
+                        break expLoop; // stop parsing exponent.
+                    }
+                }
+                int expLimit = BIG_DECIMAL_EXPONENT + nDigits + nTrailZero;
+                if (expOverflow || (expVal > expLimit)) {
+                    // There is still a chance that the exponent will be safe to
+                    // use: if it would eventually decrease due to a negative
+                    // decExp, and that number is below the limit.  We check for
+                    // that here.
+                    if (!expOverflow && (expSign == 1 && decExp < 0)
+                            && (expVal + decExp) < expLimit) {
+                        // Cannot overflow: adding a positive and negative number.
+                        decExp += expVal;
+                    } else {
+                        //
+                        // The intent here is to end up with
+                        // infinity or zero, as appropriate.
+                        // The reason for yielding such a small decExponent,
+                        // rather than something intuitive such as
+                        // expSign*Integer.MAX_VALUE, is that this value
+                        // is subject to further manipulation in
+                        // doubleValue() and floatValue(), and I don't want
+                        // it to be able to cause overflow there!
+                        // (The only way we can get into trouble here is for
+                        // really outrageous nDigits+nTrailZero, such as 2
+                        // billion.)
+                        //
+                        decExp = expSign * expLimit;
+                    }
+                } else {
+                    // this should not overflow, since we tested
+                    // for expVal > (MAX+N), where N >= abs(decExp)
+                    decExp = decExp + expSign*expVal;
+                }
+
+                // if we saw something not a digit ( or end of string )
+                // after the [Ee][+-], without seeing any digits at all
+                // this is certainly an error. If we saw some digits,
+                // but then some trailing garbage, that might be ok.
+                // so we just fall through in that case.
+                // HUMBUG
+                if ( i == expAt ) {
+                    break parseNumber; // certainly bad
+                }
+            }
+            //
+            // We parsed everything we could.
+            // If there are leftovers, then this is not good input!
+            //
+            if ( i < len &&
+                ((i != len - 1) ||
+                (in.charAt(i) != 'f' &&
+                 in.charAt(i) != 'F' &&
+                 in.charAt(i) != 'd' &&
+                 in.charAt(i) != 'D'))) {
+                break parseNumber; // go throw exception
+            }
+            if(isZero) {
+                return isNegative ? A2BC_NEGATIVE_ZERO : A2BC_POSITIVE_ZERO;
+            }
+            return new ASCIIToBinaryBuffer(isNegative, decExp, digits, nDigits);
+        } catch ( StringIndexOutOfBoundsException e ){ }
+        throw new NumberFormatException("For input string: \"" + in + "\"");
+    }
+
+    private static class HexFloatPattern {
+        /**
+         * Grammar is compatible with hexadecimal floating-point constants
+         * described in section 6.4.4.2 of the C99 specification.
+         */
+        private static final Pattern VALUE = Pattern.compile(
+                   //1           234                   56                7                   8      9
+                    "([-+])?0[xX](((\\p{XDigit}+)\\.?)|((\\p{XDigit}*)\\.(\\p{XDigit}+)))[pP]([-+])?(\\p{Digit}+)[fFdD]?"
+                    );
+    }
+
+    /**
+     * Converts string s to a suitable floating decimal; uses the
+     * double constructor and sets the roundDir variable appropriately
+     * in case the value is later converted to a float.
+     *
+     * @param s The String to parse.
+     */
+   static ASCIIToBinaryConverter parseHexString(String s) {
+            // Verify string is a member of the hexadecimal floating-point
+            // string language.
+            Matcher m = HexFloatPattern.VALUE.matcher(s);
+            boolean validInput = m.matches();
+            if (!validInput) {
+                // Input does not match pattern
+                throw new NumberFormatException("For input string: \"" + s + "\"");
+            } else { // validInput
+                //
+                // We must isolate the sign, significand, and exponent
+                // fields.  The sign value is straightforward.  Since
+                // floating-point numbers are stored with a normalized
+                // representation, the significand and exponent are
+                // interrelated.
+                //
+                // After extracting the sign, we normalized the
+                // significand as a hexadecimal value, calculating an
+                // exponent adjust for any shifts made during
+                // normalization.  If the significand is zero, the
+                // exponent doesn't need to be examined since the output
+                // will be zero.
+                //
+                // Next the exponent in the input string is extracted.
+                // Afterwards, the significand is normalized as a *binary*
+                // value and the input value's normalized exponent can be
+                // computed.  The significand bits are copied into a
+                // double significand; if the string has more logical bits
+                // than can fit in a double, the extra bits affect the
+                // round and sticky bits which are used to round the final
+                // value.
+                //
+                //  Extract significand sign
+                String group1 = m.group(1);
+                boolean isNegative = ((group1 != null) && group1.equals("-"));
+
+                //  Extract Significand magnitude
+                //
+                // Based on the form of the significand, calculate how the
+                // binary exponent needs to be adjusted to create a
+                // normalized//hexadecimal* floating-point number; that
+                // is, a number where there is one nonzero hex digit to
+                // the left of the (hexa)decimal point.  Since we are
+                // adjusting a binary, not hexadecimal exponent, the
+                // exponent is adjusted by a multiple of 4.
+                //
+                // There are a number of significand scenarios to consider;
+                // letters are used in indicate nonzero digits:
+                //
+                // 1. 000xxxx       =>      x.xxx   normalized
+                //    increase exponent by (number of x's - 1)*4
+                //
+                // 2. 000xxx.yyyy =>        x.xxyyyy        normalized
+                //    increase exponent by (number of x's - 1)*4
+                //
+                // 3. .000yyy  =>   y.yy    normalized
+                //    decrease exponent by (number of zeros + 1)*4
+                //
+                // 4. 000.00000yyy => y.yy normalized
+                //    decrease exponent by (number of zeros to right of point + 1)*4
+                //
+                // If the significand is exactly zero, return a properly
+                // signed zero.
+                //
+
+                String significandString;
+                int signifLength;
+                int exponentAdjust;
+                {
+                    int leftDigits = 0; // number of meaningful digits to
+                    // left of "decimal" point
+                    // (leading zeros stripped)
+                    int rightDigits = 0; // number of digits to right of
+                    // "decimal" point; leading zeros
+                    // must always be accounted for
+                    //
+                    // The significand is made up of either
+                    //
+                    // 1. group 4 entirely (integer portion only)
+                    //
+                    // OR
+                    //
+                    // 2. the fractional portion from group 7 plus any
+                    // (optional) integer portions from group 6.
+                    //
+                    String group4;
+                    if ((group4 = m.group(4)) != null) {  // Integer-only significand
+                        // Leading zeros never matter on the integer portion
+                        significandString = stripLeadingZeros(group4);
+                        leftDigits = significandString.length();
+                    } else {
+                        // Group 6 is the optional integer; leading zeros
+                        // never matter on the integer portion
+                        String group6 = stripLeadingZeros(m.group(6));
+                        leftDigits = group6.length();
+
+                        // fraction
+                        String group7 = m.group(7);
+                        rightDigits = group7.length();
+
+                        // Turn "integer.fraction" into "integer"+"fraction"
+                        significandString =
+                                ((group6 == null) ? "" : group6) + // is the null
+                                        // check necessary?
+                                        group7;
+                    }
+
+                    significandString = stripLeadingZeros(significandString);
+                    signifLength = significandString.length();
+
+                    //
+                    // Adjust exponent as described above
+                    //
+                    if (leftDigits >= 1) {  // Cases 1 and 2
+                        exponentAdjust = 4 * (leftDigits - 1);
+                    } else {                // Cases 3 and 4
+                        exponentAdjust = -4 * (rightDigits - signifLength + 1);
+                    }
+
+                    // If the significand is zero, the exponent doesn't
+                    // matter; return a properly signed zero.
+
+                    if (signifLength == 0) { // Only zeros in input
+                        return isNegative ? A2BC_NEGATIVE_ZERO : A2BC_POSITIVE_ZERO;
+                    }
+                }
+
+                //  Extract Exponent
+                //
+                // Use an int to read in the exponent value; this should
+                // provide more than sufficient range for non-contrived
+                // inputs.  If reading the exponent in as an int does
+                // overflow, examine the sign of the exponent and
+                // significand to determine what to do.
+                //
+                String group8 = m.group(8);
+                boolean positiveExponent = (group8 == null) || group8.equals("+");
+                long unsignedRawExponent;
+                try {
+                    unsignedRawExponent = Integer.parseInt(m.group(9));
+                }
+                catch (NumberFormatException e) {
+                    // At this point, we know the exponent is
+                    // syntactically well-formed as a sequence of
+                    // digits.  Therefore, if an NumberFormatException
+                    // is thrown, it must be due to overflowing int's
+                    // range.  Also, at this point, we have already
+                    // checked for a zero significand.  Thus the signs
+                    // of the exponent and significand determine the
+                    // final result:
+                    //
+                    //                      significand
+                    //                      +               -
+                    // exponent     +       +infinity       -infinity
+                    //              -       +0.0            -0.0
+                    return isNegative ?
+                              (positiveExponent ? A2BC_NEGATIVE_INFINITY : A2BC_NEGATIVE_ZERO)
+                            : (positiveExponent ? A2BC_POSITIVE_INFINITY : A2BC_POSITIVE_ZERO);
+
+                }
+
+                long rawExponent =
+                        (positiveExponent ? 1L : -1L) * // exponent sign
+                                unsignedRawExponent;            // exponent magnitude
+
+                // Calculate partially adjusted exponent
+                long exponent = rawExponent + exponentAdjust;
+
+                // Starting copying non-zero bits into proper position in
+                // a long; copy explicit bit too; this will be masked
+                // later for normal values.
+
+                boolean round = false;
+                boolean sticky = false;
+                int nextShift;
+                long significand = 0L;
+                // First iteration is different, since we only copy
+                // from the leading significand bit; one more exponent
+                // adjust will be needed...
+
+                // IMPORTANT: make leadingDigit a long to avoid
+                // surprising shift semantics!
+                long leadingDigit = getHexDigit(significandString, 0);
+
+                //
+                // Left shift the leading digit (53 - (bit position of
+                // leading 1 in digit)); this sets the top bit of the
+                // significand to 1.  The nextShift value is adjusted
+                // to take into account the number of bit positions of
+                // the leadingDigit actually used.  Finally, the
+                // exponent is adjusted to normalize the significand
+                // as a binary value, not just a hex value.
+                //
+                if (leadingDigit == 1) {
+                    significand |= leadingDigit << 52;
+                    nextShift = 52 - 4;
+                    // exponent += 0
+                } else if (leadingDigit <= 3) { // [2, 3]
+                    significand |= leadingDigit << 51;
+                    nextShift = 52 - 5;
+                    exponent += 1;
+                } else if (leadingDigit <= 7) { // [4, 7]
+                    significand |= leadingDigit << 50;
+                    nextShift = 52 - 6;
+                    exponent += 2;
+                } else if (leadingDigit <= 15) { // [8, f]
+                    significand |= leadingDigit << 49;
+                    nextShift = 52 - 7;
+                    exponent += 3;
+                } else {
+                    throw new AssertionError("Result from digit conversion too large!");
+                }
+                // The preceding if-else could be replaced by a single
+                // code block based on the high-order bit set in
+                // leadingDigit.  Given leadingOnePosition,
+
+                // significand |= leadingDigit << (SIGNIFICAND_WIDTH - leadingOnePosition);
+                // nextShift = 52 - (3 + leadingOnePosition);
+                // exponent += (leadingOnePosition-1);
+
+                //
+                // Now the exponent variable is equal to the normalized
+                // binary exponent.  Code below will make representation
+                // adjustments if the exponent is incremented after
+                // rounding (includes overflows to infinity) or if the
+                // result is subnormal.
+                //
+
+                // Copy digit into significand until the significand can't
+                // hold another full hex digit or there are no more input
+                // hex digits.
+                int i = 0;
+                for (i = 1;
+                     i < signifLength && nextShift >= 0;
+                     i++) {
+                    long currentDigit = getHexDigit(significandString, i);
+                    significand |= (currentDigit << nextShift);
+                    nextShift -= 4;
+                }
+
+                // After the above loop, the bulk of the string is copied.
+                // Now, we must copy any partial hex digits into the
+                // significand AND compute the round bit and start computing
+                // sticky bit.
+
+                if (i < signifLength) { // at least one hex input digit exists
+                    long currentDigit = getHexDigit(significandString, i);
+
+                    // from nextShift, figure out how many bits need
+                    // to be copied, if any
+                    switch (nextShift) { // must be negative
+                        case -1:
+                            // three bits need to be copied in; can
+                            // set round bit
+                            significand |= ((currentDigit & 0xEL) >> 1);
+                            round = (currentDigit & 0x1L) != 0L;
+                            break;
+
+                        case -2:
+                            // two bits need to be copied in; can
+                            // set round and start sticky
+                            significand |= ((currentDigit & 0xCL) >> 2);
+                            round = (currentDigit & 0x2L) != 0L;
+                            sticky = (currentDigit & 0x1L) != 0;
+                            break;
+
+                        case -3:
+                            // one bit needs to be copied in
+                            significand |= ((currentDigit & 0x8L) >> 3);
+                            // Now set round and start sticky, if possible
+                            round = (currentDigit & 0x4L) != 0L;
+                            sticky = (currentDigit & 0x3L) != 0;
+                            break;
+
+                        case -4:
+                            // all bits copied into significand; set
+                            // round and start sticky
+                            round = ((currentDigit & 0x8L) != 0);  // is top bit set?
+                            // nonzeros in three low order bits?
+                            sticky = (currentDigit & 0x7L) != 0;
+                            break;
+
+                        default:
+                            throw new AssertionError("Unexpected shift distance remainder.");
+                            // break;
+                    }
+
+                    // Round is set; sticky might be set.
+
+                    // For the sticky bit, it suffices to check the
+                    // current digit and test for any nonzero digits in
+                    // the remaining unprocessed input.
+                    i++;
+                    while (i < signifLength && !sticky) {
+                        currentDigit = getHexDigit(significandString, i);
+                        sticky = sticky || (currentDigit != 0);
+                        i++;
+                    }
+
+                }
+                // else all of string was seen, round and sticky are
+                // correct as false.
+
+                // Float calculations
+                int floatBits = isNegative ? FloatConsts.SIGN_BIT_MASK : 0;
+                if (exponent >= Float.MIN_EXPONENT) {
+                    if (exponent > Float.MAX_EXPONENT) {
+                        // Float.POSITIVE_INFINITY
+                        floatBits |= FloatConsts.EXP_BIT_MASK;
+                    } else {
+                        int threshShift = DoubleConsts.SIGNIFICAND_WIDTH - FloatConsts.SIGNIFICAND_WIDTH - 1;
+                        boolean floatSticky = (significand & ((1L << threshShift) - 1)) != 0 || round || sticky;
+                        int iValue = (int) (significand >>> threshShift);
+                        if ((iValue & 3) != 1 || floatSticky) {
+                            iValue++;
+                        }
+                        floatBits |= (((((int) exponent) + (FloatConsts.EXP_BIAS - 1))) << SINGLE_EXP_SHIFT) + (iValue >> 1);
+                    }
+                } else {
+                    if (exponent < FloatConsts.MIN_SUB_EXPONENT - 1) {
+                        // 0
+                    } else {
+                        // exponent == -127 ==> threshShift = 53 - 2 + (-149) - (-127) = 53 - 24
+                        int threshShift = (int) ((DoubleConsts.SIGNIFICAND_WIDTH - 2 + FloatConsts.MIN_SUB_EXPONENT) - exponent);
+                        assert threshShift >= DoubleConsts.SIGNIFICAND_WIDTH - FloatConsts.SIGNIFICAND_WIDTH;
+                        assert threshShift < DoubleConsts.SIGNIFICAND_WIDTH;
+                        boolean floatSticky = (significand & ((1L << threshShift) - 1)) != 0 || round || sticky;
+                        int iValue = (int) (significand >>> threshShift);
+                        if ((iValue & 3) != 1 || floatSticky) {
+                            iValue++;
+                        }
+                        floatBits |= iValue >> 1;
+                    }
+                }
+                float fValue = Float.intBitsToFloat(floatBits);
+
+                // Check for overflow and update exponent accordingly.
+                if (exponent > Double.MAX_EXPONENT) {         // Infinite result
+                    // overflow to properly signed infinity
+                    return isNegative ? A2BC_NEGATIVE_INFINITY : A2BC_POSITIVE_INFINITY;
+                } else {  // Finite return value
+                    if (exponent <= Double.MAX_EXPONENT && // (Usually) normal result
+                            exponent >= Double.MIN_EXPONENT) {
+
+                        // The result returned in this block cannot be a
+                        // zero or subnormal; however after the
+                        // significand is adjusted from rounding, we could
+                        // still overflow in infinity.
+
+                        // AND exponent bits into significand; if the
+                        // significand is incremented and overflows from
+                        // rounding, this combination will update the
+                        // exponent correctly, even in the case of
+                        // Double.MAX_VALUE overflowing to infinity.
+
+                        significand = ((( exponent +
+                                (long) DoubleConsts.EXP_BIAS) <<
+                                (DoubleConsts.SIGNIFICAND_WIDTH - 1))
+                                & DoubleConsts.EXP_BIT_MASK) |
+                                (DoubleConsts.SIGNIF_BIT_MASK & significand);
+
+                    } else {  // Subnormal or zero
+                        // (exponent < Double.MIN_EXPONENT)
+
+                        if (exponent < (DoubleConsts.MIN_SUB_EXPONENT - 1)) {
+                            // No way to round back to nonzero value
+                            // regardless of significand if the exponent is
+                            // less than -1075.
+                            return isNegative ? A2BC_NEGATIVE_ZERO : A2BC_POSITIVE_ZERO;
+                        } else { //  -1075 <= exponent <= MIN_EXPONENT -1 = -1023
+                            //
+                            // Find bit position to round to; recompute
+                            // round and sticky bits, and shift
+                            // significand right appropriately.
+                            //
+
+                            sticky = sticky || round;
+                            round = false;
+
+                            // Number of bits of significand to preserve is
+                            // exponent - abs_min_exp +1
+                            // check:
+                            // -1075 +1074 + 1 = 0
+                            // -1023 +1074 + 1 = 52
+
+                            int bitsDiscarded = 53 -
+                                    ((int) exponent - DoubleConsts.MIN_SUB_EXPONENT + 1);
+                            assert bitsDiscarded >= 1 && bitsDiscarded <= 53;
+
+                            // What to do here:
+                            // First, isolate the new round bit
+                            round = (significand & (1L << (bitsDiscarded - 1))) != 0L;
+                            if (bitsDiscarded > 1) {
+                                // create mask to update sticky bits; low
+                                // order bitsDiscarded bits should be 1
+                                long mask = ~((~0L) << (bitsDiscarded - 1));
+                                sticky = sticky || ((significand & mask) != 0L);
+                            }
+
+                            // Now, discard the bits
+                            significand = significand >> bitsDiscarded;
+
+                            significand = ((((long) (Double.MIN_EXPONENT - 1) + // subnorm exp.
+                                    (long) DoubleConsts.EXP_BIAS) <<
+                                    (DoubleConsts.SIGNIFICAND_WIDTH - 1))
+                                    & DoubleConsts.EXP_BIT_MASK) |
+                                    (DoubleConsts.SIGNIF_BIT_MASK & significand);
+                        }
+                    }
+
+                    // The significand variable now contains the currently
+                    // appropriate exponent bits too.
+
+                    //
+                    // Determine if significand should be incremented;
+                    // making this determination depends on the least
+                    // significant bit and the round and sticky bits.
+                    //
+                    // Round to nearest even rounding table, adapted from
+                    // table 4.7 in "Computer Arithmetic" by IsraelKoren.
+                    // The digit to the left of the "decimal" point is the
+                    // least significant bit, the digits to the right of
+                    // the point are the round and sticky bits
+                    //
+                    // Number       Round(x)
+                    // x0.00        x0.
+                    // x0.01        x0.
+                    // x0.10        x0.
+                    // x0.11        x1. = x0. +1
+                    // x1.00        x1.
+                    // x1.01        x1.
+                    // x1.10        x1. + 1
+                    // x1.11        x1. + 1
+                    //
+                    boolean leastZero = ((significand & 1L) == 0L);
+                    if ((leastZero && round && sticky) ||
+                            ((!leastZero) && round)) {
+                        significand++;
+                    }
+
+                    double value = isNegative ?
+                            Double.longBitsToDouble(significand | DoubleConsts.SIGN_BIT_MASK) :
+                            Double.longBitsToDouble(significand );
+
+                    return new PreparedASCIIToBinaryBuffer(value, fValue);
+                }
+            }
+    }
+
+    /**
+     * Returns s with any leading zeros removed.
+     */
+    static String stripLeadingZeros(String s) {
+        if(!s.isEmpty() && s.charAt(0)=='0') {
+            for(int i=1; iposition
+     * of string s.
+     */
+    static int getHexDigit(String s, int position) {
+        int value = Character.digit(s.charAt(position), 16);
+        if (value <= -1 || value >= 16) {
+            throw new AssertionError("Unexpected failure of digit conversion of " +
+                                     s.charAt(position));
+        }
+        return value;
+    }
+}
diff --git a/tests/test_data/std/jdk/internal/math/FormattedFPDecimal.class b/tests/test_data/std/jdk/internal/math/FormattedFPDecimal.class
new file mode 100644
index 00000000..9d19e2ff
Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/FormattedFPDecimal.class differ
diff --git a/tests/test_data/std/jdk/internal/math/FormattedFPDecimal.java b/tests/test_data/std/jdk/internal/math/FormattedFPDecimal.java
new file mode 100644
index 00000000..ce9b2fc4
--- /dev/null
+++ b/tests/test_data/std/jdk/internal/math/FormattedFPDecimal.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.math;
+
+/*
+ * This class provides support for the 'e', 'f' and 'g' conversions on double
+ * values with sign bit 0.
+ * It is worth noting that float values are converted to double values _before_
+ * control reaches code in this class.
+ *
+ * It delegates the conversion to decimal to class DoubleToDecimal to get
+ * the decimal d selected by Double.toString(double) as a pair of integers
+ * f and e meeting d = f 10^e.
+ * It then rounds d to the appropriate number of digits, as per specification,
+ * and extracts the digits of both the significand and, where required, the
+ * exponent of the rounded value.
+ *
+ * Further processing like padding, sign, grouping, localization, etc., is the
+ * responsibility of the caller.
+ */
+public final class FormattedFPDecimal {
+
+    public static final char SCIENTIFIC = 'e';
+    public static final char PLAIN      = 'f';
+    public static final char GENERAL    = 'g';
+
+    private long f;
+    private int e;  // normalized to 0 when f = 0
+    private int n;
+    private char[] digits;  // ... and often the decimal separator as well
+    private char[] exp;  // [+-][e]ee, that is, sign and minimum 2 digits
+
+    private FormattedFPDecimal() {
+    }
+
+    public static FormattedFPDecimal valueOf(double v, int prec, char form) {
+        FormattedFPDecimal fd = new FormattedFPDecimal();
+        DoubleToDecimal.split(v, fd);
+        return switch (form) {
+            case SCIENTIFIC -> fd.scientific(prec);
+            case PLAIN      -> fd.plain(prec);
+            case GENERAL    -> fd.general(prec);
+            default         -> throw new IllegalArgumentException(
+                    String.format("unsupported form '%c'", form)
+            );
+        };
+    }
+
+    public void set(long f, int e, int n) {
+        /* Initially, n = 0 if f = 0, and 10^{n-1} <= f < 10^n if f != 0 */
+        this.f = f;
+        this.e = e;
+        this.n = n;
+    }
+
+    public char[] getExponent() {
+        return exp;
+    }
+
+    public char[] getMantissa() {
+        return digits;
+    }
+
+    public int getExponentRounded() {
+        return n + e - 1;
+    }
+
+    private FormattedFPDecimal plain(int prec) {
+        /*
+         * Rounding d = f 10^e to prec digits in plain mode means the same
+         * as rounding it to the p = n + e + prec most significand digits of d,
+         * with the understanding that p < 0 cuts off all its digits.
+         */
+        round(n + e + (long) prec);  // n + e is well inside the int range
+        return plainChars();
+    }
+
+    private FormattedFPDecimal plainChars() {
+        if (e >= 0) {
+            plainCharsPureInteger();
+        } else if (n + e > 0) {
+            plainCharsMixed();
+        } else {
+            plainCharsPureFraction();
+        }
+        return this;
+    }
+
+    private void plainCharsPureInteger() {
+        digits = new char[n + e];
+        fillWithZeros(n, n + e);
+        fillWithDigits(f, 0, n);
+    }
+
+    private void plainCharsMixed() {
+        digits = new char[n + 1];
+        long x = fillWithDigits(f, n + 1 + e, n + 1);
+        digits[n + e] = '.';
+        fillWithDigits(x, 0, n + e);
+    }
+
+    private void plainCharsPureFraction() {
+        digits = new char[2 - e];
+        long x = f;
+        fillWithDigits(x, 2 - e - n, 2 - e);
+        fillWithZeros(0, 2 - e - n);
+        digits[1] = '.';
+    }
+
+    private FormattedFPDecimal scientific(int prec) {
+        /*
+         * Rounding d = f 10^e to prec digits in scientific mode means the same
+         * as rounding it to the p = prec + 1 most significand digits of d.
+         */
+        round(prec + 1L);
+        return scientificChars(prec);
+    }
+
+    private FormattedFPDecimal scientificChars(int prec) {
+        if (prec != 0) {
+            scientificCharsWithFraction();
+        } else {
+            scientificCharsNoFraction();
+        }
+        expChars();
+        return this;
+    }
+
+    private void scientificCharsWithFraction() {
+        digits = new char[1 + n];  // room for leading digit and for '.'
+        long x = fillWithDigits(f, 2, 1 + n);
+        digits[1] = '.';
+        digits[0] = toDigit(x);
+    }
+
+    private void scientificCharsNoFraction() {
+        digits = new char[1];
+        digits[0] = toDigit(f);
+    }
+
+    private FormattedFPDecimal general(int prec) {
+        /*
+         * Rounding d = f 10^e to prec digits in general mode means the same
+         * as rounding it to the p = prec most significand digits of d, and then
+         * deciding whether to format it in plain or scientific mode, depending
+         * on the rounded value.
+         */
+        round(prec);
+        int er = getExponentRounded();
+        if (-4 <= er && er < prec) {
+            plainChars();
+        } else {
+            scientificChars(prec - 1);
+        }
+        return this;
+    }
+
+    private void expChars() {
+        int er = getExponentRounded();
+        int aer = Math.abs(er);
+        exp = new char[aer >= 100 ? 4 : 3];
+        int q;
+        if (aer >= 100) {
+            q = aer / 10;
+            exp[3] = toDigit(aer - 10 * q);
+            aer = q;
+        }
+        q = aer / 10;
+        exp[2] = toDigit(aer - 10 * q);
+        exp[1] = toDigit(q);
+        exp[0] = er >= 0 ? '+' : '-';
+    }
+
+    private void round(long pp) {
+        /*
+         * Let d = f 10^e, and let p shorten pp.
+         * This method rounds d to the p most significant digits.
+         * It does so by possibly modifying f, e and n.
+         * When f becomes 0, e and n are normalized to 0 and 1, resp.
+         *
+         * For any real x let
+         *      r(x) = floor(x + 1/2)
+         * which is rounding to the closest integer, with ties rounded toward
+         * positive infinity.
+         *
+         * When f = 0 there's not much to say, except that this holds iff n = 0.
+         *
+         * Otherwise, since
+         *      10^{n-1} <= f < 10^n
+         * it follows that
+         *      10^{e+n-1} <= d < 10^{e+n}
+         * To round d to the most significant p digits, first scale d to the
+         * range [10^{p-1}, 10^p), cutoff the fractional digits by applying r,
+         * and finally scale back.
+         * To this end, first define
+         *      ds = d 10^{p-e-n}
+         * which ensures
+         *      10^{p-1} <= ds < 10^p
+         *
+         * Now, if p < 0 (that is, if p <= -1) then
+         *      ds < 10^p <= 10^{-1} < 1/2
+         * so that
+         *      r(ds) = 0
+         * Thus, rounding d to p < 0 digits leads to 0.
+         */
+        if (n == 0 || pp < 0) {
+            f = 0;
+            e = 0;
+            n = 1;
+            return;
+        }
+
+        /*
+         * Further, if p >= n then
+         *      ds = f 10^e 10^{p-e-n} = f 10^{p-n}
+         * which shows that ds is an integer, so r(ds) = ds. That is,
+         * rounding to p >= n digits leads to a result equal to d.
+         */
+        if (pp >= n) {  // no rounding needed
+            return;
+        }
+
+        /*
+         * Finally, 0 <= p < n. When p = 0 it follows that
+         *      10^{-1} <= ds < 1
+         *      0 <= f' = r(ds) <= 1
+         * that is, f' is either 0 or 1.
+         *
+         * Otherwise
+         *      10^{p-1} <= ds < 10^p
+         *      1 <= 10^{p-1} <= f' = r(ds) <= 10^p
+         * Note that f' = 10^p is a possible outcome.
+         *
+         * Scale back, where e' = e + n - p
+         *      d' = f' 10^{e+n-p} = f' 10^e', with 10^{e+n-1} <= d' <= 10^{e+n}
+         *
+         * Since n > p, f' can be computed in integer arithmetic as follows,
+         * where / denotes division in the real numbers:
+         *      f' = r(ds) = r(f 10^{p-n}) = r(f / 10^{n-p})
+         *          = floor(f / 10^{n-p} + 1/2)
+         *          = floor((f + 10^{n-p}/2) / 10^{n-p})
+         */
+        int p = (int) pp;  // 0 <= pp < n, safe cast
+        e += n - p;  // new e is well inside the int range
+        long pow10 = MathUtils.pow10(n - p);
+        f = (f + (pow10 >> 1)) / pow10;
+        if (p == 0) {
+            n = 1;
+            if (f == 0) {
+                e = 0;
+            }
+            return;
+        }
+
+        n = p;
+        if (f == MathUtils.pow10(p)) {
+            /*
+             * f is n + 1 digits long.
+             * Absorb one trailing zero into e and reduce f accordingly.
+             */
+            f /= 10;
+            e += 1;
+        }
+    }
+
+    /*
+     * Fills the digits section with indices in [from, to) with the lower
+     * to - from digits of x (as chars), while stripping them away from x.
+     * Returns the stripped x.
+     */
+    private long fillWithDigits(long x, int from, int to) {
+        while (to > from) {
+            long q = x / 10;
+            digits[--to] = toDigit(x - q * 10);
+            x = q;
+        }
+        return x;
+    }
+
+    /*
+     * Fills the digits section with indices in [from, to) with '0'.
+     */
+    private void fillWithZeros(int from, int to) {
+        while (to > from) {
+            digits[--to] = '0';
+        }
+    }
+
+    private static char toDigit(long d) {
+        return toDigit((int) d);
+    }
+
+    private static char toDigit(int d) {
+        return (char) (d + '0');
+    }
+
+}
diff --git a/tests/test_data/std/jdk/internal/math/MathUtils.class b/tests/test_data/std/jdk/internal/math/MathUtils.class
new file mode 100644
index 00000000..2faa5489
Binary files /dev/null and b/tests/test_data/std/jdk/internal/math/MathUtils.class differ
diff --git a/tests/test_data/std/jdk/internal/math/MathUtils.java b/tests/test_data/std/jdk/internal/math/MathUtils.java
new file mode 100644
index 00000000..3c52a244
--- /dev/null
+++ b/tests/test_data/std/jdk/internal/math/MathUtils.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.math;
+
+/**
+ * This class exposes package private utilities for other classes.
+ * Thus, all methods are assumed to be invoked with correct arguments,
+ * so these are not checked at all.
+ */
+final class MathUtils {
+    /*
+     * For full details about this code see the following reference:
+     *
+     *     Giulietti, "The Schubfach way to render doubles",
+     *     https://drive.google.com/file/d/1gp5xv4CAa78SVgCeWfGqqI4FfYYYuNFb
+     */
+
+    /*
+     * The boundaries for k in g0(int) and g1(int).
+     * K_MIN must be DoubleToDecimal.K_MIN or less.
+     * K_MAX must be DoubleToDecimal.K_MAX or more.
+     */
+    static final int K_MIN = -324;
+    static final int K_MAX = 292;
+
+    /* Must be DoubleToDecimal.H or more */
+    static final int H = 17;
+
+    /* C_10 = floor(log10(2) * 2^Q_10), A_10 = floor(log10(3/4) * 2^Q_10) */
+    private static final int Q_10 = 41;
+    private static final long C_10 = 661_971_961_083L;
+    private static final long A_10 = -274_743_187_321L;
+
+    /* C_2 = floor(log2(10) * 2^Q_2) */
+    private static final int Q_2 = 38;
+    private static final long C_2 = 913_124_641_741L;
+
+    private MathUtils() {
+        throw new RuntimeException("not supposed to be instantiated.");
+    }
+
+    /* The first powers of 10. The last entry must be 10^(DoubleToDecimal.H) */
+    private static final long[] pow10 = {
+        1L,
+        10L,
+        100L,
+        1_000L,
+        10_000L,
+        100_000L,
+        1_000_000L,
+        10_000_000L,
+        100_000_000L,
+        1_000_000_000L,
+        10_000_000_000L,
+        100_000_000_000L,
+        1_000_000_000_000L,
+        10_000_000_000_000L,
+        100_000_000_000_000L,
+        1_000_000_000_000_000L,
+        10_000_000_000_000_000L,
+        100_000_000_000_000_000L,
+    };
+
+    /**
+     * Returns 10{@code e}.
+     *
+     * @param e The exponent which must meet
+     *          0 ≤ {@code e} ≤ {@link #H}.
+     * @return 10{@code e}.
+     */
+    static long pow10(int e) {
+        return pow10[e];
+    }
+
+    /**
+     * Returns the unique integer k such that
+     * 10k ≤ 2{@code e}
+     * < 10k+1.
+     * 

+ * The result is correct when |{@code e}| ≤ 6_432_162. + * Otherwise the result is undefined. + * + * @param e The exponent of 2, which should meet + * |{@code e}| ≤ 6_432_162 for safe results. + * @return ⌊log102{@code e}⌋. + */ + static int flog10pow2(int e) { + return (int) (e * C_10 >> Q_10); + } + + /** + * Returns the unique integer k such that + * 10k ≤ 3/4 · 2{@code e} + * < 10k+1. + *

+ * The result is correct when + * -3_606_689 ≤ {@code e} ≤ 3_150_619. + * Otherwise the result is undefined. + * + * @param e The exponent of 2, which should meet + * -3_606_689 ≤ {@code e} ≤ 3_150_619 for safe results. + * @return ⌊log10(3/4 · + * 2{@code e})⌋. + */ + static int flog10threeQuartersPow2(int e) { + return (int) (e * C_10 + A_10 >> Q_10); + } + + /** + * Returns the unique integer k such that + * 2k ≤ 10{@code e} + * < 2k+1. + *

+ * The result is correct when |{@code e}| ≤ 1_838_394. + * Otherwise the result is undefined. + * + * @param e The exponent of 10, which should meet + * |{@code e}| ≤ 1_838_394 for safe results. + * @return ⌊log210{@code e}⌋. + */ + static int flog2pow10(int e) { + return (int) (e * C_2 >> Q_2); + } + + /** + * Let 10-{@code k} = β 2r, + * for the unique pair of integer r and real β meeting + * 2125β < 2126. + * Further, let g = ⌊β⌋ + 1. + * Split g into the higher 63 bits g1 and + * the lower 63 bits g0. Thus, + * g1 = + * ⌊g 2-63⌋ + * and + * g0 = + * g - g1 263. + *

+ * This method returns g1 while + * {@link #g0(int)} returns g0. + *

+ * If needed, the exponent r can be computed as + * r = {@code flog2pow10(-k)} - 125 (see {@link #flog2pow10(int)}). + * + * @param k The exponent of 10, which must meet + * {@link #K_MIN} ≤ {@code e} ≤ {@link #K_MAX}. + * @return g1 as described above. + */ + static long g1(int k) { + return g[k - K_MIN << 1]; + } + + /** + * Returns g0 as described in + * {@link #g1(int)}. + * + * @param k The exponent of 10, which must meet + * {@link #K_MIN} ≤ {@code e} ≤ {@link #K_MAX}. + * @return g0 as described in + * {@link #g1(int)}. + */ + static long g0(int k) { + return g[k - K_MIN << 1 | 1]; + } + + /* + * The precomputed values for g1(int) and g0(int). + * The first entry must be for an exponent of K_MIN or less. + * The last entry must be for an exponent of K_MAX or more. + */ + private static final long[] g = { + 0x4F0C_EDC9_5A71_8DD4L, 0x5B01_E8B0_9AA0_D1B5L, // -324 + 0x7E7B_160E_F71C_1621L, 0x119C_A780_F767_B5EEL, // -323 + 0x652F_44D8_C5B0_11B4L, 0x0E16_EC67_2C52_F7F2L, // -322 + 0x50F2_9D7A_37C0_0E29L, 0x5812_56B8_F042_5FF5L, // -321 + 0x40C2_1794_F966_71BAL, 0x79A8_4560_C035_1991L, // -320 + 0x679C_F287_F570_B5F7L, 0x75DA_089A_CD21_C281L, // -319 + 0x52E3_F539_9126_F7F9L, 0x44AE_6D48_A41B_0201L, // -318 + 0x424F_F761_40EB_F994L, 0x36F1_F106_E9AF_34CDL, // -317 + 0x6A19_8BCE_CE46_5C20L, 0x57E9_81A4_A918_547BL, // -316 + 0x54E1_3CA5_71D1_E34DL, 0x2CBA_CE1D_5413_76C9L, // -315 + 0x43E7_63B7_8E41_82A4L, 0x23C8_A4E4_4342_C56EL, // -314 + 0x6CA5_6C58_E39C_043AL, 0x060D_D4A0_6B9E_08B0L, // -313 + 0x56EA_BD13_E949_9CFBL, 0x1E71_76E6_BC7E_6D59L, // -312 + 0x4588_9743_2107_B0C8L, 0x7EC1_2BEB_C9FE_BDE1L, // -311 + 0x6F40_F205_01A5_E7A7L, 0x7E01_DFDF_A997_9635L, // -310 + 0x5900_C19D_9AEB_1FB9L, 0x4B34_B319_5479_44F7L, // -309 + 0x4733_CE17_AF22_7FC7L, 0x55C3_C27A_A9FA_9D93L, // -308 + 0x71EC_7CF2_B1D0_CC72L, 0x5606_03F7_765D_C8EAL, // -307 + 0x5B23_9728_8E40_A38EL, 0x7804_CFF9_2B7E_3A55L, // -306 + 0x48E9_45BA_0B66_E93FL, 0x1337_0CC7_55FE_9511L, // -305 + 0x74A8_6F90_123E_41FEL, 0x51F1_AE0B_BCCA_881BL, // -304 + 0x5D53_8C73_41CB_67FEL, 0x74C1_5809_63D5_39AFL, // -303 + 0x4AA9_3D29_016F_8665L, 0x43CD_E007_8310_FAF3L, // -302 + 0x7775_2EA8_024C_0A3CL, 0x0616_333F_381B_2B1EL, // -301 + 0x5F90_F220_01D6_6E96L, 0x3811_C298_F9AF_55B1L, // -300 + 0x4C73_F4E6_67DE_BEDEL, 0x600E_3547_2E25_DE28L, // -299 + 0x7A53_2170_A631_3164L, 0x3349_EED8_49D6_303FL, // -298 + 0x61DC_1AC0_84F4_2783L, 0x42A1_8BE0_3B11_C033L, // -297 + 0x4E49_AF00_6A5C_EC69L, 0x1BB4_6FE6_95A7_CCF5L, // -296 + 0x7D42_B19A_43C7_E0A8L, 0x2C53_E63D_BC3F_AE55L, // -295 + 0x6435_5AE1_CFD3_1A20L, 0x2376_51CA_FCFF_BEAAL, // -294 + 0x502A_AF1B_0CA8_E1B3L, 0x35F8_416F_30CC_9888L, // -293 + 0x4022_25AF_3D53_E7C2L, 0x5E60_3458_F3D6_E06DL, // -292 + 0x669D_0918_621F_D937L, 0x4A33_86F4_B957_CD7BL, // -291 + 0x5217_3A79_E819_7A92L, 0x6E8F_9F2A_2DDF_D796L, // -290 + 0x41AC_2EC7_ECE1_2EDBL, 0x720C_7F54_F17F_DFABL, // -289 + 0x6913_7E0C_AE35_17C6L, 0x1CE0_CBBB_1BFF_CC45L, // -288 + 0x540F_980A_24F7_4638L, 0x171A_3C95_AFFF_D69EL, // -287 + 0x433F_ACD4_EA5F_6B60L, 0x127B_63AA_F333_1218L, // -286 + 0x6B99_1487_DD65_7899L, 0x6A5F_05DE_51EB_5026L, // -285 + 0x5614_106C_B11D_FA14L, 0x5518_D17E_A7EF_7352L, // -284 + 0x44DC_D9F0_8DB1_94DDL, 0x2A7A_4132_1FF2_C2A8L, // -283 + 0x6E2E_2980_E2B5_BAFBL, 0x5D90_6850_331E_043FL, // -282 + 0x5824_EE00_B55E_2F2FL, 0x6473_86A6_8F4B_3699L, // -281 + 0x4683_F19A_2AB1_BF59L, 0x36C2_D21E_D908_F87BL, // -280 + 0x70D3_1C29_DDE9_3228L, 0x579E_1CFE_280E_5A5DL, // -279 + 0x5A42_7CEE_4B20_F4EDL, 0x2C7E_7D98_200B_7B7EL, // -278 + 0x4835_30BE_A280_C3F1L, 0x09FE_CAE0_19A2_C932L, // -277 + 0x7388_4DFD_D0CE_064EL, 0x4331_4499_C29E_0EB6L, // -276 + 0x5C6D_0B31_73D8_050BL, 0x4F5A_9D47_CEE4_D891L, // -275 + 0x49F0_D5C1_2979_9DA2L, 0x72AE_E439_7250_AD41L, // -274 + 0x764E_22CE_A8C2_95D1L, 0x377E_39F5_83B4_4868L, // -273 + 0x5EA4_E8A5_53CE_DE41L, 0x12CB_6191_3629_D387L, // -272 + 0x4BB7_2084_430B_E500L, 0x756F_8140_F821_7605L, // -271 + 0x7925_00D3_9E79_6E67L, 0x6F18_CECE_59CF_233CL, // -270 + 0x60EA_670F_B1FA_BEB9L, 0x3F47_0BD8_47D8_E8FDL, // -269 + 0x4D88_5272_F4C8_9894L, 0x329F_3CAD_0647_20CAL, // -268 + 0x7C0D_50B7_EE0D_C0EDL, 0x3765_2DE1_A3A5_0143L, // -267 + 0x633D_DA2C_BE71_6724L, 0x2C50_F181_4FB7_3436L, // -266 + 0x4F64_AE8A_31F4_5283L, 0x3D0D_8E01_0C92_902BL, // -265 + 0x7F07_7DA9_E986_EA6BL, 0x7B48_E334_E0EA_8045L, // -264 + 0x659F_97BB_2138_BB89L, 0x4907_1C2A_4D88_669DL, // -263 + 0x514C_7962_80FA_2FA1L, 0x20D2_7CEE_A46D_1EE4L, // -262 + 0x4109_FAB5_33FB_594DL, 0x670E_CA58_838A_7F1DL, // -261 + 0x680F_F788_532B_C216L, 0x0B4A_DD5A_6C10_CB62L, // -260 + 0x533F_F939_DC23_01ABL, 0x22A2_4AAE_BCDA_3C4EL, // -259 + 0x4299_942E_49B5_9AEFL, 0x354E_A225_63E1_C9D8L, // -258 + 0x6A8F_537D_42BC_2B18L, 0x554A_9D08_9FCF_A95AL, // -257 + 0x553F_75FD_CEFC_EF46L, 0x776E_E406_E63F_BAAEL, // -256 + 0x4432_C4CB_0BFD_8C38L, 0x5F8B_E99F_1E99_6225L, // -255 + 0x6D1E_07AB_4662_79F4L, 0x3279_75CB_6428_9D08L, // -254 + 0x574B_3955_D1E8_6190L, 0x2861_2B09_1CED_4A6DL, // -253 + 0x45D5_C777_DB20_4E0DL, 0x06B4_226D_B0BD_D524L, // -252 + 0x6FBC_7259_5E9A_167BL, 0x2453_6A49_1AC9_5506L, // -251 + 0x5963_8EAD_E548_11FCL, 0x1D0F_883A_7BD4_4405L, // -250 + 0x4782_D88B_1DD3_4196L, 0x4A72_D361_FCA9_D004L, // -249 + 0x726A_F411_C952_028AL, 0x43EA_EBCF_FAA9_4CD3L, // -248 + 0x5B88_C341_6DDB_353BL, 0x4FEF_230C_C887_70A9L, // -247 + 0x493A_35CD_F17C_2A96L, 0x0CBF_4F3D_6D39_26EEL, // -246 + 0x7529_EFAF_E8C6_AA89L, 0x6132_1862_485B_717CL, // -245 + 0x5DBB_2626_53D2_2207L, 0x675B_46B5_06AF_8DFDL, // -244 + 0x4AFC_1E85_0FDB_4E6CL, 0x52AF_6BC4_0559_3E64L, // -243 + 0x77F9_CA6E_7FC5_4A47L, 0x377F_12D3_3BC1_FD6DL, // -242 + 0x5FFB_0858_6637_6E9FL, 0x45FF_4242_9634_CABDL, // -241 + 0x4CC8_D379_EB5F_8BB2L, 0x6B32_9B68_782A_3BCBL, // -240 + 0x7ADA_EBF6_4565_AC51L, 0x2B84_2BDA_59DD_2C77L, // -239 + 0x6248_BCC5_0451_56A7L, 0x3C69_BCAE_AE4A_89F9L, // -238 + 0x4EA0_9704_0374_4552L, 0x6387_CA25_583B_A194L, // -237 + 0x7DCD_BE6C_D253_A21EL, 0x05A6_103B_C05F_68EDL, // -236 + 0x64A4_9857_0EA9_4E7EL, 0x37B8_0CFC_99E5_ED8AL, // -235 + 0x5083_AD12_7221_0B98L, 0x2C93_3D96_E184_BE08L, // -234 + 0x4069_5741_F4E7_3C79L, 0x7075_CADF_1AD0_9807L, // -233 + 0x670E_F203_2171_FA5CL, 0x4D89_4498_2AE7_59A4L, // -232 + 0x5272_5B35_B45B_2EB0L, 0x3E07_6A13_5585_E150L, // -231 + 0x41F5_15C4_9048_F226L, 0x64D2_BB42_AAD1_810DL, // -230 + 0x6988_22D4_1A0E_503EL, 0x07B7_9204_4482_6815L, // -229 + 0x546C_E8A9_AE71_D9CBL, 0x1FC6_0E69_D068_5344L, // -228 + 0x438A_53BA_F1F4_AE3CL, 0x196B_3EBB_0D20_429DL, // -227 + 0x6C10_85F7_E987_7D2DL, 0x0F11_FDF8_1500_6A94L, // -226 + 0x5673_9E5F_EE05_FDBDL, 0x58DB_3193_4400_5543L, // -225 + 0x4529_4B7F_F19E_6497L, 0x60AF_5ADC_3666_AA9CL, // -224 + 0x6EA8_78CC_B5CA_3A8CL, 0x344B_C493_8A3D_DDC7L, // -223 + 0x5886_C70A_2B08_2ED6L, 0x5D09_6A0F_A1CB_17D2L, // -222 + 0x46D2_38D4_EF39_BF12L, 0x173A_BB3F_B4A2_7975L, // -221 + 0x7150_5AEE_4B8F_981DL, 0x0B91_2B99_2103_F588L, // -220 + 0x5AA6_AF25_093F_ACE4L, 0x0940_EFAD_B403_2AD3L, // -219 + 0x4885_58EA_6DCC_8A50L, 0x0767_2624_9002_88A9L, // -218 + 0x7408_8E43_E2E0_DD4CL, 0x723E_A36D_B337_410EL, // -217 + 0x5CD3_A503_1BE7_1770L, 0x5B65_4F8A_F5C5_CDA5L, // -216 + 0x4A42_EA68_E31F_45F3L, 0x62B7_72D5_916B_0AEBL, // -215 + 0x76D1_770E_3832_0986L, 0x0458_B7BC_1BDE_77DDL, // -214 + 0x5F0D_F8D8_2CF4_D46BL, 0x1D13_C630_164B_9318L, // -213 + 0x4C0B_2D79_BD90_A9EFL, 0x30DC_9E8C_DEA2_DC13L, // -212 + 0x79AB_7BF5_FC1A_A97FL, 0x0160_FDAE_3104_9351L, // -211 + 0x6155_FCC4_C9AE_EDFFL, 0x1AB3_FE24_F403_A90EL, // -210 + 0x4DDE_63D0_A158_BE65L, 0x6229_981D_9002_EDA5L, // -209 + 0x7C97_061A_9BC1_30A2L, 0x69DC_2695_B337_E2A1L, // -208 + 0x63AC_04E2_1634_26E8L, 0x54B0_1EDE_28F9_821BL, // -207 + 0x4FBC_D0B4_DE90_1F20L, 0x43C0_18B1_BA61_34E2L, // -206 + 0x7F94_8121_6419_CB67L, 0x1F99_C11C_5D68_549DL, // -205 + 0x6610_674D_E9AE_3C52L, 0x4C7B_00E3_7DED_107EL, // -204 + 0x51A6_B90B_2158_3042L, 0x09FC_00B5_FE57_4065L, // -203 + 0x4152_2DA2_8113_59CEL, 0x3B30_0091_9845_CD1DL, // -202 + 0x6883_7C37_34EB_C2E3L, 0x784C_CDB5_C06F_AE95L, // -201 + 0x539C_635F_5D89_68B6L, 0x2D0A_3E2B_0059_5877L, // -200 + 0x42E3_82B2_B13A_BA2BL, 0x3DA1_CB55_99E1_1393L, // -199 + 0x6B05_9DEA_B52A_C378L, 0x629C_7888_F634_EC1EL, // -198 + 0x559E_17EE_F755_692DL, 0x3549_FA07_2B5D_89B1L, // -197 + 0x447E_798B_F911_20F1L, 0x1107_FB38_EF7E_07C1L, // -196 + 0x6D97_28DF_F4E8_34B5L, 0x01A6_5EC1_7F30_0C68L, // -195 + 0x57AC_20B3_2A53_5D5DL, 0x4E1E_B234_65C0_09EDL, // -194 + 0x4623_4D5C_21DC_4AB1L, 0x24E5_5B5D_1E33_3B24L, // -193 + 0x7038_7BC6_9C93_AAB5L, 0x216E_F894_FD1E_C506L, // -192 + 0x59C6_C96B_B076_222AL, 0x4DF2_6077_30E5_6A6CL, // -191 + 0x47D2_3ABC_8D2B_4E88L, 0x3E5B_805F_5A51_21F0L, // -190 + 0x72E9_F794_1512_1740L, 0x63C5_9A32_2A1B_697FL, // -189 + 0x5BEE_5FA9_AA74_DF67L, 0x0304_7B5B_54E2_BACCL, // -188 + 0x498B_7FBA_EEC3_E5ECL, 0x0269_FC49_10B5_623DL, // -187 + 0x75AB_FF91_7E06_3CACL, 0x6A43_2D41_B455_69FBL, // -186 + 0x5E23_32DA_CB38_308AL, 0x21CF_5767_C377_87FCL, // -185 + 0x4B4F_5BE2_3C2C_F3A1L, 0x67D9_12B9_692C_6CCAL, // -184 + 0x787E_F969_F9E1_85CFL, 0x595B_5128_A847_1476L, // -183 + 0x6065_9454_C7E7_9E3FL, 0x6115_DA86_ED05_A9F8L, // -182 + 0x4D1E_1043_D31F_B1CCL, 0x4DAB_1538_BD9E_2193L, // -181 + 0x7B63_4D39_51CC_4FADL, 0x62AB_5527_95C9_CF52L, // -180 + 0x62B5_D761_0E3D_0C8BL, 0x0222_AA86_116E_3F75L, // -179 + 0x4EF7_DF80_D830_D6D5L, 0x4E82_2204_DABE_992AL, // -178 + 0x7E59_659A_F381_57BCL, 0x1736_9CD4_9130_F510L, // -177 + 0x6514_5148_C2CD_DFC9L, 0x5F5E_E3DD_40F3_F740L, // -176 + 0x50DD_0DD3_CF0B_196EL, 0x1918_B64A_9A5C_C5CDL, // -175 + 0x40B0_D7DC_A5A2_7ABEL, 0x4746_F83B_AEB0_9E3EL, // -174 + 0x6781_5961_0903_F797L, 0x253E_59F9_1780_FD2FL, // -173 + 0x52CD_E11A_6D9C_C612L, 0x50FE_AE60_DF9A_6426L, // -172 + 0x423E_4DAE_BE17_04DBL, 0x5A65_584D_7FAE_B685L, // -171 + 0x69FD_4917_968B_3AF9L, 0x10A2_26E2_65E4_573BL, // -170 + 0x54CA_A0DF_ABA2_9594L, 0x0D4E_8581_EB1D_1295L, // -169 + 0x43D5_4D7F_BC82_1143L, 0x243E_D134_BC17_4211L, // -168 + 0x6C88_7BFF_9403_4ED2L, 0x06CA_E854_6025_3682L, // -167 + 0x56D3_9666_1002_A574L, 0x6BD5_86A9_E684_2B9BL, // -166 + 0x4576_11EB_4002_1DF7L, 0x0977_9EEE_5203_5616L, // -165 + 0x6F23_4FDE_CCD0_2FF1L, 0x5BF2_97E3_B66B_BCEFL, // -164 + 0x58E9_0CB2_3D73_598EL, 0x165B_ACB6_2B89_63F3L, // -163 + 0x4720_D6F4_FDF5_E13EL, 0x4516_23C4_EFA1_1CC2L, // -162 + 0x71CE_24BB_2FEF_CECAL, 0x3B56_9FA1_7F68_2E03L, // -161 + 0x5B0B_5095_BFF3_0BD5L, 0x15DE_E61A_CC53_5803L, // -160 + 0x48D5_DA11_665C_0977L, 0x2B18_B815_7042_ACCFL, // -159 + 0x7489_5CE8_A3C6_758BL, 0x5E8D_F355_806A_AE18L, // -158 + 0x5D3A_B0BA_1C9E_C46FL, 0x653E_5C44_66BB_BE7AL, // -157 + 0x4A95_5A2E_7D4B_D059L, 0x3765_169D_1EFC_9861L, // -156 + 0x7755_5D17_2EDF_B3C2L, 0x256E_8A94_FE60_F3CFL, // -155 + 0x5F77_7DAC_257F_C301L, 0x6ABE_D543_FEB3_F63FL, // -154 + 0x4C5F_97BC_EACC_9C01L, 0x3BCB_DDCF_FEF6_5E99L, // -153 + 0x7A32_8C61_77AD_C668L, 0x5FAC_9619_97F0_975BL, // -152 + 0x61C2_09E7_92F1_6B86L, 0x7FBD_44E1_465A_12AFL, // -151 + 0x4E34_D4B9_425A_BC6BL, 0x7FCA_9D81_0514_DBBFL, // -150 + 0x7D21_545B_9D5D_FA46L, 0x32DD_C8CE_6E87_C5FFL, // -149 + 0x641A_A9E2_E44B_2E9EL, 0x5BE4_A0A5_2539_6B32L, // -148 + 0x5015_54B5_836F_587EL, 0x7CB6_E6EA_842D_EF5CL, // -147 + 0x4011_1091_35F2_AD32L, 0x3092_5255_368B_25E3L, // -146 + 0x6681_B41B_8984_4850L, 0x4DB6_EA21_F0DE_A304L, // -145 + 0x5201_5CE2_D469_D373L, 0x57C5_881B_2718_826AL, // -144 + 0x419A_B0B5_76BB_0F8FL, 0x5FD1_39AF_527A_01EFL, // -143 + 0x68F7_8122_5791_B27FL, 0x4C81_F5E5_50C3_364AL, // -142 + 0x53F9_341B_7941_5B99L, 0x239B_2B1D_DA35_C508L, // -141 + 0x432D_C349_2DCD_E2E1L, 0x02E2_88E4_AE91_6A6DL, // -140 + 0x6B7C_6BA8_4949_6B01L, 0x516A_74A1_174F_10AEL, // -139 + 0x55FD_22ED_076D_EF34L, 0x4121_F6E7_45D8_DA25L, // -138 + 0x44CA_8257_3924_BF5DL, 0x1A81_9252_9E47_14EBL, // -137 + 0x6E10_D08B_8EA1_322EL, 0x5D9C_1D50_FD3E_87DDL, // -136 + 0x580D_73A2_D880_F4F2L, 0x17B0_1773_FDCB_9FE4L, // -135 + 0x4671_294F_139A_5D8EL, 0x4626_7929_97D6_1984L, // -134 + 0x70B5_0EE4_EC2A_2F4AL, 0x3D0A_5B75_BFBC_F59FL, // -133 + 0x5A2A_7250_BCEE_8C3BL, 0x4A6E_AF91_6630_C47FL, // -132 + 0x4821_F50D_63F2_09C9L, 0x21F2_260D_EB5A_36CCL, // -131 + 0x7369_8815_6CB6_760EL, 0x6983_7016_455D_247AL, // -130 + 0x5C54_6CDD_F091_F80BL, 0x6E02_C011_D117_5062L, // -129 + 0x49DD_23E4_C074_C66FL, 0x719B_CCDB_0DAC_404EL, // -128 + 0x762E_9FD4_6721_3D7FL, 0x68F9_47C4_E2AD_33B0L, // -127 + 0x5E8B_B310_5280_FDFFL, 0x6D94_396A_4EF0_F627L, // -126 + 0x4BA2_F5A6_A867_3199L, 0x3E10_2DEE_A58D_91B9L, // -125 + 0x7904_BC3D_DA3E_B5C2L, 0x3019_E317_6F48_E927L, // -124 + 0x60D0_9697_E1CB_C49BL, 0x4014_B5AC_5907_20ECL, // -123 + 0x4D73_ABAC_B4A3_03AFL, 0x4CDD_5E23_7A6C_1A57L, // -122 + 0x7BEC_45E1_2104_D2B2L, 0x47C8_969F_2A46_908AL, // -121 + 0x6323_6B1A_80D0_A88EL, 0x6CA0_787F_5505_406FL, // -120 + 0x4F4F_88E2_00A6_ED3FL, 0x0A19_F9FF_7737_66BFL, // -119 + 0x7EE5_A7D0_010B_1531L, 0x5CF6_5CCB_F1F2_3DFEL, // -118 + 0x6584_8640_00D5_AA8EL, 0x172B_7D6F_F4C1_CB32L, // -117 + 0x5136_D1CC_CD77_BBA4L, 0x78EF_978C_C3CE_3C28L, // -116 + 0x40F8_A7D7_0AC6_2FB7L, 0x13F2_DFA3_CFD8_3020L, // -115 + 0x67F4_3FBE_77A3_7F8BL, 0x3984_9906_1959_E699L, // -114 + 0x5329_CC98_5FB5_FFA2L, 0x6136_E0D1_ADE1_8548L, // -113 + 0x4287_D6E0_4C91_994FL, 0x00F8_B3DA_F181_376DL, // -112 + 0x6A72_F166_E0E8_F54BL, 0x1B27_862B_1C01_F247L, // -111 + 0x5528_C11F_1A53_F76FL, 0x2F52_D1BC_1667_F506L, // -110 + 0x4420_9A7F_4843_2C59L, 0x0C42_4163_451F_F738L, // -109 + 0x6D00_F732_0D38_46F4L, 0x7A03_9BD2_0833_2526L, // -108 + 0x5733_F8F4_D760_38C3L, 0x7B36_1641_A028_EA85L, // -107 + 0x45C3_2D90_AC4C_FA36L, 0x2F5E_7834_8020_BB9EL, // -106 + 0x6F9E_AF4D_E07B_29F0L, 0x4BCA_59ED_99CD_F8FCL, // -105 + 0x594B_BF71_8062_87F3L, 0x563B_7B24_7B0B_2D96L, // -104 + 0x476F_CC5A_CD1B_9FF6L, 0x11C9_2F50_626F_57ACL, // -103 + 0x724C_7A2A_E1C5_CCBDL, 0x02DB_7EE7_03E5_5912L, // -102 + 0x5B70_61BB_E7D1_7097L, 0x1BE2_CBEC_031D_E0DCL, // -101 + 0x4926_B496_530D_F3ACL, 0x164F_0989_9C17_E716L, // -100 + 0x750A_BA8A_1E7C_B913L, 0x3D4B_4275_C68C_A4F0L, // -99 + 0x5DA2_2ED4_E530_940FL, 0x4AA2_9B91_6BA3_B726L, // -98 + 0x4AE8_2577_1DC0_7672L, 0x6EE8_7C74_561C_9285L, // -97 + 0x77D9_D58B_62CD_8A51L, 0x3173_FA53_BCFA_8408L, // -96 + 0x5FE1_77A2_B571_3B74L, 0x278F_FB76_30C8_69A0L, // -95 + 0x4CB4_5FB5_5DF4_2F90L, 0x1FA6_62C4_F3D3_87B3L, // -94 + 0x7ABA_32BB_C986_B280L, 0x32A3_D13B_1FB8_D91FL, // -93 + 0x622E_8EFC_A138_8ECDL, 0x0EE9_742F_4C93_E0E6L, // -92 + 0x4E8B_A596_E760_723DL, 0x58BA_C359_0A0F_E71EL, // -91 + 0x7DAC_3C24_A567_1D2FL, 0x412A_D228_1019_71C9L, // -90 + 0x6489_C9B6_EAB8_E426L, 0x00EF_0E86_7347_8E3BL, // -89 + 0x506E_3AF8_BBC7_1CEBL, 0x1A58_D86B_8F6C_71C9L, // -88 + 0x4058_2F2D_6305_B0BCL, 0x1513_E056_0C56_C16EL, // -87 + 0x66F3_7EAF_04D5_E793L, 0x3B53_0089_AD57_9BE2L, // -86 + 0x525C_6558_D0AB_1FA9L, 0x15DC_006E_2446_164FL, // -85 + 0x41E3_8447_0D55_B2EDL, 0x5E49_99F1_B69E_783FL, // -84 + 0x696C_06D8_1555_EB15L, 0x7D42_8FE9_2430_C065L, // -83 + 0x5456_6BE0_1111_88DEL, 0x3102_0CBA_835A_3384L, // -82 + 0x4378_564C_DA74_6D7EL, 0x5A68_0A2E_CF7B_5C69L, // -81 + 0x6BF3_BD47_C3ED_7BFDL, 0x770C_DD17_B25E_FA42L, // -80 + 0x565C_976C_9CBD_FCCBL, 0x1270_B0DF_C1E5_9502L, // -79 + 0x4516_DF8A_16FE_63D5L, 0x5B8D_5A4C_9B1E_10CEL, // -78 + 0x6E8A_FF43_57FD_6C89L, 0x127B_C3AD_C4FC_E7B0L, // -77 + 0x586F_329C_4664_56D4L, 0x0EC9_6957_D0CA_52F3L, // -76 + 0x46BF_5BB0_3850_4576L, 0x3F07_8779_73D5_0F29L, // -75 + 0x7132_2C4D_26E6_D58AL, 0x31A5_A58F_1FBB_4B75L, // -74 + 0x5A8E_89D7_5252_446EL, 0x5AEA_EAD8_E62F_6F91L, // -73 + 0x4872_07DF_750E_9D25L, 0x2F22_557A_51BF_8C74L, // -72 + 0x73E9_A632_54E4_2EA2L, 0x1836_EF2A_1C65_AD86L, // -71 + 0x5CBA_EB5B_771C_F21BL, 0x2CF8_BF54_E384_8AD2L, // -70 + 0x4A2F_22AF_927D_8E7CL, 0x23FA_32AA_4F9D_3BDBL, // -69 + 0x76B1_D118_EA62_7D93L, 0x5329_EAAA_18FB_92F8L, // -68 + 0x5EF4_A747_21E8_6476L, 0x0F54_BBBB_472F_A8C6L, // -67 + 0x4BF6_EC38_E7ED_1D2BL, 0x25DD_62FC_38F2_ED6CL, // -66 + 0x798B_138E_3FE1_C845L, 0x22FB_D193_8E51_7BDFL, // -65 + 0x613C_0FA4_FFE7_D36AL, 0x4F2F_DADC_71DA_C97FL, // -64 + 0x4DC9_A61D_9986_42BBL, 0x58F3_157D_27E2_3ACCL, // -63 + 0x7C75_D695_C270_6AC5L, 0x74B8_2261_D969_F7ADL, // -62 + 0x6391_7877_CEC0_556BL, 0x1093_4EB4_ADEE_5FBEL, // -61 + 0x4FA7_9393_0BCD_1122L, 0x4075_D890_8B25_1965L, // -60 + 0x7F72_85B8_12E1_B504L, 0x00BC_8DB4_11D4_F56EL, // -59 + 0x65F5_37C6_7581_5D9CL, 0x66FD_3E29_A7DD_9125L, // -58 + 0x5190_F96B_9134_4AE3L, 0x6BFD_CB54_864A_DA84L, // -57 + 0x4140_C789_40F6_A24FL, 0x6FFE_3C43_9EA2_486AL, // -56 + 0x6867_A5A8_67F1_03B2L, 0x7FFD_2D38_FDD0_73DCL, // -55 + 0x5386_1E20_5327_3628L, 0x6664_242D_97D9_F64AL, // -54 + 0x42D1_B1B3_75B8_F820L, 0x51E9_B68A_DFE1_91D5L, // -53 + 0x6AE9_1C52_55F4_C034L, 0x1CA9_2411_6635_B621L, // -52 + 0x5587_49DB_77F7_0029L, 0x63BA_8341_1E91_5E81L, // -51 + 0x446C_3B15_F992_6687L, 0x6962_029A_7EDA_B201L, // -50 + 0x6D79_F823_28EA_3DA6L, 0x0F03_375D_97C4_5001L, // -49 + 0x5794_C682_8721_CAEBL, 0x259C_2C4A_DFD0_4001L, // -48 + 0x4610_9ECE_D281_6F22L, 0x5149_BD08_B30D_0001L, // -47 + 0x701A_97B1_50CF_1837L, 0x3542_C80D_EB48_0001L, // -46 + 0x59AE_DFC1_0D72_79C5L, 0x7768_A00B_22A0_0001L, // -45 + 0x47BF_1967_3DF5_2E37L, 0x7920_8008_E880_0001L, // -44 + 0x72CB_5BD8_6321_E38CL, 0x5B67_3341_7400_0001L, // -43 + 0x5BD5_E313_8281_82D6L, 0x7C52_8F67_9000_0001L, // -42 + 0x4977_E8DC_6867_9BDFL, 0x16A8_72B9_4000_0001L, // -41 + 0x758C_A7C7_0D72_92FEL, 0x5773_EAC2_0000_0001L, // -40 + 0x5E0A_1FD2_7128_7598L, 0x45F6_5568_0000_0001L, // -39 + 0x4B3B_4CA8_5A86_C47AL, 0x04C5_1120_0000_0001L, // -38 + 0x785E_E10D_5DA4_6D90L, 0x07A1_B500_0000_0001L, // -37 + 0x604B_E73D_E483_8AD9L, 0x52E7_C400_0000_0001L, // -36 + 0x4D09_85CB_1D36_08AEL, 0x0F1F_D000_0000_0001L, // -35 + 0x7B42_6FAB_61F0_0DE3L, 0x31CC_8000_0000_0001L, // -34 + 0x629B_8C89_1B26_7182L, 0x5B0A_0000_0000_0001L, // -33 + 0x4EE2_D6D4_15B8_5ACEL, 0x7C08_0000_0000_0001L, // -32 + 0x7E37_BE20_22C0_914BL, 0x1340_0000_0000_0001L, // -31 + 0x64F9_64E6_8233_A76FL, 0x2900_0000_0000_0001L, // -30 + 0x50C7_83EB_9B5C_85F2L, 0x5400_0000_0000_0001L, // -29 + 0x409F_9CBC_7C4A_04C2L, 0x1000_0000_0000_0001L, // -28 + 0x6765_C793_FA10_079DL, 0x0000_0000_0000_0001L, // -27 + 0x52B7_D2DC_C80C_D2E4L, 0x0000_0000_0000_0001L, // -26 + 0x422C_A8B0_A00A_4250L, 0x0000_0000_0000_0001L, // -25 + 0x69E1_0DE7_6676_D080L, 0x0000_0000_0000_0001L, // -24 + 0x54B4_0B1F_852B_DA00L, 0x0000_0000_0000_0001L, // -23 + 0x43C3_3C19_3756_4800L, 0x0000_0000_0000_0001L, // -22 + 0x6C6B_935B_8BBD_4000L, 0x0000_0000_0000_0001L, // -21 + 0x56BC_75E2_D631_0000L, 0x0000_0000_0000_0001L, // -20 + 0x4563_9182_44F4_0000L, 0x0000_0000_0000_0001L, // -19 + 0x6F05_B59D_3B20_0000L, 0x0000_0000_0000_0001L, // -18 + 0x58D1_5E17_6280_0000L, 0x0000_0000_0000_0001L, // -17 + 0x470D_E4DF_8200_0000L, 0x0000_0000_0000_0001L, // -16 + 0x71AF_D498_D000_0000L, 0x0000_0000_0000_0001L, // -15 + 0x5AF3_107A_4000_0000L, 0x0000_0000_0000_0001L, // -14 + 0x48C2_7395_0000_0000L, 0x0000_0000_0000_0001L, // -13 + 0x746A_5288_0000_0000L, 0x0000_0000_0000_0001L, // -12 + 0x5D21_DBA0_0000_0000L, 0x0000_0000_0000_0001L, // -11 + 0x4A81_7C80_0000_0000L, 0x0000_0000_0000_0001L, // -10 + 0x7735_9400_0000_0000L, 0x0000_0000_0000_0001L, // -9 + 0x5F5E_1000_0000_0000L, 0x0000_0000_0000_0001L, // -8 + 0x4C4B_4000_0000_0000L, 0x0000_0000_0000_0001L, // -7 + 0x7A12_0000_0000_0000L, 0x0000_0000_0000_0001L, // -6 + 0x61A8_0000_0000_0000L, 0x0000_0000_0000_0001L, // -5 + 0x4E20_0000_0000_0000L, 0x0000_0000_0000_0001L, // -4 + 0x7D00_0000_0000_0000L, 0x0000_0000_0000_0001L, // -3 + 0x6400_0000_0000_0000L, 0x0000_0000_0000_0001L, // -2 + 0x5000_0000_0000_0000L, 0x0000_0000_0000_0001L, // -1 + 0x4000_0000_0000_0000L, 0x0000_0000_0000_0001L, // 0 + 0x6666_6666_6666_6666L, 0x3333_3333_3333_3334L, // 1 + 0x51EB_851E_B851_EB85L, 0x0F5C_28F5_C28F_5C29L, // 2 + 0x4189_374B_C6A7_EF9DL, 0x5916_872B_020C_49BBL, // 3 + 0x68DB_8BAC_710C_B295L, 0x74F0_D844_D013_A92BL, // 4 + 0x53E2_D623_8DA3_C211L, 0x43F3_E037_0CDC_8755L, // 5 + 0x431B_DE82_D7B6_34DAL, 0x698F_E692_70B0_6C44L, // 6 + 0x6B5F_CA6A_F2BD_215EL, 0x0F4C_A41D_811A_46D4L, // 7 + 0x55E6_3B88_C230_E77EL, 0x3F70_834A_CDAE_9F10L, // 8 + 0x44B8_2FA0_9B5A_52CBL, 0x4C5A_02A2_3E25_4C0DL, // 9 + 0x6DF3_7F67_5EF6_EADFL, 0x2D5C_D103_96A2_1347L, // 10 + 0x57F5_FF85_E592_557FL, 0x3DE3_DA69_454E_75D3L, // 11 + 0x465E_6604_B7A8_4465L, 0x7E4F_E1ED_D10B_9175L, // 12 + 0x7097_09A1_25DA_0709L, 0x4A19_697C_81AC_1BEFL, // 13 + 0x5A12_6E1A_84AE_6C07L, 0x54E1_2130_67BC_E326L, // 14 + 0x480E_BE7B_9D58_566CL, 0x43E7_4DC0_52FD_8285L, // 15 + 0x734A_CA5F_6226_F0ADL, 0x530B_AF9A_1E62_6A6DL, // 16 + 0x5C3B_D519_1B52_5A24L, 0x426F_BFAE_7EB5_21F1L, // 17 + 0x49C9_7747_490E_AE83L, 0x4EBF_CC8B_9890_E7F4L, // 18 + 0x760F_253E_DB4A_B0D2L, 0x4ACC_7A78_F41B_0CBAL, // 19 + 0x5E72_8432_4908_8D75L, 0x223D_2EC7_29AF_3D62L, // 20 + 0x4B8E_D028_3A6D_3DF7L, 0x34FD_BF05_BAF2_9781L, // 21 + 0x78E4_8040_5D7B_9658L, 0x54C9_31A2_C4B7_58CFL, // 22 + 0x60B6_CD00_4AC9_4513L, 0x5D6D_C14F_03C5_E0A5L, // 23 + 0x4D5F_0A66_A23A_9DA9L, 0x3124_9AA5_9C9E_4D51L, // 24 + 0x7BCB_43D7_69F7_62A8L, 0x4EA0_F76F_60FD_4882L, // 25 + 0x6309_0312_BB2C_4EEDL, 0x254D_92BF_80CA_A068L, // 26 + 0x4F3A_68DB_C8F0_3F24L, 0x1DD7_A899_33D5_4D20L, // 27 + 0x7EC3_DAF9_4180_6506L, 0x62F2_A75B_8622_1500L, // 28 + 0x6569_7BFA_9ACD_1D9FL, 0x025B_B916_04E8_10CDL, // 29 + 0x5121_2FFB_AF0A_7E18L, 0x6849_60DE_6A53_40A4L, // 30 + 0x40E7_5996_25A1_FE7AL, 0x203A_B3E5_21DC_33B6L, // 31 + 0x67D8_8F56_A29C_CA5DL, 0x19F7_863B_6960_52BDL, // 32 + 0x5313_A5DE_E87D_6EB0L, 0x7B2C_6B62_BAB3_7564L, // 33 + 0x4276_1E4B_ED31_255AL, 0x2F56_BC4E_FBC2_C450L, // 34 + 0x6A56_96DF_E1E8_3BC3L, 0x6557_93B1_92D1_3A1AL, // 35 + 0x5512_124C_B4B9_C969L, 0x3779_42F4_7574_2E7BL, // 36 + 0x440E_750A_2A2E_3ABAL, 0x5F94_3590_5DF6_8B96L, // 37 + 0x6CE3_EE76_A9E3_912AL, 0x65B9_EF4D_6324_1289L, // 38 + 0x571C_BEC5_54B6_0DBBL, 0x6AFB_25D7_8283_4207L, // 39 + 0x45B0_989D_DD5E_7163L, 0x08C8_EB12_CECF_6806L, // 40 + 0x6F80_F42F_C897_1BD1L, 0x5ADB_11B7_B14B_D9A3L, // 41 + 0x5933_F68C_A078_E30EL, 0x157C_0E2C_8DD6_47B5L, // 42 + 0x475C_C53D_4D2D_8271L, 0x5DFC_D823_A4AB_6C91L, // 43 + 0x722E_0862_1515_9D82L, 0x632E_269F_6DDF_141BL, // 44 + 0x5B58_06B4_DDAA_E468L, 0x4F58_1EE5_F17F_4349L, // 45 + 0x4913_3890_B155_8386L, 0x72AC_E584_C132_9C3BL, // 46 + 0x74EB_8DB4_4EEF_38D7L, 0x6AAE_3C07_9B84_2D2AL, // 47 + 0x5D89_3E29_D8BF_60ACL, 0x5558_3006_1603_5755L, // 48 + 0x4AD4_31BB_13CC_4D56L, 0x7779_C004_DE69_12ABL, // 49 + 0x77B9_E92B_52E0_7BBEL, 0x258F_99A1_63DB_5111L, // 50 + 0x5FC7_EDBC_424D_2FCBL, 0x37A6_1481_1CAF_740DL, // 51 + 0x4C9F_F163_683D_BFD5L, 0x7951_AA00_E3BF_900BL, // 52 + 0x7A99_8238_A6C9_32EFL, 0x754F_7667_D2CC_19ABL, // 53 + 0x6214_682D_523A_8F26L, 0x2AA5_F853_0F09_AE22L, // 54 + 0x4E76_B9BD_DB62_0C1EL, 0x5551_9375_A5A1_581BL, // 55 + 0x7D8A_C2C9_5F03_4697L, 0x3BB5_B8BC_3C35_59C5L, // 56 + 0x646F_023A_B269_0545L, 0x7C91_6096_9691_149EL, // 57 + 0x5058_CE95_5B87_376BL, 0x16DA_B3AB_ABA7_43B2L, // 58 + 0x4047_0BAA_AF9F_5F88L, 0x78AE_F622_EFB9_02F5L, // 59 + 0x66D8_12AA_B298_98DBL, 0x0DE4_BD04_B2C1_9E54L, // 60 + 0x5246_7555_5BAD_4715L, 0x57EA_30D0_8F01_4B76L, // 61 + 0x41D1_F777_7C8A_9F44L, 0x4654_F3DA_0C01_092CL, // 62 + 0x694F_F258_C744_3207L, 0x23BB_1FC3_4668_0EACL, // 63 + 0x543F_F513_D29C_F4D2L, 0x4FC8_E635_D1EC_D88AL, // 64 + 0x4366_5DA9_754A_5D75L, 0x263A_51C4_A7F0_AD3BL, // 65 + 0x6BD6_FC42_5543_C8BBL, 0x56C3_B607_731A_AEC4L, // 66 + 0x5645_969B_7769_6D62L, 0x789C_919F_8F48_8BD0L, // 67 + 0x4504_787C_5F87_8AB5L, 0x46E3_A7B2_D906_D640L, // 68 + 0x6E6D_8D93_CC0C_1122L, 0x3E39_0C51_5B3E_239AL, // 69 + 0x5857_A476_3CD6_741BL, 0x4B60_D6A7_7C31_B615L, // 70 + 0x46AC_8391_CA45_29AFL, 0x55E7_121F_968E_2B44L, // 71 + 0x7114_05B6_106E_A919L, 0x0971_B698_F0E3_786DL, // 72 + 0x5A76_6AF8_0D25_5414L, 0x078E_2BAD_8D82_C6BDL, // 73 + 0x485E_BBF9_A41D_DCDCL, 0x6C71_BC8A_D79B_D231L, // 74 + 0x73CA_C65C_39C9_6161L, 0x2D82_C744_8C2C_8382L, // 75 + 0x5CA2_3849_C7D4_4DE7L, 0x3E02_3903_A356_CF9BL, // 76 + 0x4A1B_603B_0643_7185L, 0x7E68_2D9C_82AB_D949L, // 77 + 0x7692_3391_A39F_1C09L, 0x4A40_48FA_6AAC_8EDBL, // 78 + 0x5EDB_5C74_82E5_B007L, 0x5500_3A61_EEF0_7249L, // 79 + 0x4BE2_B05D_3584_8CD2L, 0x7733_61E7_F259_F507L, // 80 + 0x796A_B3C8_55A0_E151L, 0x3EB8_9CA6_508F_EE71L, // 81 + 0x6122_296D_114D_810DL, 0x7EFA_16EB_73A6_585BL, // 82 + 0x4DB4_EDF0_DAA4_673EL, 0x3261_ABEF_8FB8_46AFL, // 83 + 0x7C54_AFE7_C43A_3ECAL, 0x1D69_1318_E5F3_A44BL, // 84 + 0x6376_F31F_D02E_98A1L, 0x6454_0F47_1E5C_836FL, // 85 + 0x4F92_5C19_7358_7A1BL, 0x0376_729F_4B7D_35F3L, // 86 + 0x7F50_935B_EBC0_C35EL, 0x38BD_8432_1261_EFEBL, // 87 + 0x65DA_0F7C_BC9A_35E5L, 0x13CA_D028_0EB4_BFEFL, // 88 + 0x517B_3F96_FD48_2B1DL, 0x5CA2_4020_0BC3_CCBFL, // 89 + 0x412F_6612_6439_BC17L, 0x63B5_0019_A303_0A33L, // 90 + 0x684B_D683_D38F_9359L, 0x1F88_0029_04D1_A9EAL, // 91 + 0x536F_DECF_DC72_DC47L, 0x32D3_3354_03DA_EE55L, // 92 + 0x42BF_E573_16C2_49D2L, 0x5BDC_2910_0315_8B77L, // 93 + 0x6ACC_A251_BE03_A951L, 0x12F9_DB4C_D1BC_1258L, // 94 + 0x5570_81DA_FE69_5440L, 0x7594_AF70_A7C9_A847L, // 95 + 0x445A_017B_FEBA_A9CDL, 0x4476_F2C0_863A_ED06L, // 96 + 0x6D5C_CF2C_CAC4_42E2L, 0x3A57_EACD_A391_7B3CL, // 97 + 0x577D_728A_3BD0_3581L, 0x7B79_88A4_82DA_C8FDL, // 98 + 0x45FD_F53B_630C_F79BL, 0x15FA_D3B6_CF15_6D97L, // 99 + 0x6FFC_BB92_3814_BF5EL, 0x565E_1F8A_E4EF_15BEL, // 100 + 0x5996_FC74_F9AA_32B2L, 0x11E4_E608_B725_AAFFL, // 101 + 0x47AB_FD2A_6154_F55BL, 0x27EA_51A0_9284_88CCL, // 102 + 0x72AC_C843_CEEE_555EL, 0x7310_829A_8407_4146L, // 103 + 0x5BBD_6D03_0BF1_DDE5L, 0x4273_9BAE_D005_CDD2L, // 104 + 0x4964_5735_A327_E4B7L, 0x4EC2_E2F2_4004_A4A8L, // 105 + 0x756D_5855_D1D9_6DF2L, 0x4AD1_6B1D_333A_A10CL, // 106 + 0x5DF1_1377_DB14_57F5L, 0x2241_227D_C295_4DA3L, // 107 + 0x4B27_42C6_48DD_132AL, 0x4E9A_81FE_3544_3E1CL, // 108 + 0x783E_D13D_4161_B844L, 0x175D_9CC9_EED3_9694L, // 109 + 0x6032_40FD_CDE7_C69CL, 0x7917_B0A1_8BDC_7876L, // 110 + 0x4CF5_00CB_0B1F_D217L, 0x1412_F3B4_6FE3_9392L, // 111 + 0x7B21_9ADE_7832_E9BEL, 0x5351_85ED_7FD2_85B6L, // 112 + 0x6281_48B1_F9C2_5498L, 0x42A7_9E57_9975_37C5L, // 113 + 0x4ECD_D3C1_949B_76E0L, 0x3552_E512_E12A_9304L, // 114 + 0x7E16_1F9C_20F8_BE33L, 0x6EEB_081E_3510_EB39L, // 115 + 0x64DE_7FB0_1A60_9829L, 0x3F22_6CE4_F740_BC2EL, // 116 + 0x50B1_FFC0_151A_1354L, 0x3281_F0B7_2C33_C9BEL, // 117 + 0x408E_6633_4414_DC43L, 0x4201_8D5F_568F_D498L, // 118 + 0x674A_3D1E_D354_939FL, 0x1CCF_4898_8A7F_BA8DL, // 119 + 0x52A1_CA7F_0F76_DC7FL, 0x30A5_D3AD_3B99_620BL, // 120 + 0x421B_0865_A5F8_B065L, 0x73B7_DC8A_9614_4E6FL, // 121 + 0x69C4_DA3C_3CC1_1A3CL, 0x52BF_C744_2353_B0B1L, // 122 + 0x549D_7B63_63CD_AE96L, 0x7566_3903_4F76_26F4L, // 123 + 0x43B1_2F82_B63E_2545L, 0x4451_C735_D92B_525DL, // 124 + 0x6C4E_B26A_BD30_3BA2L, 0x3A1C_71EF_C1DE_EA2EL, // 125 + 0x56A5_5B88_9759_C94EL, 0x61B0_5B26_34B2_54F2L, // 126 + 0x4551_1606_DF7B_0772L, 0x1AF3_7C1E_908E_AA5BL, // 127 + 0x6EE8_233E_325E_7250L, 0x2B1F_2CFD_B417_76F8L, // 128 + 0x58B9_B5CB_5B7E_C1D9L, 0x6F4C_23FE_29AC_5F2DL, // 129 + 0x46FA_F7D5_E2CB_CE47L, 0x72A3_4FFE_87BD_18F1L, // 130 + 0x7191_8C89_6ADF_B073L, 0x0438_7FFD_A5FB_5B1BL, // 131 + 0x5ADA_D6D4_557F_C05CL, 0x0360_6664_84C9_15AFL, // 132 + 0x48AF_1243_7799_66B0L, 0x02B3_851D_3707_448CL, // 133 + 0x744B_506B_F28F_0AB3L, 0x1DEC_082E_BE72_0746L, // 134 + 0x5D09_0D23_2872_6EF5L, 0x64BC_D358_985B_3905L, // 135 + 0x4A6D_A41C_205B_8BF7L, 0x6A30_A913_AD15_C738L, // 136 + 0x7715_D360_33C5_ACBFL, 0x5D1A_A81F_7B56_0B8CL, // 137 + 0x5F44_A919_C304_8A32L, 0x7DAE_ECE5_FC44_D609L, // 138 + 0x4C36_EDAE_359D_3B5BL, 0x7E25_8A51_969D_7808L, // 139 + 0x79F1_7C49_EF61_F893L, 0x16A2_76E8_F0FB_F33FL, // 140 + 0x618D_FD07_F2B4_C6DCL, 0x121B_9253_F3FC_C299L, // 141 + 0x4E0B_30D3_2890_9F16L, 0x41AF_A843_2997_0214L, // 142 + 0x7CDE_B485_0DB4_31BDL, 0x4F7F_739E_A8F1_9CEDL, // 143 + 0x63E5_5D37_3E29_C164L, 0x3F99_294B_BA5A_E3F1L, // 144 + 0x4FEA_B0F8_FE87_CDE9L, 0x7FAD_BAA2_FB7B_E98DL, // 145 + 0x7FDD_E7F4_CA72_E30FL, 0x7F7C_5DD1_925F_DC15L, // 146 + 0x664B_1FF7_085B_E8D9L, 0x4C63_7E41_41E6_49ABL, // 147 + 0x51D5_B32C_06AF_ED7AL, 0x704F_9834_34B8_3AEFL, // 148 + 0x4177_C289_9EF3_2462L, 0x26A6_135C_F6F9_C8BFL, // 149 + 0x68BF_9DA8_FE51_D3D0L, 0x3DD6_8561_8B29_4132L, // 150 + 0x53CC_7E20_CB74_A973L, 0x4B12_044E_08ED_CDC2L, // 151 + 0x4309_FE80_A2C3_BAC2L, 0x6F41_9D0B_3A57_D7CEL, // 152 + 0x6B43_30CD_D139_2AD1L, 0x3202_94DE_C3BF_BFB0L, // 153 + 0x55CF_5A3E_40FA_88A7L, 0x419B_AA4B_CFCC_995AL, // 154 + 0x44A5_E1CB_672E_D3B9L, 0x1AE2_EEA3_0CA3_ADE1L, // 155 + 0x6DD6_3612_3EB1_52C1L, 0x77D1_7DD1_ADD2_AFCFL, // 156 + 0x57DE_91A8_3227_7567L, 0x7974_64A7_BE42_263FL, // 157 + 0x464B_A7B9_C1B9_2AB9L, 0x4790_5086_31CE_84FFL, // 158 + 0x7079_0C5C_6928_445CL, 0x0C1A_1A70_4FB0_D4CCL, // 159 + 0x59FA_7049_EDB9_D049L, 0x567B_4859_D95A_43D6L, // 160 + 0x47FB_8D07_F161_736EL, 0x11FC_39E1_7AAE_9CABL, // 161 + 0x732C_14D9_8235_857DL, 0x032D_2968_C44A_9445L, // 162 + 0x5C23_43E1_34F7_9DFDL, 0x4F57_5453_D03B_A9D1L, // 163 + 0x49B5_CFE7_5D92_E4CAL, 0x72AC_4376_402F_BB0EL, // 164 + 0x75EF_B30B_C8EB_07ABL, 0x0446_D256_CD19_2B49L, // 165 + 0x5E59_5C09_6D88_D2EFL, 0x1D05_7512_3DAD_BC3AL, // 166 + 0x4B7A_B007_8AD3_DBF2L, 0x4A6A_C40E_97BE_302FL, // 167 + 0x78C4_4CD8_DE1F_C650L, 0x7711_39B0_F2C9_E6B1L, // 168 + 0x609D_0A47_1819_6B73L, 0x78DA_948D_8F07_EBC1L, // 169 + 0x4D4A_6E9F_467A_BC5CL, 0x60AE_DD3E_0C06_5634L, // 170 + 0x7BAA_4A98_70C4_6094L, 0x344A_FB96_79A3_BD20L, // 171 + 0x62EE_A213_8D69_E6DDL, 0x103B_FC78_614F_CA80L, // 172 + 0x4F25_4E76_0ABB_1F17L, 0x2696_6393_810C_A200L, // 173 + 0x7EA2_1723_445E_9825L, 0x2423_D285_9B47_6999L, // 174 + 0x654E_78E9_037E_E01DL, 0x69B6_4204_7C39_2148L, // 175 + 0x510B_93ED_9C65_8017L, 0x6E2B_6803_9694_1AA0L, // 176 + 0x40D6_0FF1_49EA_CCDFL, 0x71BC_5336_1210_154DL, // 177 + 0x67BC_E64E_DCAA_E166L, 0x1C60_8523_5019_BBAEL, // 178 + 0x52FD_850B_E3BB_E784L, 0x7D1A_041C_4014_9625L, // 179 + 0x4264_6A6F_E963_1F9DL, 0x4A7B_367D_0010_781DL, // 180 + 0x6A3A_43E6_4238_3295L, 0x5D91_F0C8_001A_59C8L, // 181 + 0x54FB_6985_01C6_8EDEL, 0x17A7_F3D3_3348_47D4L, // 182 + 0x43FC_546A_67D2_0BE4L, 0x7953_2975_C2A0_3976L, // 183 + 0x6CC6_ED77_0C83_463BL, 0x0EEB_7589_3766_C256L, // 184 + 0x5705_8AC5_A39C_382FL, 0x2589_2AD4_2C52_3512L, // 185 + 0x459E_089E_1C7C_F9BFL, 0x37A0_EF10_2374_F742L, // 186 + 0x6F63_40FC_FA61_8F98L, 0x5901_7E80_38BB_2536L, // 187 + 0x591C_33FD_951A_D946L, 0x7A67_9866_93C8_EA91L, // 188 + 0x4749_C331_4415_7A9FL, 0x151F_AD1E_DCA0_BBA8L, // 189 + 0x720F_9EB5_39BB_F765L, 0x0832_AE97_C767_92A5L, // 190 + 0x5B3F_B22A_9496_5F84L, 0x068E_F213_05EC_7551L, // 191 + 0x48FF_C1BB_AA11_E603L, 0x1ED8_C1A8_D189_F774L, // 192 + 0x74CC_692C_434F_D66BL, 0x4AF4_690E_1C0F_F253L, // 193 + 0x5D70_5423_690C_AB89L, 0x225D_20D8_1673_2843L, // 194 + 0x4AC0_434F_873D_5607L, 0x3517_4D79_AB8F_5369L, // 195 + 0x779A_054C_0B95_5672L, 0x21BE_E25C_45B2_1F0EL, // 196 + 0x5FAE_6AA3_3C77_785BL, 0x3498_B516_9E28_18D8L, // 197 + 0x4C8B_8882_96C5_F9E2L, 0x5D46_F745_4B53_4713L, // 198 + 0x7A78_DA6A_8AD6_5C9DL, 0x7BA4_BED5_4552_0B52L, // 199 + 0x61FA_4855_3BDE_B07EL, 0x2FB6_FF11_0441_A2A8L, // 200 + 0x4E61_D377_6318_8D31L, 0x72F8_CC0D_9D01_4EEDL, // 201 + 0x7D69_5258_9E8D_AEB6L, 0x1E5A_E015_C802_17E1L, // 202 + 0x6454_41E0_7ED7_BEF8L, 0x1848_B344_A001_ACB4L, // 203 + 0x5043_67E6_CBDF_CBF9L, 0x603A_2903_B334_8A2AL, // 204 + 0x4035_ECB8_A319_6FFBL, 0x002E_8736_28F6_D4EEL, // 205 + 0x66BC_ADF4_3828_B32BL, 0x19E4_0B89_DB24_87E3L, // 206 + 0x5230_8B29_C686_F5BCL, 0x14B6_6FA1_7C1D_3983L, // 207 + 0x41C0_6F54_9ED2_5E30L, 0x1091_F2E7_967D_C79CL, // 208 + 0x6933_E554_3150_96B3L, 0x341C_B7D8_F0C9_3F5FL, // 209 + 0x5429_8443_5AA6_DEF5L, 0x767D_5FE0_C0A0_FF80L, // 210 + 0x4354_69CF_7BB8_B25EL, 0x2B97_7FE7_0080_CC66L, // 211 + 0x6BBA_42E5_92C1_1D63L, 0x5F58_CCA4_CD9A_E0A3L, // 212 + 0x562E_9BEA_DBCD_B11CL, 0x4C47_0A1D_7148_B3B6L, // 213 + 0x44F2_1655_7CA4_8DB0L, 0x3D05_A1B1_276D_5C92L, // 214 + 0x6E50_23BB_FAA0_E2B3L, 0x7B3C_35E8_3F15_60E9L, // 215 + 0x5840_1C96_621A_4EF6L, 0x2F63_5E53_65AA_B3EDL, // 216 + 0x4699_B078_4E7B_725EL, 0x591C_4B75_EAEE_F658L, // 217 + 0x70F5_E726_E3F8_B6FDL, 0x74FA_1256_44B1_8A26L, // 218 + 0x5A5E_5285_832D_5F31L, 0x43FB_41DE_9D5A_D4EBL, // 219 + 0x484B_7537_9C24_4C27L, 0x4FFC_34B2_177B_DD89L, // 220 + 0x73AB_EEBF_603A_1372L, 0x4CC6_BAB6_8BF9_6274L, // 221 + 0x5C89_8BCC_4CFB_42C2L, 0x0A38_955E_D661_1B90L, // 222 + 0x4A07_A309_D72F_689BL, 0x21C6_DDE5_784D_AFA7L, // 223 + 0x7672_9E76_2518_A75EL, 0x693E_2FD5_8D49_190BL, // 224 + 0x5EC2_185E_8413_B918L, 0x5431_BFDE_0AA0_E0D5L, // 225 + 0x4BCE_79E5_3676_2DADL, 0x29C1_664B_3BB3_E711L, // 226 + 0x794A_5CA1_F0BD_15E2L, 0x0F9B_D6DE_C5EC_A4E8L, // 227 + 0x6108_4A1B_26FD_AB1BL, 0x2616_457F_04BD_50BAL, // 228 + 0x4DA0_3B48_EBFE_227CL, 0x1E78_3798_D097_73C8L, // 229 + 0x7C33_920E_4663_6A60L, 0x30C0_58F4_80F2_52D9L, // 230 + 0x635C_74D8_384F_884DL, 0x0D66_AD90_6728_4247L, // 231 + 0x4F7D_2A46_9372_D370L, 0x711E_F140_5286_9B6CL, // 232 + 0x7F2E_AA0A_8584_8581L, 0x34FE_4ECD_50D7_5F14L, // 233 + 0x65BE_EE6E_D136_D134L, 0x2A65_0BD7_73DF_7F43L, // 234 + 0x5165_8B8B_DA92_40F6L, 0x551D_A312_C319_329CL, // 235 + 0x411E_093C_AEDB_672BL, 0x5DB1_4F42_35AD_C217L, // 236 + 0x6830_0EC7_7E2B_D845L, 0x7C4E_E536_BC49_368AL, // 237 + 0x5359_A56C_64EF_E037L, 0x7D0B_EA92_303A_9208L, // 238 + 0x42AE_1DF0_50BF_E693L, 0x173C_BBA8_2695_41A0L, // 239 + 0x6AB0_2FE6_E799_70EBL, 0x3EC7_92A6_A422_029AL, // 240 + 0x5559_BFEB_EC7A_C0BCL, 0x3239_421E_E9B4_CEE1L, // 241 + 0x4447_CCBC_BD2F_0096L, 0x5B61_01B2_5490_A581L, // 242 + 0x6D3F_ADFA_C84B_3424L, 0x2BCE_691D_541A_A268L, // 243 + 0x5766_24C8_A03C_29B6L, 0x563E_BA7D_DCE2_1B87L, // 244 + 0x45EB_50A0_8030_215EL, 0x7832_2ECB_171B_4939L, // 245 + 0x6FDE_E767_3380_3564L, 0x59E9_E478_24F8_7527L, // 246 + 0x597F_1F85_C2CC_F783L, 0x6187_E9F9_B72D_2A86L, // 247 + 0x4798_E604_9BD7_2C69L, 0x346C_BB2E_2C24_2205L, // 248 + 0x728E_3CD4_2C8B_7A42L, 0x20AD_F849_E039_D007L, // 249 + 0x5BA4_FD76_8A09_2E9BL, 0x33BE_603B_19C7_D99FL, // 250 + 0x4950_CAC5_3B3A_8BAFL, 0x42FE_B362_7B06_47B3L, // 251 + 0x754E_113B_91F7_45E5L, 0x5197_856A_5E70_72B8L, // 252 + 0x5DD8_0DC9_4192_9E51L, 0x27AC_6ABB_7EC0_5BC6L, // 253 + 0x4B13_3E3A_9ADB_B1DAL, 0x52F0_5562_CBCD_1638L, // 254 + 0x781E_C9F7_5E2C_4FC4L, 0x1E4D_556A_DFAE_89F3L, // 255 + 0x6018_A192_B1BD_0C9CL, 0x7EA4_4455_7FBE_D4C3L, // 256 + 0x4CE0_8142_27CA_707DL, 0x4BB6_9D11_32FF_109CL, // 257 + 0x7B00_CED0_3FAA_4D95L, 0x5F8A_94E8_5198_1A93L, // 258 + 0x6267_0BD9_CC88_3E11L, 0x32D5_43ED_0E13_4875L, // 259 + 0x4EB8_D647_D6D3_64DAL, 0x5BDD_CFF0_D80F_6D2BL, // 260 + 0x7DF4_8A0C_8AEB_D491L, 0x12FC_7FE7_C018_AEABL, // 261 + 0x64C3_A1A3_A256_43A7L, 0x28C9_FFEC_99AD_5889L, // 262 + 0x509C_814F_B511_CFB9L, 0x0707_FFF0_7AF1_13A1L, // 263 + 0x407D_343F_C40E_3FC7L, 0x1F39_998D_2F27_42E7L, // 264 + 0x672E_B9FF_A016_CC71L, 0x7EC2_8F48_4B72_04A4L, // 265 + 0x528B_C7FF_B345_705BL, 0x189B_A5D3_6F8E_6A1DL, // 266 + 0x4209_6CCC_8F6A_C048L, 0x7A16_1E42_BFA5_21B1L, // 267 + 0x69A8_AE14_18AA_CD41L, 0x4356_96D1_32A1_CF81L, // 268 + 0x5486_F1A9_AD55_7101L, 0x1C45_4574_2881_72CEL, // 269 + 0x439F_27BA_F111_2734L, 0x169D_D129_BA01_28A5L, // 270 + 0x6C31_D92B_1B4E_A520L, 0x242F_B50F_9001_DAA1L, // 271 + 0x568E_4755_AF72_1DB3L, 0x368C_90D9_4001_7BB4L, // 272 + 0x453E_9F77_BF8E_7E29L, 0x120A_0D7A_999A_C95DL, // 273 + 0x6ECA_98BF_98E3_FD0EL, 0x5010_1590_F5C4_7561L, // 274 + 0x58A2_13CC_7A4F_FDA5L, 0x2673_4473_F7D0_5DE8L, // 275 + 0x46E8_0FD6_C83F_FE1DL, 0x6B8F_69F6_5FD9_E4B9L, // 276 + 0x7173_4C8A_D9FF_FCFCL, 0x45B2_4323_CC8F_D45CL, // 277 + 0x5AC2_A3A2_47FF_FD96L, 0x6AF5_0283_0A0C_A9E3L, // 278 + 0x489B_B61B_6CCC_CADFL, 0x08C4_0202_6E70_87E9L, // 279 + 0x742C_5692_47AE_1164L, 0x746C_D003_E3E7_3FDBL, // 280 + 0x5CF0_4541_D2F1_A783L, 0x76BD_7336_4FEC_3315L, // 281 + 0x4A59_D101_758E_1F9CL, 0x5EFD_F5C5_0CBC_F5ABL, // 282 + 0x76F6_1B35_88E3_65C7L, 0x4B2F_EFA1_ADFB_22ABL, // 283 + 0x5F2B_48F7_A0B5_EB06L, 0x08F3_261A_F195_B555L, // 284 + 0x4C22_A0C6_1A2B_226BL, 0x20C2_84E2_5ADE_2AABL, // 285 + 0x79D1_013C_F6AB_6A45L, 0x1AD0_D49D_5E30_4444L, // 286 + 0x6174_00FD_9222_BB6AL, 0x48A7_107D_E4F3_69D0L, // 287 + 0x4DF6_6731_41B5_62BBL, 0x53B8_D9FE_50C2_BB0DL, // 288 + 0x7CBD_71E8_6922_3792L, 0x52C1_5CCA_1AD1_2B48L, // 289 + 0x63CA_C186_BA81_C60EL, 0x7567_7D6E_7BDA_8906L, // 290 + 0x4FD5_679E_FB9B_04D8L, 0x5DEC_6458_6315_3A6CL, // 291 + 0x7FBB_D8FE_5F5E_6E27L, 0x497A_3A27_04EE_C3DFL, // 292 + }; + +} diff --git a/tests/test_data/std/jdk/internal/misc/Blocker.class b/tests/test_data/std/jdk/internal/misc/Blocker.class new file mode 100644 index 00000000..297f11d8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/Blocker.class differ diff --git a/tests/test_data/std/jdk/internal/misc/Blocker.java b/tests/test_data/std/jdk/internal/misc/Blocker.java new file mode 100644 index 00000000..d41ae737 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/Blocker.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + +/** + * Defines static methods to mark the beginning and end of a possibly blocking + * operation. The methods are intended to be used with try-finally as follows: + * {@snippet lang=java : + * boolean attempted = Blocker.begin(); + * try { + * // blocking operation + * } finally { + * Blocker.end(attempted); + * } + * } + * If invoked from a virtual thread and the underlying carrier thread is a + * CarrierThread then the code in the block runs as if it were in run in + * ForkJoinPool.ManagedBlocker. This means the pool can be expanded to support + * additional parallelism during the blocking operation. + */ +public class Blocker { + private static final JavaLangAccess JLA; + static { + JLA = SharedSecrets.getJavaLangAccess(); + if (JLA == null) { + throw new InternalError("JavaLangAccess not setup"); + } + } + + private Blocker() { } + + private static Thread currentCarrierThread() { + return JLA.currentCarrierThread(); + } + + /** + * Marks the beginning of a blocking operation. + * @return true if tryCompensate attempted + */ + public static boolean begin() { + if (VM.isBooted() + && Thread.currentThread().isVirtual() + && currentCarrierThread() instanceof CarrierThread ct) { + return ct.beginBlocking(); + } + return false; + } + + /** + * Marks the beginning of a possibly blocking operation. + * @param blocking true if the operation may block, otherwise false + * @return true if tryCompensate attempted + */ + public static boolean begin(boolean blocking) { + return (blocking) ? begin() : false; + } + + /** + * Marks the end of an operation that may have blocked. + * @param attempted if tryCompensate attempted + */ + public static void end(boolean attempted) { + if (attempted) { + CarrierThread ct = (CarrierThread) currentCarrierThread(); + ct.endBlocking(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/misc/CDS.class b/tests/test_data/std/jdk/internal/misc/CDS.class new file mode 100644 index 00000000..a84035a4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/CDS.class differ diff --git a/tests/test_data/std/jdk/internal/misc/CDS.java b/tests/test_data/std/jdk/internal/misc/CDS.java new file mode 100644 index 00000000..ddb25cb7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/CDS.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.io.InputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import jdk.internal.access.SharedSecrets; + +public class CDS { + // Must be in sync with cdsConfig.hpp + private static final int IS_DUMPING_ARCHIVE = 1 << 0; + private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 1; + private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 2; + private static final int IS_USING_ARCHIVE = 1 << 3; + private static final int configStatus = getCDSConfigStatus(); + + /** + * Should we log the use of lambda form invokers? + */ + public static boolean isLoggingLambdaFormInvokers() { + return (configStatus & IS_LOGGING_LAMBDA_FORM_INVOKERS) != 0; + } + + /** + * Is the VM writing to a (static or dynamic) CDS archive. + */ + public static boolean isDumpingArchive() { + return (configStatus & IS_DUMPING_ARCHIVE) != 0; + } + + /** + * Is the VM using at least one CDS archive? + */ + public static boolean isUsingArchive() { + return (configStatus & IS_USING_ARCHIVE) != 0; + } + + /** + * Is dumping static archive. + */ + public static boolean isDumpingStaticArchive() { + return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0; + } + + private static native int getCDSConfigStatus(); + private static native void logLambdaFormInvoker(String line); + + /** + * Initialize archived static fields in the given Class using archived + * values from CDS dump time. Also initialize the classes of objects in + * the archived graph referenced by those fields. + * + * Those static fields remain as uninitialized if there is no mapped CDS + * java heap data or there is any error during initialization of the + * object class in the archived graph. + */ + public static native void initializeFromArchive(Class c); + + /** + * Ensure that the native representation of all archived java.lang.Module objects + * are properly restored. + */ + public static native void defineArchivedModules(ClassLoader platformLoader, ClassLoader systemLoader); + + /** + * Returns a predictable "random" seed derived from the VM's build ID and version, + * to be used by java.util.ImmutableCollections to ensure that archived + * ImmutableCollections are always sorted the same order for the same VM build. + */ + public static native long getRandomSeedForDumping(); + + /** + * log lambda form invoker holder, name and method type + */ + public static void logLambdaFormInvoker(String prefix, String holder, String name, String type) { + if (isLoggingLambdaFormInvokers()) { + logLambdaFormInvoker(prefix + " " + holder + " " + name + " " + type); + } + } + + /** + * log species + */ + public static void logSpeciesType(String prefix, String cn) { + if (isLoggingLambdaFormInvokers()) { + logLambdaFormInvoker(prefix + " " + cn); + } + } + + static final String DIRECT_HOLDER_CLASS_NAME = "java.lang.invoke.DirectMethodHandle$Holder"; + static final String DELEGATING_HOLDER_CLASS_NAME = "java.lang.invoke.DelegatingMethodHandle$Holder"; + static final String BASIC_FORMS_HOLDER_CLASS_NAME = "java.lang.invoke.LambdaForm$Holder"; + static final String INVOKERS_HOLDER_CLASS_NAME = "java.lang.invoke.Invokers$Holder"; + + private static boolean isValidHolderName(String name) { + return name.equals(DIRECT_HOLDER_CLASS_NAME) || + name.equals(DELEGATING_HOLDER_CLASS_NAME) || + name.equals(BASIC_FORMS_HOLDER_CLASS_NAME) || + name.equals(INVOKERS_HOLDER_CLASS_NAME); + } + + private static boolean isBasicTypeChar(char c) { + return "LIJFDV".indexOf(c) >= 0; + } + + private static boolean isValidMethodType(String type) { + String[] typeParts = type.split("_"); + // check return type (second part) + if (typeParts.length != 2 || typeParts[1].length() != 1 + || !isBasicTypeChar(typeParts[1].charAt(0))) { + return false; + } + // first part + if (!isBasicTypeChar(typeParts[0].charAt(0))) { + return false; + } + for (int i = 1; i < typeParts[0].length(); i++) { + char c = typeParts[0].charAt(i); + if (!isBasicTypeChar(c)) { + if (!(c >= '0' && c <= '9')) { + return false; + } + } + } + return true; + } + + // Throw exception on invalid input + private static void validateInputLines(String[] lines) { + for (String s: lines) { + if (!s.startsWith("[LF_RESOLVE]") && !s.startsWith("[SPECIES_RESOLVE]")) { + throw new IllegalArgumentException("Wrong prefix: " + s); + } + + String[] parts = s.split(" "); + boolean isLF = s.startsWith("[LF_RESOLVE]"); + + if (isLF) { + if (parts.length != 4) { + throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); + } + if (!isValidHolderName(parts[1])) { + throw new IllegalArgumentException("Invalid holder class name: " + parts[1]); + } + if (!isValidMethodType(parts[3])) { + throw new IllegalArgumentException("Invalid method type: " + parts[3]); + } + } else { + if (parts.length != 2) { + throw new IllegalArgumentException("Incorrect number of items in the line: " + parts.length); + } + } + } + } + + /** + * called from vm to generate MethodHandle holder classes + * @return {@code Object[]} if holder classes can be generated. + * @param lines in format of LF_RESOLVE or SPECIES_RESOLVE output + */ + private static Object[] generateLambdaFormHolderClasses(String[] lines) { + Objects.requireNonNull(lines); + validateInputLines(lines); + Stream lineStream = Arrays.stream(lines); + Map result = SharedSecrets.getJavaLangInvokeAccess().generateHolderClasses(lineStream); + int size = result.size(); + Object[] retArray = new Object[size * 2]; + int index = 0; + for (Map.Entry entry : result.entrySet()) { + retArray[index++] = entry.getKey(); + retArray[index++] = entry.getValue(); + }; + return retArray; + } + + private static native void dumpClassList(String listFileName); + private static native void dumpDynamicArchive(String archiveFileName); + + private static String drainOutput(InputStream stream, long pid, String tail, List cmds) { + String fileName = "java_pid" + pid + "_" + tail; + new Thread( ()-> { + try (InputStreamReader isr = new InputStreamReader(stream); + BufferedReader rdr = new BufferedReader(isr); + PrintStream prt = new PrintStream(fileName)) { + prt.println("Command:"); + for (String s : cmds) { + prt.print(s + " "); + } + prt.println(""); + String line; + while((line = rdr.readLine()) != null) { + prt.println(line); + } + } catch (IOException e) { + throw new RuntimeException("IOException happens during drain stream to file " + + fileName + ": " + e.getMessage()); + }}).start(); + return fileName; + } + + private static String[] excludeFlags = { + "-XX:DumpLoadedClassList=", + "-XX:+RecordDynamicDumpInfo", + "-Xshare:", + "-XX:SharedClassListFile=", + "-XX:SharedArchiveFile=", + "-XX:ArchiveClassesAtExit="}; + private static boolean containsExcludedFlags(String testStr) { + for (String e : excludeFlags) { + if (testStr.contains(e)) { + return true; + } + } + return false; + } + + /** + * called from jcmd VM.cds to dump static or dynamic shared archive + * @param isStatic true for dump static archive or false for dynnamic archive. + * @param fileName user input archive name, can be null. + * @return The archive name if successfully dumped. + */ + private static String dumpSharedArchive(boolean isStatic, String fileName) throws Exception { + String cwd = new File("").getAbsolutePath(); // current dir used for printing message. + String currentPid = String.valueOf(ProcessHandle.current().pid()); + String archiveFileName = fileName != null ? fileName : + "java_pid" + currentPid + (isStatic ? "_static.jsa" : "_dynamic.jsa"); + + String tempArchiveFileName = archiveFileName + ".temp"; + File tempArchiveFile = new File(tempArchiveFileName); + // The operation below may cause exception if the file or its dir is protected. + if (!tempArchiveFile.exists()) { + tempArchiveFile.createNewFile(); + } + tempArchiveFile.delete(); + + if (isStatic) { + String listFileName = archiveFileName + ".classlist"; + File listFile = new File(listFileName); + if (listFile.exists()) { + listFile.delete(); + } + dumpClassList(listFileName); + String jdkHome = System.getProperty("java.home"); + String classPath = System.getProperty("java.class.path"); + List cmds = new ArrayList(); + cmds.add(jdkHome + File.separator + "bin" + File.separator + "java"); // java + cmds.add("-cp"); + cmds.add(classPath); + cmds.add("-Xlog:cds"); + cmds.add("-Xshare:dump"); + cmds.add("-XX:SharedClassListFile=" + listFileName); + cmds.add("-XX:SharedArchiveFile=" + tempArchiveFileName); + + // All runtime args. + String[] vmArgs = VM.getRuntimeArguments(); + if (vmArgs != null) { + for (String arg : vmArgs) { + if (arg != null && !containsExcludedFlags(arg)) { + cmds.add(arg); + } + } + } + + Process proc = Runtime.getRuntime().exec(cmds.toArray(new String[0])); + + // Drain stdout/stderr to files in new threads. + String stdOutFileName = drainOutput(proc.getInputStream(), proc.pid(), "stdout", cmds); + String stdErrFileName = drainOutput(proc.getErrorStream(), proc.pid(), "stderr", cmds); + + proc.waitFor(); + // done, delete classlist file. + listFile.delete(); + + // Check if archive has been successfully dumped. We won't reach here if exception happens. + // Throw exception if file is not created. + if (!tempArchiveFile.exists()) { + throw new RuntimeException("Archive file " + tempArchiveFileName + + " is not created, please check stdout file " + + cwd + File.separator + stdOutFileName + " or stderr file " + + cwd + File.separator + stdErrFileName + " for more detail"); + } + } else { + dumpDynamicArchive(tempArchiveFileName); + if (!tempArchiveFile.exists()) { + throw new RuntimeException("Archive file " + tempArchiveFileName + + " is not created, please check current working directory " + + cwd + " for process " + + currentPid + " output for more detail"); + } + } + // Override the existing archive file + File archiveFile = new File(archiveFileName); + if (archiveFile.exists()) { + archiveFile.delete(); + } + if (!tempArchiveFile.renameTo(archiveFile)) { + throw new RuntimeException("Cannot rename temp file " + tempArchiveFileName + " to archive file" + archiveFileName); + } + // Everything goes well, print out the file name. + String archiveFilePath = new File(archiveFileName).getAbsolutePath(); + System.out.println("The process was attached by jcmd and dumped a " + (isStatic ? "static" : "dynamic") + " archive " + archiveFilePath); + return archiveFilePath; + } +} diff --git a/tests/test_data/std/jdk/internal/misc/CarrierThread$1.class b/tests/test_data/std/jdk/internal/misc/CarrierThread$1.class new file mode 100644 index 00000000..8f1ad64b Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/CarrierThread$1.class differ diff --git a/tests/test_data/std/jdk/internal/misc/CarrierThread$ForkJoinPools.class b/tests/test_data/std/jdk/internal/misc/CarrierThread$ForkJoinPools.class new file mode 100644 index 00000000..79a6b36a Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/CarrierThread$ForkJoinPools.class differ diff --git a/tests/test_data/std/jdk/internal/misc/CarrierThread.class b/tests/test_data/std/jdk/internal/misc/CarrierThread.class new file mode 100644 index 00000000..cf934c7a Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/CarrierThread.class differ diff --git a/tests/test_data/std/jdk/internal/misc/CarrierThread.java b/tests/test_data/std/jdk/internal/misc/CarrierThread.java new file mode 100644 index 00000000..d20b402f --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/CarrierThread.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.JavaUtilConcurrentFJPAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.vm.Continuation; + +/** + * A ForkJoinWorkerThread that can be used as a carrier thread. + */ +public class CarrierThread extends ForkJoinWorkerThread { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private static final Unsafe U = Unsafe.getUnsafe(); + + private static final ThreadGroup CARRIER_THREADGROUP = carrierThreadGroup(); + @SuppressWarnings("removal") + private static final AccessControlContext INNOCUOUS_ACC = innocuousACC(); + + private static final long CONTEXTCLASSLOADER; + private static final long INHERITABLETHREADLOCALS; + private static final long INHERITEDACCESSCONTROLCONTEXT; + + // compensating state + private static final int NOT_COMPENSATING = 0; + private static final int COMPENSATE_IN_PROGRESS = 1; + private static final int COMPENSATING = 2; + private int compensating; + + // FJP value to adjust release counts + private long compensateValue; + + @SuppressWarnings("this-escape") + public CarrierThread(ForkJoinPool pool) { + super(CARRIER_THREADGROUP, pool, true); + U.putReference(this, CONTEXTCLASSLOADER, ClassLoader.getSystemClassLoader()); + U.putReference(this, INHERITABLETHREADLOCALS, null); + U.putReferenceRelease(this, INHERITEDACCESSCONTROLCONTEXT, INNOCUOUS_ACC); + } + + /** + * Mark the start of a blocking operation. + */ + public boolean beginBlocking() { + assert Thread.currentThread().isVirtual() && JLA.currentCarrierThread() == this; + assert compensating == NOT_COMPENSATING || compensating == COMPENSATING; + + if (compensating == NOT_COMPENSATING) { + // don't preempt when attempting to compensate + Continuation.pin(); + try { + compensating = COMPENSATE_IN_PROGRESS; + + // Uses FJP.tryCompensate to start or re-activate a spare thread + compensateValue = ForkJoinPools.beginCompensatedBlock(getPool()); + compensating = COMPENSATING; + return true; + } catch (Throwable e) { + // exception starting spare thread + compensating = NOT_COMPENSATING; + throw e; + } finally { + Continuation.unpin(); + } + } else { + return false; + } + } + + /** + * Mark the end of a blocking operation. + */ + public void endBlocking() { + assert Thread.currentThread() == this || JLA.currentCarrierThread() == this; + if (compensating == COMPENSATING) { + ForkJoinPools.endCompensatedBlock(getPool(), compensateValue); + compensating = NOT_COMPENSATING; + compensateValue = 0; + } + } + + @Override + public void setUncaughtExceptionHandler(UncaughtExceptionHandler ueh) { } + + @Override + public void setContextClassLoader(ClassLoader cl) { + throw new SecurityException("setContextClassLoader"); + } + + /** + * The thread group for the carrier threads. + */ + @SuppressWarnings("removal") + private static ThreadGroup carrierThreadGroup() { + return AccessController.doPrivileged(new PrivilegedAction() { + public ThreadGroup run() { + ThreadGroup group = JLA.currentCarrierThread().getThreadGroup(); + for (ThreadGroup p; (p = group.getParent()) != null; ) + group = p; + var carrierThreadsGroup = new ThreadGroup(group, "CarrierThreads"); + return carrierThreadsGroup; + } + }); + } + + /** + * Return an AccessControlContext that doesn't support any permissions. + */ + @SuppressWarnings("removal") + private static AccessControlContext innocuousACC() { + return new AccessControlContext(new ProtectionDomain[] { + new ProtectionDomain(null, null) + }); + } + + /** + * Defines static methods to invoke non-public ForkJoinPool methods via the + * shared secret support. + */ + private static class ForkJoinPools { + private static final JavaUtilConcurrentFJPAccess FJP_ACCESS = + SharedSecrets.getJavaUtilConcurrentFJPAccess(); + static long beginCompensatedBlock(ForkJoinPool pool) { + return FJP_ACCESS.beginCompensatedBlock(pool); + } + static void endCompensatedBlock(ForkJoinPool pool, long post) { + FJP_ACCESS.endCompensatedBlock(pool, post); + } + } + + static { + CONTEXTCLASSLOADER = U.objectFieldOffset(Thread.class, + "contextClassLoader"); + INHERITABLETHREADLOCALS = U.objectFieldOffset(Thread.class, + "inheritableThreadLocals"); + INHERITEDACCESSCONTROLCONTEXT = U.objectFieldOffset(Thread.class, + "inheritedAccessControlContext"); + } +} diff --git a/tests/test_data/std/jdk/internal/misc/CarrierThreadLocal.class b/tests/test_data/std/jdk/internal/misc/CarrierThreadLocal.class new file mode 100644 index 00000000..20298494 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/CarrierThreadLocal.class differ diff --git a/tests/test_data/std/jdk/internal/misc/CarrierThreadLocal.java b/tests/test_data/std/jdk/internal/misc/CarrierThreadLocal.java new file mode 100644 index 00000000..b14ecaff --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/CarrierThreadLocal.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + +/** + * A {@link ThreadLocal} variant which binds its value to current thread's + * carrier thread. + */ +public class CarrierThreadLocal extends ThreadLocal { + + @Override + public T get() { + return JLA.getCarrierThreadLocal(this); + } + + @Override + public void set(T value) { + JLA.setCarrierThreadLocal(this, value); + } + + @Override + public void remove() { + JLA.removeCarrierThreadLocal(this); + } + + public boolean isPresent() { + return JLA.isCarrierThreadLocalPresent(this); + } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); +} diff --git a/tests/test_data/std/jdk/internal/misc/ExtendedMapMode.class b/tests/test_data/std/jdk/internal/misc/ExtendedMapMode.class new file mode 100644 index 00000000..3853e1b1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ExtendedMapMode.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ExtendedMapMode.java b/tests/test_data/std/jdk/internal/misc/ExtendedMapMode.java new file mode 100644 index 00000000..f2852f56 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/ExtendedMapMode.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.nio.channels.FileChannel.MapMode; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; + +/** + * JDK-specific map modes implemented in java.base. + */ +public class ExtendedMapMode { + + static final MethodHandle MAP_MODE_CONSTRUCTOR; + static { + try { + PrivilegedExceptionAction pae = () -> + MethodHandles.privateLookupIn(MapMode.class, MethodHandles.lookup()); + @SuppressWarnings("removal") + Lookup lookup = AccessController.doPrivileged(pae); + var methodType = MethodType.methodType(void.class, String.class); + MAP_MODE_CONSTRUCTOR = lookup.findConstructor(MapMode.class, methodType); + } catch (Exception e) { + throw new InternalError(e); + } + } + + public static final MapMode READ_ONLY_SYNC = newMapMode("READ_ONLY_SYNC"); + + public static final MapMode READ_WRITE_SYNC = newMapMode("READ_WRITE_SYNC"); + + private static MapMode newMapMode(String name) { + try { + return (MapMode) MAP_MODE_CONSTRUCTOR.invoke(name); + } catch (Throwable e) { + throw new InternalError(e); + } + } + + private ExtendedMapMode() { } +} diff --git a/tests/test_data/std/jdk/internal/misc/FileSystemOption.class b/tests/test_data/std/jdk/internal/misc/FileSystemOption.class new file mode 100644 index 00000000..b899e125 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/FileSystemOption.class differ diff --git a/tests/test_data/std/jdk/internal/misc/FileSystemOption.java b/tests/test_data/std/jdk/internal/misc/FileSystemOption.java new file mode 100644 index 00000000..5a55b7df --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/FileSystemOption.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import sun.nio.fs.ExtendedOptions; + +import java.nio.file.CopyOption; +import java.nio.file.OpenOption; +import java.nio.file.WatchEvent; + +/** + * Internal file system options for jdk.unsupported com.sun.nio.file API use. + */ +public final class FileSystemOption { + public static final FileSystemOption INTERRUPTIBLE = + new FileSystemOption<>(ExtendedOptions.INTERRUPTIBLE); + public static final FileSystemOption NOSHARE_READ = + new FileSystemOption<>(ExtendedOptions.NOSHARE_READ); + public static final FileSystemOption NOSHARE_WRITE = + new FileSystemOption<>(ExtendedOptions.NOSHARE_WRITE); + public static final FileSystemOption NOSHARE_DELETE = + new FileSystemOption<>(ExtendedOptions.NOSHARE_DELETE); + public static final FileSystemOption FILE_TREE = + new FileSystemOption<>(ExtendedOptions.FILE_TREE); + public static final FileSystemOption DIRECT = + new FileSystemOption<>(ExtendedOptions.DIRECT); + public static final FileSystemOption SENSITIVITY_HIGH = + new FileSystemOption<>(ExtendedOptions.SENSITIVITY_HIGH); + public static final FileSystemOption SENSITIVITY_MEDIUM = + new FileSystemOption<>(ExtendedOptions.SENSITIVITY_MEDIUM); + public static final FileSystemOption SENSITIVITY_LOW = + new FileSystemOption<>(ExtendedOptions.SENSITIVITY_LOW); + + private final ExtendedOptions.InternalOption internalOption; + private FileSystemOption(ExtendedOptions.InternalOption option) { + this.internalOption = option; + } + + /** + * Register this internal option as an OpenOption. + */ + public void register(OpenOption option) { + internalOption.register(option); + } + + /** + * Register this internal option as a CopyOption. + */ + public void register(CopyOption option) { + internalOption.register(option); + } + + /** + * Register this internal option as a WatchEvent.Modifier. + */ + public void register(WatchEvent.Modifier option) { + internalOption.register(option); + } + + /** + * Register this internal option as a WatchEvent.Modifier with the + * given parameter. + */ + public void register(WatchEvent.Modifier option, T param) { + internalOption.register(option, param); + } +} diff --git a/tests/test_data/std/jdk/internal/misc/InnocuousThread$1.class b/tests/test_data/std/jdk/internal/misc/InnocuousThread$1.class new file mode 100644 index 00000000..f77fa364 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/InnocuousThread$1.class differ diff --git a/tests/test_data/std/jdk/internal/misc/InnocuousThread$2.class b/tests/test_data/std/jdk/internal/misc/InnocuousThread$2.class new file mode 100644 index 00000000..18cf75e4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/InnocuousThread$2.class differ diff --git a/tests/test_data/std/jdk/internal/misc/InnocuousThread$3.class b/tests/test_data/std/jdk/internal/misc/InnocuousThread$3.class new file mode 100644 index 00000000..73a8f3a4 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/InnocuousThread$3.class differ diff --git a/tests/test_data/std/jdk/internal/misc/InnocuousThread$4.class b/tests/test_data/std/jdk/internal/misc/InnocuousThread$4.class new file mode 100644 index 00000000..522f4074 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/InnocuousThread$4.class differ diff --git a/tests/test_data/std/jdk/internal/misc/InnocuousThread.class b/tests/test_data/std/jdk/internal/misc/InnocuousThread.class new file mode 100644 index 00000000..ffbcd67a Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/InnocuousThread.class differ diff --git a/tests/test_data/std/jdk/internal/misc/InnocuousThread.java b/tests/test_data/std/jdk/internal/misc/InnocuousThread.java new file mode 100644 index 00000000..f8db0bc1 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/InnocuousThread.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.ProtectionDomain; +import java.security.PrivilegedAction; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A thread that has no permissions, is not a member of any user-defined + * ThreadGroup and supports the ability to erase ThreadLocals. + */ +@SuppressWarnings("removal") +public final class InnocuousThread extends Thread { + private static final jdk.internal.misc.Unsafe UNSAFE; + private static final long THREAD_LOCALS; + private static final long INHERITABLE_THREAD_LOCALS; + private static final ThreadGroup INNOCUOUSTHREADGROUP; + private static final AccessControlContext ACC; + private static final long INHERITEDACCESSCONTROLCONTEXT; + private static final long CONTEXTCLASSLOADER; + + private static final AtomicInteger threadNumber = new AtomicInteger(1); + private static String newName() { + return "InnocuousThread-" + threadNumber.getAndIncrement(); + } + + /** + * Returns a new InnocuousThread with an auto-generated thread name, + * and its context class loader is set to the system class loader. + */ + public static Thread newThread(Runnable target) { + return newThread(newName(), target); + } + + /** + * Returns a new InnocuousThread with its context class loader + * set to the system class loader. + */ + public static Thread newThread(String name, Runnable target) { + return newThread(name, target, -1); + } + /** + * Returns a new InnocuousThread with its context class loader + * set to the system class loader. The thread priority will be + * set to the given priority. + */ + public static Thread newThread(String name, Runnable target, int priority) { + if (System.getSecurityManager() == null) { + return createThread(name, target, 0L, + ClassLoader.getSystemClassLoader(), priority); + } + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Thread run() { + return createThread(name, target, 0L, + ClassLoader.getSystemClassLoader(), priority); + } + }); + } + + /** + * Returns a new InnocuousThread with an auto-generated thread name. + * Its context class loader is set to null. + */ + public static Thread newSystemThread(Runnable target) { + return newSystemThread(newName(), target); + } + + /** + * Returns a new InnocuousThread with null context class loader. + */ + public static Thread newSystemThread(String name, Runnable target) { + return newSystemThread(name, target, -1); + } + + /** + * Returns a new InnocuousThread with null context class loader. + * Thread priority is set to the given priority. + */ + public static Thread newSystemThread(String name, Runnable target, int priority) { + if (System.getSecurityManager() == null) { + return createThread(name, target, 0L, null, priority); + } + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Thread run() { + return createThread(name, target, 0L, + null, priority); + } + }); + } + + /** + * Returns a new InnocuousThread with null context class loader. + * Thread priority is set to the given priority. + */ + public static Thread newSystemThread(String name, Runnable target, + long stackSize, int priority) { + if (System.getSecurityManager() == null) { + return createThread(name, target, stackSize, null, priority); + } + return AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Thread run() { + return createThread(name, target, 0L, + null, priority); + } + }); + } + + private static Thread createThread(String name, Runnable target, long stackSize, + ClassLoader loader, int priority) { + Thread t = new InnocuousThread(INNOCUOUSTHREADGROUP, + target, name, stackSize, loader); + if (priority >= 0) { + t.setPriority(priority); + } + return t; + } + + private InnocuousThread(ThreadGroup group, Runnable target, String name, + long stackSize, ClassLoader tccl) { + super(group, target, name, stackSize, false); + UNSAFE.putReferenceRelease(this, INHERITEDACCESSCONTROLCONTEXT, ACC); + UNSAFE.putReferenceRelease(this, CONTEXTCLASSLOADER, tccl); + } + + @Override + public void setUncaughtExceptionHandler(UncaughtExceptionHandler x) { + // silently fail + } + + @Override + public void setContextClassLoader(ClassLoader cl) { + // Allow clearing of the TCCL to remove the reference to the system classloader. + if (cl == null) + super.setContextClassLoader(null); + else + throw new SecurityException("setContextClassLoader"); + } + + /** + * Drops all thread locals (and inherited thread locals). + */ + public final void eraseThreadLocals() { + UNSAFE.putReference(this, THREAD_LOCALS, null); + UNSAFE.putReference(this, INHERITABLE_THREAD_LOCALS, null); + } + + // ensure run method is run only once + private volatile boolean hasRun; + + @Override + public void run() { + if (Thread.currentThread() == this && !hasRun) { + hasRun = true; + super.run(); + } + } + + // Use Unsafe to access Thread group and ThreadGroup parent fields + static { + try { + ACC = new AccessControlContext(new ProtectionDomain[] { + new ProtectionDomain(null, null) + }); + + // Find and use topmost ThreadGroup as parent of new group + UNSAFE = jdk.internal.misc.Unsafe.getUnsafe(); + Class tk = Thread.class; + Class gk = ThreadGroup.class; + + THREAD_LOCALS = UNSAFE.objectFieldOffset(tk, "threadLocals"); + INHERITABLE_THREAD_LOCALS = UNSAFE.objectFieldOffset + (tk, "inheritableThreadLocals"); + INHERITEDACCESSCONTROLCONTEXT = UNSAFE.objectFieldOffset + (tk, "inheritedAccessControlContext"); + CONTEXTCLASSLOADER = UNSAFE.objectFieldOffset + (tk, "contextClassLoader"); + + long gp = UNSAFE.objectFieldOffset(gk, "parent"); + ThreadGroup group = Thread.currentThread().getThreadGroup(); + + while (group != null) { + ThreadGroup parent = (ThreadGroup)UNSAFE.getReference(group, gp); + if (parent == null) + break; + group = parent; + } + final ThreadGroup root = group; + if (System.getSecurityManager() == null) { + INNOCUOUSTHREADGROUP = new ThreadGroup(root, "InnocuousThreadGroup"); + } else { + INNOCUOUSTHREADGROUP = AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public ThreadGroup run() { + return new ThreadGroup(root, "InnocuousThreadGroup"); + } + }); + } + } catch (Exception e) { + throw new Error(e); + } + } +} diff --git a/tests/test_data/std/jdk/internal/misc/InternalLock.class b/tests/test_data/std/jdk/internal/misc/InternalLock.class new file mode 100644 index 00000000..42c3da4e Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/InternalLock.class differ diff --git a/tests/test_data/std/jdk/internal/misc/InternalLock.java b/tests/test_data/std/jdk/internal/misc/InternalLock.java new file mode 100644 index 00000000..d7a271e6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/InternalLock.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.util.concurrent.locks.ReentrantLock; + +/** + * A reentrant mutual exclusion lock for internal use. The lock does not + * implement {@link java.util.concurrent.locks.Lock} or extend {@link + * java.util.concurrent.locks.ReentrantLock} so that it can be distinguished + * from lock objects accessible to subclasses of {@link java.io.Reader} and + * {@link java.io.Writer} (it is possible to create a Reader that uses a + * lock object of type ReentrantLock for example). + */ +public class InternalLock { + private static final boolean CAN_USE_INTERNAL_LOCK; + static { + String s = System.getProperty("jdk.io.useMonitors"); + if (s != null && (s.isEmpty() || s.equals("true"))) { + CAN_USE_INTERNAL_LOCK = false; + } else { + CAN_USE_INTERNAL_LOCK = true; + } + } + + private final ReentrantLock lock; + + private InternalLock() { + this.lock = new ReentrantLock(); + } + + /** + * Returns a new InternalLock or null. + */ + public static InternalLock newLockOrNull() { + return (CAN_USE_INTERNAL_LOCK) ? new InternalLock() : null; + } + + /** + * Returns a new InternalLock or the given object. + */ + public static Object newLockOr(Object obj) { + return (CAN_USE_INTERNAL_LOCK) ? new InternalLock() : obj; + } + + public boolean tryLock() { + return lock.tryLock(); + } + + public void lock() { + lock.lock(); + } + + public void unlock() { + lock.unlock(); + } + + public boolean isHeldByCurrentThread() { + return lock.isHeldByCurrentThread(); + } +} diff --git a/tests/test_data/std/jdk/internal/misc/MethodFinder.class b/tests/test_data/std/jdk/internal/misc/MethodFinder.class new file mode 100644 index 00000000..aef6b6e0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/MethodFinder.class differ diff --git a/tests/test_data/std/jdk/internal/misc/MethodFinder.java b/tests/test_data/std/jdk/internal/misc/MethodFinder.java new file mode 100644 index 00000000..970cebe7 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/MethodFinder.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + +/** + * A collection of static methods that return specific method objects of interest. + */ +public class MethodFinder { + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + private MethodFinder() { + throw new AssertionError("private constructor"); + } + + /** + * Return the first method that meets the requirements of an application main method + * {@jls 12.1.4}. The method must: + *

    + *
  • be declared in this class's hierarchy
  • + *
  • have the name "main"
  • + *
  • have a single argument of type {@code String[]}, {@code String...} or no argument
  • + *
  • have the return type of void
  • + *
  • be public, protected or package private
  • + *
  • not be abstract
  • + *
+ * + * The method returned would be used by a launcher to initiate the execution of an + * application. + * + * Searching continues until a main method is found or the search is exhausted. The + * primary search occurs in two phases, once for a main method with a {@code + * String[]} or {@code String...} argument and failing that, once for a main method + * with a no arguments. The search itself uses recursion to first look at methods + * in this class, then default methods in this class's interface hierarchy and + * then repeating these steps with the class's super class. + * + * @apiNote The method returned may be declared in this class, a super class + * or as a default method of an interface that the class or super class + * implements. + *

It is not possible to declare a static main method and instance main + * method with the same signature in the same class. {@jls 8.4.2} states that + * "It is a compile-time error to declare two methods with override-equivalent + * signatures in a class." + *

{@link SecurityException SecurityExceptions} can halt + * the search. In this case, a null is returned. + * + * @return the main method if a method found or null if no method is found + * + * @jls 8.2 Class Members + * @jls 8.4 Method Declarations + * @jls 8.4.2 Method Signature + * @jls 12.1.4 Invoke a main method + */ + public static Method findMainMethod(Class cls) { + boolean isPreview = PreviewFeatures.isEnabled(); + Method mainMethod = JLA.findMethod(cls, !isPreview, "main", String[].class); + + if (isPreview && mainMethod == null) { + mainMethod = JLA.findMethod(cls, false, "main"); + } + + if (mainMethod == null) { + return null; + } + + int mods = mainMethod.getModifiers(); + + if (Modifier.isAbstract(mods) || + mainMethod.getReturnType() != void.class || + (isPreview && Modifier.isPrivate(mods)) || + (!isPreview && !Modifier.isStatic(mods))) { + return null; + } + + return mainMethod; + } + +} diff --git a/tests/test_data/std/jdk/internal/misc/OSEnvironment.class b/tests/test_data/std/jdk/internal/misc/OSEnvironment.class new file mode 100644 index 00000000..3be8e953 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/OSEnvironment.class differ diff --git a/tests/test_data/std/jdk/internal/misc/OSEnvironment.java b/tests/test_data/std/jdk/internal/misc/OSEnvironment.java new file mode 100644 index 00000000..506273a5 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/OSEnvironment.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +public class OSEnvironment { + + /* + * Initialize any miscellaneous operating system settings that need to be set + * for the class libraries. + */ + public static void initialize() { + // no-op on Solaris and Linux + } + +} diff --git a/tests/test_data/std/jdk/internal/misc/PreviewFeatures.class b/tests/test_data/std/jdk/internal/misc/PreviewFeatures.class new file mode 100644 index 00000000..56451a69 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/PreviewFeatures.class differ diff --git a/tests/test_data/std/jdk/internal/misc/PreviewFeatures.java b/tests/test_data/std/jdk/internal/misc/PreviewFeatures.java new file mode 100644 index 00000000..05a0c2c4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/PreviewFeatures.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.misc; + +/** + * Defines static methods to test if preview features are enabled at run-time. + */ +public class PreviewFeatures { + private static final boolean ENABLED = isPreviewEnabled(); + + private PreviewFeatures() { + } + + /** + * {@return true if preview features are enabled, otherwise false} + */ + public static boolean isEnabled() { + return ENABLED; + } + + /** + * Ensures that preview features are enabled. + * @throws UnsupportedOperationException if preview features are not enabled + */ + public static void ensureEnabled() { + if (!isEnabled()) { + throw new UnsupportedOperationException( + "Preview Features not enabled, need to run with --enable-preview"); + } + } + + private static native boolean isPreviewEnabled(); +} diff --git a/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess$Scoped.class b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess$Scoped.class new file mode 100644 index 00000000..07dcaeff Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess$Scoped.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess$ScopedAccessError.class b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess$ScopedAccessError.class new file mode 100644 index 00000000..af78ab25 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess$ScopedAccessError.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess.class b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess.class new file mode 100644 index 00000000..f306d437 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess.java b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess.java new file mode 100644 index 00000000..e1760325 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/ScopedMemoryAccess.java @@ -0,0 +1,4138 @@ +/* + * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.foreign.MemorySegment; +import java.lang.ref.Reference; +import java.io.FileDescriptor; +import java.util.function.Supplier; + +import jdk.internal.access.JavaNioAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.foreign.AbstractMemorySegmentImpl; +import jdk.internal.foreign.MemorySessionImpl; +import jdk.internal.util.ArraysSupport; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.vector.VectorSupport; + + +/** + * This class defines low-level methods to access on-heap and off-heap memory. The methods in this class + * can be thought of as thin wrappers around methods provided in the {@link Unsafe} class. All the methods in this + * class accept one or more {@link MemorySessionImpl} parameter, which is used to validate as to whether access to memory + * can be performed in a safe fashion - more specifically, to ensure that the memory being accessed has not + * already been released (which would result in a hard VM crash). + *

+ * Accessing and releasing memory from a single thread is not problematic - after all, a given thread cannot, + * at the same time, access a memory region and free it. But ensuring correctness of memory access + * when multiple threads are involved is much trickier, as there can be cases where a thread is accessing + * a memory region while another thread is releasing it. + *

+ * This class provides tools to manage races when multiple threads are accessing and/or releasing the same memory + * session concurrently. More specifically, when a thread wants to release a memory session, it should call the + * {@link ScopedMemoryAccess#closeScope(MemorySessionImpl)} method. This method initiates thread-local handshakes with all the other VM threads, + * which are then stopped one by one. If any thread is found accessing a resource associated to the very memory session + * being closed, the handshake fails, and the session will not be closed. + *

+ * This synchronization strategy relies on the idea that accessing memory is atomic with respect to checking the + * validity of the session associated with that memory region - that is, a thread that wants to perform memory access will be + * suspended either before a liveness check or after the memory access. To ensure this atomicity, + * all methods in this class are marked with the special {@link Scoped} annotation, which is recognized by the VM, + * and used during the thread-local handshake to detect (and stop) threads performing potentially problematic memory access + * operations. Additionally, to make sure that the session object(s) of the memory being accessed is always + * reachable during an access operation, all the methods in this class add reachability fences around the underlying + * unsafe access. + *

+ * This form of synchronization allows APIs to use plain memory access without any other form of synchronization + * which might be deemed to expensive; in other words, this approach prioritizes the performance of memory access over + * that of releasing a shared memory resource. + */ +public class ScopedMemoryAccess { + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static native void registerNatives(); + static { + registerNatives(); + } + + public void closeScope(MemorySessionImpl session, ScopedAccessError error) { + closeScope0(session, error); + } + + native void closeScope0(MemorySessionImpl session, ScopedAccessError error); + + private ScopedMemoryAccess() {} + + private static final ScopedMemoryAccess theScopedMemoryAccess = new ScopedMemoryAccess(); + + public static ScopedMemoryAccess getScopedMemoryAccess() { + return theScopedMemoryAccess; + } + + public static final class ScopedAccessError extends Error { + + @SuppressWarnings("serial") + private final Supplier runtimeExceptionSupplier; + + public ScopedAccessError(Supplier runtimeExceptionSupplier) { + super("Invalid memory access", null, false, false); + this.runtimeExceptionSupplier = runtimeExceptionSupplier; + } + + static final long serialVersionUID = 1L; + + public final RuntimeException newRuntimeException() { + return runtimeExceptionSupplier.get(); + } + } + + @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) + @Retention(RetentionPolicy.RUNTIME) + @interface Scoped { } + + // bulk ops + + @ForceInline + public void copyMemory(MemorySessionImpl srcSession, MemorySessionImpl dstSession, + Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes) { + try { + copyMemoryInternal(srcSession, dstSession, srcBase, srcOffset, destBase, destOffset, bytes); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void copyMemoryInternal(MemorySessionImpl srcSession, MemorySessionImpl dstSession, + Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes) { + try { + if (srcSession != null) { + srcSession.checkValidStateRaw(); + } + if (dstSession != null) { + dstSession.checkValidStateRaw(); + } + UNSAFE.copyMemory(srcBase, srcOffset, destBase, destOffset, bytes); + } finally { + Reference.reachabilityFence(srcSession); + Reference.reachabilityFence(dstSession); + } + } + + @ForceInline + public void copySwapMemory(MemorySessionImpl srcSession, MemorySessionImpl dstSession, + Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes, long elemSize) { + try { + copySwapMemoryInternal(srcSession, dstSession, srcBase, srcOffset, destBase, destOffset, bytes, elemSize); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void copySwapMemoryInternal(MemorySessionImpl srcSession, MemorySessionImpl dstSession, + Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes, long elemSize) { + try { + if (srcSession != null) { + srcSession.checkValidStateRaw(); + } + if (dstSession != null) { + dstSession.checkValidStateRaw(); + } + UNSAFE.copySwapMemory(srcBase, srcOffset, destBase, destOffset, bytes, elemSize); + } finally { + Reference.reachabilityFence(srcSession); + Reference.reachabilityFence(dstSession); + } + } + + @ForceInline + public void setMemory(MemorySessionImpl session, Object o, long offset, long bytes, byte value) { + try { + setMemoryInternal(session, o, offset, bytes, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void setMemoryInternal(MemorySessionImpl session, Object o, long offset, long bytes, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.setMemory(o, offset, bytes, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int vectorizedMismatch(MemorySessionImpl aSession, MemorySessionImpl bSession, + Object a, long aOffset, + Object b, long bOffset, + int length, + int log2ArrayIndexScale) { + try { + return vectorizedMismatchInternal(aSession, bSession, a, aOffset, b, bOffset, length, log2ArrayIndexScale); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int vectorizedMismatchInternal(MemorySessionImpl aSession, MemorySessionImpl bSession, + Object a, long aOffset, + Object b, long bOffset, + int length, + int log2ArrayIndexScale) { + try { + if (aSession != null) { + aSession.checkValidStateRaw(); + } + if (bSession != null) { + bSession.checkValidStateRaw(); + } + return ArraysSupport.vectorizedMismatch(a, aOffset, b, bOffset, length, log2ArrayIndexScale); + } finally { + Reference.reachabilityFence(aSession); + Reference.reachabilityFence(bSession); + } + } + + @ForceInline + public boolean isLoaded(MemorySessionImpl session, long address, boolean isSync, long size) { + try { + return isLoadedInternal(session, address, isSync, size); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + public boolean isLoadedInternal(MemorySessionImpl session, long address, boolean isSync, long size) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return SharedSecrets.getJavaNioAccess().isLoaded(address, isSync, size); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void load(MemorySessionImpl session, long address, boolean isSync, long size) { + try { + loadInternal(session, address, isSync, size); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + public void loadInternal(MemorySessionImpl session, long address, boolean isSync, long size) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + SharedSecrets.getJavaNioAccess().load(address, isSync, size); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void unload(MemorySessionImpl session, long address, boolean isSync, long size) { + try { + unloadInternal(session, address, isSync, size); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + public void unloadInternal(MemorySessionImpl session, long address, boolean isSync, long size) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + SharedSecrets.getJavaNioAccess().unload(address, isSync, size); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void force(MemorySessionImpl session, FileDescriptor fd, long address, boolean isSync, long index, long length) { + try { + forceInternal(session, fd, address, isSync, index, length); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + public void forceInternal(MemorySessionImpl session, FileDescriptor fd, long address, boolean isSync, long index, long length) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + SharedSecrets.getJavaNioAccess().force(fd, address, isSync, index, length); + } finally { + Reference.reachabilityFence(session); + } + } + + // MemorySegment vector access ops + + @ForceInline + public static + , E, S extends VectorSupport.VectorSpecies> + V loadFromMemorySegment(Class vmClass, Class e, int length, + AbstractMemorySegmentImpl msp, long offset, + S s, + VectorSupport.LoadOperation defaultImpl) { + + try { + return loadFromMemorySegmentScopedInternal( + msp.sessionImpl(), + vmClass, e, length, + msp, offset, + s, + defaultImpl); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @Scoped + @ForceInline + private static + , E, S extends VectorSupport.VectorSpecies> + V loadFromMemorySegmentScopedInternal(MemorySessionImpl session, + Class vmClass, Class e, int length, + AbstractMemorySegmentImpl msp, long offset, + S s, + VectorSupport.LoadOperation defaultImpl) { + try { + session.checkValidStateRaw(); + + return VectorSupport.load(vmClass, e, length, + msp.unsafeGetBase(), msp.unsafeGetOffset() + offset, true, + msp, offset, s, + defaultImpl); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public static + , E, S extends VectorSupport.VectorSpecies, + M extends VectorSupport.VectorMask> + V loadFromMemorySegmentMasked(Class vmClass, Class maskClass, Class e, + int length, AbstractMemorySegmentImpl msp, long offset, M m, S s, int offsetInRange, + VectorSupport.LoadVectorMaskedOperation defaultImpl) { + + try { + return loadFromMemorySegmentMaskedScopedInternal( + msp.sessionImpl(), + vmClass, maskClass, e, length, + msp, offset, m, + s, offsetInRange, + defaultImpl); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @Scoped + @ForceInline + private static + , E, S extends VectorSupport.VectorSpecies, + M extends VectorSupport.VectorMask> + V loadFromMemorySegmentMaskedScopedInternal(MemorySessionImpl session, Class vmClass, + Class maskClass, Class e, int length, + AbstractMemorySegmentImpl msp, long offset, M m, + S s, int offsetInRange, + VectorSupport.LoadVectorMaskedOperation defaultImpl) { + try { + session.checkValidStateRaw(); + + return VectorSupport.loadMasked(vmClass, maskClass, e, length, + msp.unsafeGetBase(), msp.unsafeGetOffset() + offset, true, m, offsetInRange, + msp, offset, s, + defaultImpl); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public static + , E> + void storeIntoMemorySegment(Class vmClass, Class e, int length, + V v, + AbstractMemorySegmentImpl msp, long offset, + VectorSupport.StoreVectorOperation defaultImpl) { + + try { + storeIntoMemorySegmentScopedInternal( + msp.sessionImpl(), + vmClass, e, length, + v, + msp, offset, + defaultImpl); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @Scoped + @ForceInline + private static + , E> + void storeIntoMemorySegmentScopedInternal(MemorySessionImpl session, + Class vmClass, Class e, int length, + V v, + AbstractMemorySegmentImpl msp, long offset, + VectorSupport.StoreVectorOperation defaultImpl) { + try { + session.checkValidStateRaw(); + + VectorSupport.store(vmClass, e, length, + msp.unsafeGetBase(), msp.unsafeGetOffset() + offset, true, + v, + msp, offset, + defaultImpl); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public static + , E, M extends VectorSupport.VectorMask> + void storeIntoMemorySegmentMasked(Class vmClass, Class maskClass, Class e, + int length, V v, M m, + AbstractMemorySegmentImpl msp, long offset, + VectorSupport.StoreVectorMaskedOperation defaultImpl) { + + try { + storeIntoMemorySegmentMaskedScopedInternal( + msp.sessionImpl(), + vmClass, maskClass, e, length, + v, m, + msp, offset, + defaultImpl); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @Scoped + @ForceInline + private static + , E, M extends VectorSupport.VectorMask> + void storeIntoMemorySegmentMaskedScopedInternal(MemorySessionImpl session, + Class vmClass, Class maskClass, + Class e, int length, V v, M m, + AbstractMemorySegmentImpl msp, long offset, + VectorSupport.StoreVectorMaskedOperation defaultImpl) { + try { + session.checkValidStateRaw(); + + VectorSupport.storeMasked(vmClass, maskClass, e, length, + msp.unsafeGetBase(), msp.unsafeGetOffset() + offset, true, + v, m, + msp, offset, + defaultImpl); + } finally { + Reference.reachabilityFence(session); + } + } + + // typed-ops here + + // Note: all the accessor methods defined below take advantage of argument type profiling + // (see src/hotspot/share/oops/methodData.cpp) which greatly enhances performance when the same accessor + // method is used repeatedly with different 'base' objects. + @ForceInline + public byte getByte(MemorySessionImpl session, Object base, long offset) { + try { + return getByteInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getByteInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getByte(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putByte(MemorySessionImpl session, Object base, long offset, byte value) { + try { + putByteInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putByteInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putByte(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + + @ForceInline + public byte getByteVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getByteVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getByteVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getByteVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putByteVolatile(MemorySessionImpl session, Object base, long offset, byte value) { + try { + putByteVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putByteVolatileInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putByteVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getByteAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getByteAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getByteAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getByteAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putByteRelease(MemorySessionImpl session, Object base, long offset, byte value) { + try { + putByteReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putByteReleaseInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putByteRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getByteOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getByteOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getByteOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getByteOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putByteOpaque(MemorySessionImpl session, Object base, long offset, byte value) { + try { + putByteOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putByteOpaqueInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putByteOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndAddByte(MemorySessionImpl session, Object base, long offset, byte delta) { + try { + return getAndAddByteInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndAddByteInternal(MemorySessionImpl session, Object base, long offset, byte delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddByte(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndAddByteAcquire(MemorySessionImpl session, Object base, long offset, byte delta) { + try { + return getAndAddByteAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndAddByteAcquireInternal(MemorySessionImpl session, Object base, long offset, byte delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddByteAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndAddByteRelease(MemorySessionImpl session, Object base, long offset, byte delta) { + try { + return getAndAddByteReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndAddByteReleaseInternal(MemorySessionImpl session, Object base, long offset, byte delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddByteRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseOrByte(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseOrByteInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseOrByteInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrByte(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseOrByteAcquire(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseOrByteAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseOrByteAcquireInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrByteAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseOrByteRelease(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseOrByteReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseOrByteReleaseInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrByteRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseAndByte(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseAndByteInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseAndByteInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndByte(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseAndByteAcquire(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseAndByteAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseAndByteAcquireInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndByteAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseAndByteRelease(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseAndByteReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseAndByteReleaseInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndByteRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseXorByte(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseXorByteInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseXorByteInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorByte(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseXorByteAcquire(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseXorByteAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseXorByteAcquireInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorByteAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public byte getAndBitwiseXorByteRelease(MemorySessionImpl session, Object base, long offset, byte value) { + try { + return getAndBitwiseXorByteReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private byte getAndBitwiseXorByteReleaseInternal(MemorySessionImpl session, Object base, long offset, byte value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorByteRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public short getShort(MemorySessionImpl session, Object base, long offset) { + try { + return getShortInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getShortInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getShort(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putShort(MemorySessionImpl session, Object base, long offset, short value) { + try { + putShortInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putShortInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putShort(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getShortUnaligned(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + return getShortUnalignedInternal(session, base, offset, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getShortUnalignedInternal(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getShortUnaligned(base, offset, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putShortUnaligned(MemorySessionImpl session, Object base, long offset, short value, boolean be) { + try { + putShortUnalignedInternal(session, base, offset, value, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putShortUnalignedInternal(MemorySessionImpl session, Object base, long offset, short value, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putShortUnaligned(base, offset, value, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getShortVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getShortVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getShortVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getShortVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putShortVolatile(MemorySessionImpl session, Object base, long offset, short value) { + try { + putShortVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putShortVolatileInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putShortVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getShortAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getShortAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getShortAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getShortAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putShortRelease(MemorySessionImpl session, Object base, long offset, short value) { + try { + putShortReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putShortReleaseInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putShortRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getShortOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getShortOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getShortOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getShortOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putShortOpaque(MemorySessionImpl session, Object base, long offset, short value) { + try { + putShortOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putShortOpaqueInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putShortOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndAddShort(MemorySessionImpl session, Object base, long offset, short delta) { + try { + return getAndAddShortInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndAddShortInternal(MemorySessionImpl session, Object base, long offset, short delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddShort(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndAddShortAcquire(MemorySessionImpl session, Object base, long offset, short delta) { + try { + return getAndAddShortAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndAddShortAcquireInternal(MemorySessionImpl session, Object base, long offset, short delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddShortAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndAddShortRelease(MemorySessionImpl session, Object base, long offset, short delta) { + try { + return getAndAddShortReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndAddShortReleaseInternal(MemorySessionImpl session, Object base, long offset, short delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddShortRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseOrShort(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseOrShortInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseOrShortInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrShort(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseOrShortAcquire(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseOrShortAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseOrShortAcquireInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrShortAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseOrShortRelease(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseOrShortReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseOrShortReleaseInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrShortRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseAndShort(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseAndShortInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseAndShortInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndShort(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseAndShortAcquire(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseAndShortAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseAndShortAcquireInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndShortAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseAndShortRelease(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseAndShortReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseAndShortReleaseInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndShortRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseXorShort(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseXorShortInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseXorShortInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorShort(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseXorShortAcquire(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseXorShortAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseXorShortAcquireInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorShortAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public short getAndBitwiseXorShortRelease(MemorySessionImpl session, Object base, long offset, short value) { + try { + return getAndBitwiseXorShortReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private short getAndBitwiseXorShortReleaseInternal(MemorySessionImpl session, Object base, long offset, short value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorShortRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public char getChar(MemorySessionImpl session, Object base, long offset) { + try { + return getCharInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getCharInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getChar(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putChar(MemorySessionImpl session, Object base, long offset, char value) { + try { + putCharInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putCharInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putChar(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getCharUnaligned(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + return getCharUnalignedInternal(session, base, offset, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getCharUnalignedInternal(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getCharUnaligned(base, offset, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putCharUnaligned(MemorySessionImpl session, Object base, long offset, char value, boolean be) { + try { + putCharUnalignedInternal(session, base, offset, value, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putCharUnalignedInternal(MemorySessionImpl session, Object base, long offset, char value, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putCharUnaligned(base, offset, value, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getCharVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getCharVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getCharVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getCharVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putCharVolatile(MemorySessionImpl session, Object base, long offset, char value) { + try { + putCharVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putCharVolatileInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putCharVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getCharAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getCharAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getCharAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getCharAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putCharRelease(MemorySessionImpl session, Object base, long offset, char value) { + try { + putCharReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putCharReleaseInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putCharRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getCharOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getCharOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getCharOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getCharOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putCharOpaque(MemorySessionImpl session, Object base, long offset, char value) { + try { + putCharOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putCharOpaqueInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putCharOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndAddChar(MemorySessionImpl session, Object base, long offset, char delta) { + try { + return getAndAddCharInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndAddCharInternal(MemorySessionImpl session, Object base, long offset, char delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddChar(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndAddCharAcquire(MemorySessionImpl session, Object base, long offset, char delta) { + try { + return getAndAddCharAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndAddCharAcquireInternal(MemorySessionImpl session, Object base, long offset, char delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddCharAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndAddCharRelease(MemorySessionImpl session, Object base, long offset, char delta) { + try { + return getAndAddCharReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndAddCharReleaseInternal(MemorySessionImpl session, Object base, long offset, char delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddCharRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseOrChar(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseOrCharInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseOrCharInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrChar(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseOrCharAcquire(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseOrCharAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseOrCharAcquireInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrCharAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseOrCharRelease(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseOrCharReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseOrCharReleaseInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrCharRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseAndChar(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseAndCharInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseAndCharInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndChar(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseAndCharAcquire(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseAndCharAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseAndCharAcquireInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndCharAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseAndCharRelease(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseAndCharReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseAndCharReleaseInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndCharRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseXorChar(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseXorCharInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseXorCharInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorChar(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseXorCharAcquire(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseXorCharAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseXorCharAcquireInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorCharAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public char getAndBitwiseXorCharRelease(MemorySessionImpl session, Object base, long offset, char value) { + try { + return getAndBitwiseXorCharReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private char getAndBitwiseXorCharReleaseInternal(MemorySessionImpl session, Object base, long offset, char value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorCharRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public int getInt(MemorySessionImpl session, Object base, long offset) { + try { + return getIntInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getIntInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getInt(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putInt(MemorySessionImpl session, Object base, long offset, int value) { + try { + putIntInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putIntInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putInt(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getIntUnaligned(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + return getIntUnalignedInternal(session, base, offset, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getIntUnalignedInternal(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getIntUnaligned(base, offset, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putIntUnaligned(MemorySessionImpl session, Object base, long offset, int value, boolean be) { + try { + putIntUnalignedInternal(session, base, offset, value, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putIntUnalignedInternal(MemorySessionImpl session, Object base, long offset, int value, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putIntUnaligned(base, offset, value, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getIntVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getIntVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getIntVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getIntVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putIntVolatile(MemorySessionImpl session, Object base, long offset, int value) { + try { + putIntVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putIntVolatileInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putIntVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getIntAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getIntAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getIntAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getIntAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putIntRelease(MemorySessionImpl session, Object base, long offset, int value) { + try { + putIntReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putIntRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getIntOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getIntOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getIntOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getIntOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putIntOpaque(MemorySessionImpl session, Object base, long offset, int value) { + try { + putIntOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putIntOpaqueInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putIntOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public boolean compareAndSetInt(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return compareAndSetIntInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean compareAndSetIntInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndSetInt(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int compareAndExchangeInt(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return compareAndExchangeIntInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int compareAndExchangeIntInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeInt(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int compareAndExchangeIntAcquire(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return compareAndExchangeIntAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int compareAndExchangeIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeIntAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int compareAndExchangeIntRelease(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return compareAndExchangeIntReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int compareAndExchangeIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeIntRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetIntPlain(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return weakCompareAndSetIntPlainInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetIntPlainInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetIntPlain(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetInt(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return weakCompareAndSetIntInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetIntInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetInt(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetIntAcquire(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return weakCompareAndSetIntAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetIntAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetIntRelease(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + return weakCompareAndSetIntReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int expected, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetIntRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndSetInt(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndSetIntInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndSetIntInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetInt(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndSetIntAcquire(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndSetIntAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndSetIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetIntAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndSetIntRelease(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndSetIntReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndSetIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetIntRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndAddInt(MemorySessionImpl session, Object base, long offset, int delta) { + try { + return getAndAddIntInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndAddIntInternal(MemorySessionImpl session, Object base, long offset, int delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddInt(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndAddIntAcquire(MemorySessionImpl session, Object base, long offset, int delta) { + try { + return getAndAddIntAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndAddIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddIntAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndAddIntRelease(MemorySessionImpl session, Object base, long offset, int delta) { + try { + return getAndAddIntReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndAddIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddIntRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseOrInt(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseOrIntInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseOrIntInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrInt(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseOrIntAcquire(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseOrIntAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseOrIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrIntAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseOrIntRelease(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseOrIntReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseOrIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrIntRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseAndInt(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseAndIntInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseAndIntInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndInt(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseAndIntAcquire(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseAndIntAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseAndIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndIntAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseAndIntRelease(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseAndIntReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseAndIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndIntRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseXorInt(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseXorIntInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseXorIntInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorInt(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseXorIntAcquire(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseXorIntAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseXorIntAcquireInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorIntAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public int getAndBitwiseXorIntRelease(MemorySessionImpl session, Object base, long offset, int value) { + try { + return getAndBitwiseXorIntReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private int getAndBitwiseXorIntReleaseInternal(MemorySessionImpl session, Object base, long offset, int value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorIntRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public long getLong(MemorySessionImpl session, Object base, long offset) { + try { + return getLongInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getLongInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getLong(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putLong(MemorySessionImpl session, Object base, long offset, long value) { + try { + putLongInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putLongInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putLong(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getLongUnaligned(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + return getLongUnalignedInternal(session, base, offset, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getLongUnalignedInternal(MemorySessionImpl session, Object base, long offset, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getLongUnaligned(base, offset, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putLongUnaligned(MemorySessionImpl session, Object base, long offset, long value, boolean be) { + try { + putLongUnalignedInternal(session, base, offset, value, be); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putLongUnalignedInternal(MemorySessionImpl session, Object base, long offset, long value, boolean be) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putLongUnaligned(base, offset, value, be); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getLongVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getLongVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getLongVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getLongVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putLongVolatile(MemorySessionImpl session, Object base, long offset, long value) { + try { + putLongVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putLongVolatileInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putLongVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getLongAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getLongAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getLongAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getLongAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putLongRelease(MemorySessionImpl session, Object base, long offset, long value) { + try { + putLongReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putLongRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getLongOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getLongOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getLongOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getLongOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putLongOpaque(MemorySessionImpl session, Object base, long offset, long value) { + try { + putLongOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putLongOpaqueInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putLongOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public boolean compareAndSetLong(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return compareAndSetLongInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean compareAndSetLongInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndSetLong(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long compareAndExchangeLong(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return compareAndExchangeLongInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long compareAndExchangeLongInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeLong(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long compareAndExchangeLongAcquire(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return compareAndExchangeLongAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long compareAndExchangeLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeLongAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long compareAndExchangeLongRelease(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return compareAndExchangeLongReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long compareAndExchangeLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeLongRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetLongPlain(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return weakCompareAndSetLongPlainInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetLongPlainInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetLongPlain(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetLong(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return weakCompareAndSetLongInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetLongInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetLong(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetLongAcquire(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return weakCompareAndSetLongAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetLongAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetLongRelease(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + return weakCompareAndSetLongReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long expected, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetLongRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndSetLong(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndSetLongInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndSetLongInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetLong(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndSetLongAcquire(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndSetLongAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndSetLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetLongAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndSetLongRelease(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndSetLongReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndSetLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetLongRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndAddLong(MemorySessionImpl session, Object base, long offset, long delta) { + try { + return getAndAddLongInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndAddLongInternal(MemorySessionImpl session, Object base, long offset, long delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddLong(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndAddLongAcquire(MemorySessionImpl session, Object base, long offset, long delta) { + try { + return getAndAddLongAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndAddLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddLongAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndAddLongRelease(MemorySessionImpl session, Object base, long offset, long delta) { + try { + return getAndAddLongReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndAddLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddLongRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseOrLong(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseOrLongInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseOrLongInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrLong(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseOrLongAcquire(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseOrLongAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseOrLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrLongAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseOrLongRelease(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseOrLongReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseOrLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseOrLongRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseAndLong(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseAndLongInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseAndLongInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndLong(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseAndLongAcquire(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseAndLongAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseAndLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndLongAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseAndLongRelease(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseAndLongReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseAndLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseAndLongRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseXorLong(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseXorLongInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseXorLongInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorLong(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseXorLongAcquire(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseXorLongAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseXorLongAcquireInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorLongAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public long getAndBitwiseXorLongRelease(MemorySessionImpl session, Object base, long offset, long value) { + try { + return getAndBitwiseXorLongReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private long getAndBitwiseXorLongReleaseInternal(MemorySessionImpl session, Object base, long offset, long value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndBitwiseXorLongRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public float getFloat(MemorySessionImpl session, Object base, long offset) { + try { + return getFloatInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getFloatInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getFloat(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putFloat(MemorySessionImpl session, Object base, long offset, float value) { + try { + putFloatInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putFloatInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putFloat(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + + @ForceInline + public float getFloatVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getFloatVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getFloatVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getFloatVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putFloatVolatile(MemorySessionImpl session, Object base, long offset, float value) { + try { + putFloatVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putFloatVolatileInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putFloatVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getFloatAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getFloatAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getFloatAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getFloatAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putFloatRelease(MemorySessionImpl session, Object base, long offset, float value) { + try { + putFloatReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putFloatReleaseInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putFloatRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getFloatOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getFloatOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getFloatOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getFloatOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putFloatOpaque(MemorySessionImpl session, Object base, long offset, float value) { + try { + putFloatOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putFloatOpaqueInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putFloatOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public boolean compareAndSetFloat(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return compareAndSetFloatInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean compareAndSetFloatInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndSetFloat(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float compareAndExchangeFloat(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return compareAndExchangeFloatInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float compareAndExchangeFloatInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeFloat(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float compareAndExchangeFloatAcquire(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return compareAndExchangeFloatAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float compareAndExchangeFloatAcquireInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeFloatAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float compareAndExchangeFloatRelease(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return compareAndExchangeFloatReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float compareAndExchangeFloatReleaseInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeFloatRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetFloatPlain(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return weakCompareAndSetFloatPlainInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetFloatPlainInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetFloatPlain(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetFloat(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return weakCompareAndSetFloatInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetFloatInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetFloat(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetFloatAcquire(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return weakCompareAndSetFloatAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetFloatAcquireInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetFloatAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetFloatRelease(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + return weakCompareAndSetFloatReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetFloatReleaseInternal(MemorySessionImpl session, Object base, long offset, float expected, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetFloatRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getAndSetFloat(MemorySessionImpl session, Object base, long offset, float value) { + try { + return getAndSetFloatInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getAndSetFloatInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetFloat(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getAndSetFloatAcquire(MemorySessionImpl session, Object base, long offset, float value) { + try { + return getAndSetFloatAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getAndSetFloatAcquireInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetFloatAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getAndSetFloatRelease(MemorySessionImpl session, Object base, long offset, float value) { + try { + return getAndSetFloatReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getAndSetFloatReleaseInternal(MemorySessionImpl session, Object base, long offset, float value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetFloatRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getAndAddFloat(MemorySessionImpl session, Object base, long offset, float delta) { + try { + return getAndAddFloatInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getAndAddFloatInternal(MemorySessionImpl session, Object base, long offset, float delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddFloat(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getAndAddFloatAcquire(MemorySessionImpl session, Object base, long offset, float delta) { + try { + return getAndAddFloatAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getAndAddFloatAcquireInternal(MemorySessionImpl session, Object base, long offset, float delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddFloatAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public float getAndAddFloatRelease(MemorySessionImpl session, Object base, long offset, float delta) { + try { + return getAndAddFloatReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private float getAndAddFloatReleaseInternal(MemorySessionImpl session, Object base, long offset, float delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddFloatRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getDouble(MemorySessionImpl session, Object base, long offset) { + try { + return getDoubleInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getDoubleInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getDouble(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putDouble(MemorySessionImpl session, Object base, long offset, double value) { + try { + putDoubleInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putDoubleInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putDouble(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + + @ForceInline + public double getDoubleVolatile(MemorySessionImpl session, Object base, long offset) { + try { + return getDoubleVolatileInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getDoubleVolatileInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getDoubleVolatile(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putDoubleVolatile(MemorySessionImpl session, Object base, long offset, double value) { + try { + putDoubleVolatileInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putDoubleVolatileInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putDoubleVolatile(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getDoubleAcquire(MemorySessionImpl session, Object base, long offset) { + try { + return getDoubleAcquireInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getDoubleAcquireInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getDoubleAcquire(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public void putDoubleRelease(MemorySessionImpl session, Object base, long offset, double value) { + try { + putDoubleReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putDoubleReleaseInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putDoubleRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getDoubleOpaque(MemorySessionImpl session, Object base, long offset) { + try { + return getDoubleOpaqueInternal(session, base, offset); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getDoubleOpaqueInternal(MemorySessionImpl session, Object base, long offset) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getDoubleOpaque(base, offset); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public void putDoubleOpaque(MemorySessionImpl session, Object base, long offset, double value) { + try { + putDoubleOpaqueInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private void putDoubleOpaqueInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + UNSAFE.putDoubleOpaque(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + @ForceInline + public boolean compareAndSetDouble(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return compareAndSetDoubleInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean compareAndSetDoubleInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndSetDouble(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double compareAndExchangeDouble(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return compareAndExchangeDoubleInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double compareAndExchangeDoubleInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeDouble(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double compareAndExchangeDoubleAcquire(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return compareAndExchangeDoubleAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double compareAndExchangeDoubleAcquireInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeDoubleAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double compareAndExchangeDoubleRelease(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return compareAndExchangeDoubleReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double compareAndExchangeDoubleReleaseInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.compareAndExchangeDoubleRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetDoublePlain(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return weakCompareAndSetDoublePlainInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetDoublePlainInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetDoublePlain(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetDouble(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return weakCompareAndSetDoubleInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetDoubleInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetDouble(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetDoubleAcquire(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return weakCompareAndSetDoubleAcquireInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetDoubleAcquireInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetDoubleAcquire(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public boolean weakCompareAndSetDoubleRelease(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + return weakCompareAndSetDoubleReleaseInternal(session, base, offset, expected, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private boolean weakCompareAndSetDoubleReleaseInternal(MemorySessionImpl session, Object base, long offset, double expected, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.weakCompareAndSetDoubleRelease(base, offset, expected, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getAndSetDouble(MemorySessionImpl session, Object base, long offset, double value) { + try { + return getAndSetDoubleInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getAndSetDoubleInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetDouble(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getAndSetDoubleAcquire(MemorySessionImpl session, Object base, long offset, double value) { + try { + return getAndSetDoubleAcquireInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getAndSetDoubleAcquireInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetDoubleAcquire(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getAndSetDoubleRelease(MemorySessionImpl session, Object base, long offset, double value) { + try { + return getAndSetDoubleReleaseInternal(session, base, offset, value); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getAndSetDoubleReleaseInternal(MemorySessionImpl session, Object base, long offset, double value) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndSetDoubleRelease(base, offset, value); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getAndAddDouble(MemorySessionImpl session, Object base, long offset, double delta) { + try { + return getAndAddDoubleInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getAndAddDoubleInternal(MemorySessionImpl session, Object base, long offset, double delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddDouble(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getAndAddDoubleAcquire(MemorySessionImpl session, Object base, long offset, double delta) { + try { + return getAndAddDoubleAcquireInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getAndAddDoubleAcquireInternal(MemorySessionImpl session, Object base, long offset, double delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddDoubleAcquire(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + + @ForceInline + public double getAndAddDoubleRelease(MemorySessionImpl session, Object base, long offset, double delta) { + try { + return getAndAddDoubleReleaseInternal(session, base, offset, delta); + } catch (ScopedAccessError ex) { + throw ex.newRuntimeException(); + } + } + + @ForceInline @Scoped + private double getAndAddDoubleReleaseInternal(MemorySessionImpl session, Object base, long offset, double delta) { + try { + if (session != null) { + session.checkValidStateRaw(); + } + return UNSAFE.getAndAddDoubleRelease(base, offset, delta); + } finally { + Reference.reachabilityFence(session); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/misc/Signal$1.class b/tests/test_data/std/jdk/internal/misc/Signal$1.class new file mode 100644 index 00000000..d9d6964f Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/Signal$1.class differ diff --git a/tests/test_data/std/jdk/internal/misc/Signal$Handler.class b/tests/test_data/std/jdk/internal/misc/Signal$Handler.class new file mode 100644 index 00000000..363474a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/Signal$Handler.class differ diff --git a/tests/test_data/std/jdk/internal/misc/Signal$NativeHandler.class b/tests/test_data/std/jdk/internal/misc/Signal$NativeHandler.class new file mode 100644 index 00000000..3b1cc578 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/Signal$NativeHandler.class differ diff --git a/tests/test_data/std/jdk/internal/misc/Signal.class b/tests/test_data/std/jdk/internal/misc/Signal.class new file mode 100644 index 00000000..c07c6693 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/Signal.class differ diff --git a/tests/test_data/std/jdk/internal/misc/Signal.java b/tests/test_data/std/jdk/internal/misc/Signal.java new file mode 100644 index 00000000..9cd9ea0b --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/Signal.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.util.Hashtable; +import java.util.Objects; + +/** + * This class provides ANSI/ISO C signal support. A Java program can register + * signal handlers for the current process. There are two restrictions: + *

    + *
  • + * Java code cannot register a handler for signals that are already used + * by the Java VM implementation. The Signal.handle + * function raises an IllegalArgumentException if such an attempt + * is made. + *
  • + * When Signal.handle is called, the VM internally registers a + * special C signal handler. There is no way to force the Java signal handler + * to run synchronously before the C signal handler returns. Instead, when the + * VM receives a signal, the special C signal handler creates a new thread + * (at priority Thread.MAX_PRIORITY) to + * run the registered Java signal handler. The C signal handler immediately + * returns. Note that because the Java signal handler runs in a newly created + * thread, it may not actually be executed until some time after the C signal + * handler returns. + *
+ *

+ * Signal objects are created based on their names. For example: + *

+ * new Signal("INT");
+ * 
+ * constructs a signal object corresponding to SIGINT, which is + * typically produced when the user presses Ctrl-C at the command line. + * The Signal constructor throws IllegalArgumentException + * when it is passed an unknown signal. + *

+ * This is an example of how Java code handles SIGINT: + *

+ * Signal.Handler handler = new Signal.Handler () {
+ *     public void handle(Signal sig) {
+ *       ... // handle SIGINT
+ *     }
+ * };
+ * Signal.handle(new Signal("INT"), handler);
+ * 
+ * + * @since 9 + */ +public final class Signal { + private static final Hashtable handlers = new Hashtable<>(4); + private static final Hashtable signals = new Hashtable<>(4); + + private int number; + private String name; + + /* Returns the signal number */ + public int getNumber() { + return number; + } + + /** + * Returns the signal name. + * + * @return the name of the signal. + * @see jdk.internal.misc.Signal#Signal(String name) + */ + public String getName() { + return name; + } + + /** + * Compares the equality of two Signal objects. + * + * @param obj the object to compare with. + * @return whether two Signal objects are equal. + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Signal other) { + return name.equals(other.name) && (number == other.number); + } + return false; + } + + /** + * Returns a hashcode for this Signal. + * + * @return a hash code value for this object. + */ + public int hashCode() { + return number; + } + + /** + * Returns a string representation of this signal. For example, "SIGINT" + * for an object constructed using new Signal ("INT"). + * + * @return a string representation of the signal + */ + public String toString() { + return "SIG" + name; + } + + /** + * Constructs a signal from its name. + * + * @param name the name of the signal. + * @exception IllegalArgumentException unknown signal + * @see jdk.internal.misc.Signal#getName() + */ + public Signal(String name) { + Objects.requireNonNull(name, "name"); + // Signal names are the short names; + // the "SIG" prefix is not used for compatibility with previous JDKs + if (name.startsWith("SIG")) { + throw new IllegalArgumentException("Unknown signal: " + name); + } + this.name = name; + number = findSignal0(name); + if (number < 0) { + throw new IllegalArgumentException("Unknown signal: " + name); + } + } + + /** + * Registers a signal handler. + * + * @param sig a signal + * @param handler the handler to be registered with the given signal. + * @return the old handler + * @exception IllegalArgumentException the signal is in use by the VM + * @see jdk.internal.misc.Signal#raise(Signal sig) + * @see jdk.internal.misc.Signal.Handler + * @see jdk.internal.misc.Signal.Handler#SIG_DFL + * @see jdk.internal.misc.Signal.Handler#SIG_IGN + */ + public static synchronized Signal.Handler handle(Signal sig, + Signal.Handler handler) + throws IllegalArgumentException { + Objects.requireNonNull(sig, "sig"); + Objects.requireNonNull(handler, "handler"); + long newH = (handler instanceof NativeHandler) ? + ((NativeHandler)handler).getHandler() : 2; + long oldH = handle0(sig.number, newH); + if (oldH == -1) { + throw new IllegalArgumentException + ("Signal already used by VM or OS: " + sig); + } + signals.put(sig.number, sig); + Signal.Handler oldHandler; + if (newH == 2) { + oldHandler = handlers.put(sig, handler); + } else { + oldHandler = handlers.remove(sig); + } + if (oldH == 0) { + return Signal.Handler.SIG_DFL; + } else if (oldH == 1) { + return Signal.Handler.SIG_IGN; + } else if (oldH == 2) { + return oldHandler; + } else { + return new NativeHandler(oldH); + } + } + + /** + * Raises a signal in the current process. + * + * @param sig a signal + * @see jdk.internal.misc.Signal#handle(Signal sig, Signal.Handler handler) + */ + public static void raise(Signal sig) throws IllegalArgumentException { + Objects.requireNonNull(sig, "sig"); + if (handlers.get(sig) == null) { + throw new IllegalArgumentException("Unhandled signal: " + sig); + } + raise0(sig.number); + } + + /* Called by the VM to execute Java signal handlers. */ + private static void dispatch(final int number) { + final Signal sig = signals.get(number); + final Signal.Handler handler = handlers.get(sig); + + Runnable runnable = new Runnable () { + public void run() { + // Don't bother to reset the priority. Signal handler will + // run at maximum priority inherited from the VM signal + // dispatch thread. + // Thread.currentThread().setPriority(Thread.NORM_PRIORITY); + handler.handle(sig); + } + }; + if (handler != null) { + new Thread(null, runnable, sig + " handler", 0, false).start(); + } + } + + /* Find the signal number, given a name. Returns -1 for unknown signals. */ + private static native int findSignal0(String sigName); + /* Registers a native signal handler, and returns the old handler. + * Handler values: + * 0 default handler + * 1 ignore the signal + * 2 call back to Signal.dispatch + * other arbitrary native signal handlers + */ + private static native long handle0(int sig, long nativeH); + /* Raise a given signal number */ + private static native void raise0(int sig); + + /** + * This is the signal handler interface expected in Signal.handle. + */ + public interface Handler { + + /** + * The default signal handler + */ + public static final Signal.Handler SIG_DFL = new NativeHandler(0); + /** + * Ignore the signal + */ + public static final Signal.Handler SIG_IGN = new NativeHandler(1); + + /** + * Handle the given signal + * + * @param sig a signal object + */ + public void handle(Signal sig); + } + + + /* + * A package-private class implementing a signal handler in native code. + */ + static final class NativeHandler implements Signal.Handler { + + private final long handler; + + long getHandler() { + return handler; + } + + NativeHandler(long handler) { + this.handler = handler; + } + + public void handle(Signal sig) { + throw new UnsupportedOperationException("invoking native signal handle not supported"); + } + + public String toString() { + return this == SIG_DFL ? "SIG_DFL" : + (this == SIG_IGN ? "SIG_IGN" : super.toString()); + } + } + +} diff --git a/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal$1.class b/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal$1.class new file mode 100644 index 00000000..9c2d7253 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal$1.class differ diff --git a/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal.class b/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal.class new file mode 100644 index 00000000..ad0ae75d Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal.class differ diff --git a/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal.java b/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal.java new file mode 100644 index 00000000..eeb1a77e --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/TerminatingThreadLocal.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.misc; + +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; + +/** + * A per-carrier-thread-local variable that is notified when a thread terminates and + * it has been initialized in the terminating carrier thread or a virtual thread + * that had the terminating carrier thread as its carrier thread (even if it was + * initialized with a null value). + */ +public class TerminatingThreadLocal extends CarrierThreadLocal { + + @Override + public void set(T value) { + super.set(value); + register(this); + } + + @Override + public void remove() { + super.remove(); + unregister(this); + } + + /** + * Invoked by a thread when terminating and this thread-local has an associated + * value for the terminating thread (even if that value is null), so that any + * native resources maintained by the value can be released. + * + * @param value current thread's value of this thread-local variable + * (may be null but only if null value was explicitly initialized) + */ + protected void threadTerminated(T value) { + } + + // following methods and field are implementation details and should only be + // called from the corresponding code int Thread/ThreadLocal class. + + /** + * Invokes the TerminatingThreadLocal's {@link #threadTerminated()} method + * on all instances registered in current thread. + */ + public static void threadTerminated() { + for (TerminatingThreadLocal ttl : REGISTRY.get()) { + ttl._threadTerminated(); + } + } + + private void _threadTerminated() { threadTerminated(get()); } + + /** + * Register given TerminatingThreadLocal + * + * @param tl the ThreadLocal to register + */ + public static void register(TerminatingThreadLocal tl) { + REGISTRY.get().add(tl); + } + + /** + * Unregister given TerminatingThreadLocal + * + * @param tl the ThreadLocal to unregister + */ + private static void unregister(TerminatingThreadLocal tl) { + REGISTRY.get().remove(tl); + } + + /** + * a per-carrier-thread registry of TerminatingThreadLocal(s) that have been registered + * but later not unregistered in a particular carrier-thread. + */ + public static final CarrierThreadLocal>> REGISTRY = + new CarrierThreadLocal<>() { + @Override + protected Collection> initialValue() { + return Collections.newSetFromMap(new IdentityHashMap<>(4)); + } + }; +} diff --git a/tests/test_data/std/jdk/internal/misc/ThreadFlock$ThreadContainerImpl.class b/tests/test_data/std/jdk/internal/misc/ThreadFlock$ThreadContainerImpl.class new file mode 100644 index 00000000..7995451d Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ThreadFlock$ThreadContainerImpl.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ThreadFlock.class b/tests/test_data/std/jdk/internal/misc/ThreadFlock.class new file mode 100644 index 00000000..8a7fb810 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ThreadFlock.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ThreadFlock.java b/tests/test_data/std/jdk/internal/misc/ThreadFlock.java new file mode 100644 index 00000000..e9c75d4a --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/ThreadFlock.java @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.misc; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.time.Duration; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.StructureViolationException; +import java.util.concurrent.locks.LockSupport; +import java.util.stream.Stream; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.vm.ScopedValueContainer; +import jdk.internal.vm.ThreadContainer; +import jdk.internal.vm.ThreadContainers; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +/** + * A grouping of threads that typically run closely related tasks. Threads started + * in a flock remain in the flock until they terminate. + * + *

ThreadFlock defines the {@link #open(String) open} method to open a new flock, + * the {@link #start(Thread) start} method to start a thread in the flock, and the + * {@link #close() close} method to close the flock. The {@code close} waits for all + * threads in the flock to finish. The {@link #awaitAll() awaitAll} method may be used + * to wait for all threads to finish without closing the flock. The {@link #wakeup()} + * method will cause {@code awaitAll} method to complete early, which can be used to + * support cancellation in higher-level APIs. ThreadFlock also defines the {@link + * #shutdown() shutdown} method to prevent new threads from starting while allowing + * existing threads in the flock to continue. + * + *

Thread flocks are intended to be used in a structured manner. The + * thread that opens a new flock is the {@link #owner() owner}. The owner closes the + * flock when done, failure to do so may result in a resource leak. The {@code open} + * and {@code close} should be matched to avoid closing an enclosing flock + * while a nested flock is open. A ThreadFlock can be used with the + * try-with-resources construct if required but more likely, the close method of a + * higher-level API that implements {@link AutoCloseable} will close the flock. + * + *

Thread flocks are conceptually nodes in a tree. A thread {@code T} started in + * flock "A" may itself open a new flock "B", implicitly forming a tree where flock + * "A" is the parent of flock "B". When nested, say where thread {@code T} opens + * flock "B" and then invokes a method that opens flock "C", then the enclosing + * flock "B" is conceptually the parent of the nested flock "C". ThreadFlock does + * not define APIs that exposes the tree structure. It does define the {@link + * #containsThread(Thread) containsThread} method to test if a flock contains a + * thread, a test that is equivalent to testing membership of flocks in the tree. + * The {@code start} and {@code shutdown} methods are confined to the flock + * owner or threads contained in the flock. The confinement check is equivalent to + * invoking the {@code containsThread} method to test if the caller is contained + * in the flock. + * + *

Unless otherwise specified, passing a {@code null} argument to a method + * in this class will cause a {@link NullPointerException} to be thrown. + */ +public class ThreadFlock implements AutoCloseable { + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private static final VarHandle THREAD_COUNT; + private static final VarHandle PERMIT; + static { + try { + MethodHandles.Lookup l = MethodHandles.lookup(); + THREAD_COUNT = l.findVarHandle(ThreadFlock.class, "threadCount", int.class); + PERMIT = l.findVarHandle(ThreadFlock.class, "permit", boolean.class); + } catch (Exception e) { + throw new InternalError(e); + } + } + + private final Set threads = ConcurrentHashMap.newKeySet(); + + // thread count, need to re-examine contention once API is stable + private volatile int threadCount; + + private final String name; + private final ScopedValueContainer.BindingsSnapshot scopedValueBindings; + private final ThreadContainerImpl container; // encapsulate for now + + // state + private volatile boolean shutdown; + private volatile boolean closed; + + // set by wakeup, cleared by awaitAll + private volatile boolean permit; + + ThreadFlock(String name) { + this.name = name; + this.scopedValueBindings = ScopedValueContainer.captureBindings(); + this.container = new ThreadContainerImpl(this); + } + + private long threadCount() { + return threadCount; + } + + private ScopedValueContainer.BindingsSnapshot scopedValueBindings() { + return scopedValueBindings; + } + + private void incrementThreadCount() { + THREAD_COUNT.getAndAdd(this, 1); + } + + /** + * Decrement the thread count. Unpark the owner if the count goes to zero. + */ + private void decrementThreadCount() { + int count = (int) THREAD_COUNT.getAndAdd(this, -1) - 1; + + // signal owner when the count goes to zero + if (count == 0) { + LockSupport.unpark(owner()); + } + } + + /** + * Invoked on the parent thread when starting {@code thread}. + */ + private void onStart(Thread thread) { + incrementThreadCount(); + boolean done = false; + try { + boolean added = threads.add(thread); + assert added; + if (shutdown) + throw new IllegalStateException("Shutdown"); + done = true; + } finally { + if (!done) { + threads.remove(thread); + decrementThreadCount(); + } + } + } + + /** + * Invoked on the terminating thread or the parent thread when starting + * {@code thread} failed. This method is only called if onStart succeeded. + */ + private void onExit(Thread thread) { + boolean removed = threads.remove(thread); + assert removed; + decrementThreadCount(); + } + + /** + * Clear wakeup permit. + */ + private void clearPermit() { + if (permit) + permit = false; + } + + /** + * Sets the wakeup permit to the given value, returning the previous value. + */ + private boolean getAndSetPermit(boolean newValue) { + if (permit != newValue) { + return (boolean) PERMIT.getAndSet(this, newValue); + } else { + return newValue; + } + } + + /** + * Throws WrongThreadException if the current thread is not the owner. + */ + private void ensureOwner() { + if (Thread.currentThread() != owner()) + throw new WrongThreadException("Current thread not owner"); + } + + /** + * Throws WrongThreadException if the current thread is not the owner + * or a thread contained in the flock. + */ + private void ensureOwnerOrContainsThread() { + Thread currentThread = Thread.currentThread(); + if (currentThread != owner() && !containsThread(currentThread)) + throw new WrongThreadException("Current thread not owner or thread in flock"); + } + + /** + * Opens a new thread flock. The flock is owned by the current thread. It can be + * named to aid debugging. + * + *

This method captures the current thread's {@linkplain ScopedValue scoped value} + * bindings for inheritance by threads created in the flock. + * + *

For the purposes of containment, monitoring, and debugging, the parent + * of the new flock is determined as follows: + *

    + *
  • If the current thread is the owner of open flocks then the most recently + * created, and open, flock is the parent of the new flock. In other words, the + * enclosing flock is the parent. + *
  • If the current thread is not the owner of any open flocks then the + * parent of the new flock is the current thread's flock. If the current thread + * was not started in a flock then the new flock does not have a parent. + *
+ * + * @param name the name of the flock, can be null + * @return a new thread flock + */ + public static ThreadFlock open(String name) { + var flock = new ThreadFlock(name); + flock.container.push(); + return flock; + } + + /** + * {@return the name of this flock or {@code null} if unnamed} + */ + public String name() { + return name; + } + + /** + * {@return the owner of this flock} + */ + public Thread owner() { + return container.owner(); + } + + /** + * Starts the given unstarted thread in this flock. + * + *

The thread is started with the scoped value bindings that were captured + * when opening the flock. The bindings must match the current thread's bindings. + * + *

This method may only be invoked by the flock owner or threads {@linkplain + * #containsThread(Thread) contained} in the flock. + * + * @param thread the unstarted thread + * @return the thread, started + * @throws IllegalStateException if this flock is shutdown or closed + * @throws IllegalThreadStateException if the given thread was already started + * @throws WrongThreadException if the current thread is not the owner or a thread + * contained in the flock + * @throws StructureViolationException if the current scoped value bindings are + * not the same as when the flock was created + */ + public Thread start(Thread thread) { + ensureOwnerOrContainsThread(); + JLA.start(thread, container); + return thread; + } + + /** + * Shutdown this flock so that no new threads can be started, existing threads + * in the flock will continue to run. This method is a no-op if the flock is + * already shutdown or closed. + * + *

This method may only be invoked by the flock owner or threads {@linkplain + * #containsThread(Thread) contained} in the flock. + * + * @throws WrongThreadException if the current thread is not the owner or a thread + * contained in the flock + */ + public void shutdown() { + ensureOwnerOrContainsThread(); + if (!shutdown) { + shutdown = true; + } + } + + /** + * Wait for all threads in the flock to finish executing their tasks. This method + * waits until all threads finish, the {@link #wakeup() wakeup} method is invoked, + * or the current thread is interrupted. + * + *

This method may only be invoked by the flock owner. The method trivially + * returns true when the flock is closed. + * + *

This method clears the effect of any previous invocations of the + * {@code wakeup} method. + * + * @return true if there are no threads in the flock, false if wakeup was invoked + * and there are unfinished threads + * @throws InterruptedException if interrupted while waiting + * @throws WrongThreadException if invoked by a thread that is not the owner + */ + public boolean awaitAll() throws InterruptedException { + ensureOwner(); + + if (getAndSetPermit(false)) + return (threadCount == 0); + + while (threadCount > 0 && !permit) { + LockSupport.park(); + if (Thread.interrupted()) + throw new InterruptedException(); + } + clearPermit(); + return (threadCount == 0); + } + + /** + * Wait, up to the given waiting timeout, for all threads in the flock to finish + * executing their tasks. This method waits until all threads finish, the {@link + * #wakeup() wakeup} method is invoked, the current thread is interrupted, or + * the timeout expires. + * + *

This method may only be invoked by the flock owner. The method trivially + * returns true when the flock is closed. + * + *

This method clears the effect of any previous invocations of the {@code wakeup} + * method. + * + * @param timeout the maximum duration to wait + * @return true if there are no threads in the flock, false if wakeup was invoked + * and there are unfinished threads + * @throws InterruptedException if interrupted while waiting + * @throws TimeoutException if the wait timed out + * @throws WrongThreadException if invoked by a thread that is not the owner + */ + public boolean awaitAll(Duration timeout) + throws InterruptedException, TimeoutException { + Objects.requireNonNull(timeout); + ensureOwner(); + + if (getAndSetPermit(false)) + return (threadCount == 0); + + long startNanos = System.nanoTime(); + long nanos = NANOSECONDS.convert(timeout); + long remainingNanos = nanos; + while (threadCount > 0 && remainingNanos > 0 && !permit) { + LockSupport.parkNanos(remainingNanos); + if (Thread.interrupted()) + throw new InterruptedException(); + remainingNanos = nanos - (System.nanoTime() - startNanos); + } + + boolean done = (threadCount == 0); + if (!done && remainingNanos <= 0 && !permit) { + throw new TimeoutException(); + } else { + clearPermit(); + return done; + } + } + + /** + * Causes the call to {@link #awaitAll()} or {@link #awaitAll(Duration)} by the + * {@linkplain #owner() owner} to return immediately. + * + *

If the owner is blocked in {@code awaitAll} then it will return immediately. + * If the owner is not blocked in {@code awaitAll} then its next call to wait + * will return immediately. The method does nothing when the flock is closed. + * + * @throws WrongThreadException if the current thread is not the owner or a thread + * contained in the flock + */ + public void wakeup() { + ensureOwnerOrContainsThread(); + if (!getAndSetPermit(true) && Thread.currentThread() != owner()) { + LockSupport.unpark(owner()); + } + } + + /** + * Closes this flock. This method first shuts down the flock to prevent + * new threads from starting. It then waits for the threads in the flock + * to finish executing their tasks. In other words, this method blocks until + * all threads in the flock finish. + * + *

This method may only be invoked by the flock owner. + * + *

If interrupted then this method continues to wait until all threads + * finish, before completing with the interrupt status set. + * + *

A ThreadFlock is intended to be used in a structured manner. If + * this method is called to close a flock before nested flocks are closed then it + * closes the nested flocks (in the reverse order that they were created in), + * closes this flock, and then throws {@code StructureViolationException}. + * Similarly, if this method is called to close a thread flock while executing with + * scoped value bindings, and the thread flock was created before the scoped values + * were bound, then {@code StructureViolationException} is thrown after closing the + * thread flock. + * + * @throws WrongThreadException if invoked by a thread that is not the owner + * @throws StructureViolationException if a structure violation was detected + */ + public void close() { + ensureOwner(); + if (closed) + return; + + // shutdown, if not already shutdown + if (!shutdown) + shutdown = true; + + // wait for threads to finish + boolean interrupted = false; + try { + while (threadCount > 0) { + LockSupport.park(); + if (Thread.interrupted()) { + interrupted = true; + } + } + + } finally { + try { + container.close(); // may throw + } finally { + closed = true; + if (interrupted) Thread.currentThread().interrupt(); + } + } + } + + /** + * {@return true if the flock has been {@linkplain #shutdown() shut down}} + */ + public boolean isShutdown() { + return shutdown; + } + + /** + * {@return true if the flock has been {@linkplain #close() closed}} + */ + public boolean isClosed() { + return closed; + } + + /** + * {@return a stream of the threads in this flock} + * The elements of the stream are threads that were started in this flock + * but have not terminated. The stream will reflect the set of threads in the + * flock at some point at or since the creation of the stream. It may or may + * not reflect changes to the set of threads subsequent to creation of the + * stream. + */ + public Stream threads() { + return threads.stream(); + } + + /** + * Tests if this flock contains the given thread. This method returns {@code true} + * if the thread was started in this flock and has not finished. If the thread + * is not in this flock then it tests if the thread is in flocks owned by threads + * in this flock, essentially equivalent to invoking {@code containsThread} method + * on all flocks owned by the threads in this flock. + * + * @param thread the thread + * @return true if this flock contains the thread + */ + public boolean containsThread(Thread thread) { + var c = JLA.threadContainer(thread); + if (c == this.container) + return true; + if (c != null && c != ThreadContainers.root()) { + var parent = c.parent(); + while (parent != null) { + if (parent == this.container) + return true; + parent = parent.parent(); + } + } + return false; + } + + @Override + public String toString() { + String id = Objects.toIdentityString(this); + if (name != null) { + return name + "/" + id; + } else { + return id; + } + } + + /** + * A ThreadContainer backed by a ThreadFlock. + */ + private static class ThreadContainerImpl extends ThreadContainer { + private final ThreadFlock flock; + private volatile Object key; + private boolean closing; + + ThreadContainerImpl(ThreadFlock flock) { + super(/*shared*/ false); + this.flock = flock; + } + + @Override + public ThreadContainerImpl push() { + // Virtual threads in the root containers may not be tracked so need + // to register container to ensure that it is found + if (!ThreadContainers.trackAllThreads()) { + Thread thread = Thread.currentThread(); + if (thread.isVirtual() + && JLA.threadContainer(thread) == ThreadContainers.root()) { + this.key = ThreadContainers.registerContainer(this); + } + } + super.push(); + return this; + } + + /** + * Invoked by ThreadFlock.close when closing the flock. This method pops the + * container from the current thread's scope stack. + */ + void close() { + assert Thread.currentThread() == owner(); + if (!closing) { + closing = true; + boolean atTop = popForcefully(); // may block + Object key = this.key; + if (key != null) + ThreadContainers.deregisterContainer(key); + if (!atTop) + throw new StructureViolationException(); + } + } + + /** + * Invoked when an enclosing scope is closing. Invokes ThreadFlock.close to + * close the flock. This method does not pop the container from the current + * thread's scope stack. + */ + @Override + protected boolean tryClose() { + assert Thread.currentThread() == owner(); + if (!closing) { + closing = true; + flock.close(); + Object key = this.key; + if (key != null) + ThreadContainers.deregisterContainer(key); + return true; + } else { + assert false : "Should not get there"; + return false; + } + } + + @Override + public String name() { + return flock.name(); + } + @Override + public long threadCount() { + return flock.threadCount(); + } + @Override + public Stream threads() { + return flock.threads().filter(Thread::isAlive); + } + @Override + public void onStart(Thread thread) { + flock.onStart(thread); + } + @Override + public void onExit(Thread thread) { + flock.onExit(thread); + } + @Override + public ScopedValueContainer.BindingsSnapshot scopedValueBindings() { + return flock.scopedValueBindings(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/misc/ThreadTracker$ThreadRef.class b/tests/test_data/std/jdk/internal/misc/ThreadTracker$ThreadRef.class new file mode 100644 index 00000000..25ab59da Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ThreadTracker$ThreadRef.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ThreadTracker.class b/tests/test_data/std/jdk/internal/misc/ThreadTracker.class new file mode 100644 index 00000000..3222e029 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/ThreadTracker.class differ diff --git a/tests/test_data/std/jdk/internal/misc/ThreadTracker.java b/tests/test_data/std/jdk/internal/misc/ThreadTracker.java new file mode 100644 index 00000000..67cf0783 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/ThreadTracker.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.misc; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Tracks threads to help detect reentrancy without using ThreadLocal variables. + * A thread invokes the {@code begin} or {@code tryBegin} methods at the start + * of a block, and the {@code end} method at the end of a block. + */ +public class ThreadTracker { + + /** + * A reference to a Thread that is suitable for use as a key in a collection. + * The hashCode/equals methods do not invoke the Thread hashCode/equals method + * as they may run arbitrary code and/or leak references to Thread objects. + */ + private record ThreadRef(Thread thread) { + @Override + public int hashCode() { + return Long.hashCode(thread.threadId()); + } + @Override + public boolean equals(Object obj) { + return (obj instanceof ThreadRef other) + && this.thread == other.thread; + } + } + + private final Set threads = ConcurrentHashMap.newKeySet(); + + /** + * Adds the current thread to thread set if not already in the set. + * Returns a key to remove the thread or {@code null} if already in the set. + */ + public Object tryBegin() { + var threadRef = new ThreadRef(Thread.currentThread()); + return threads.add(threadRef) ? threadRef : null; + } + + /** + * Adds the current thread to thread set if not already in the set. + * Returns a key to remove the thread. + */ + public Object begin() { + var threadRef = new ThreadRef(Thread.currentThread()); + boolean added = threads.add(threadRef); + assert added; + return threadRef; + } + + /** + * Removes the thread identified by the key from the thread set. + */ + public void end(Object key) { + var threadRef = (ThreadRef) key; + assert threadRef.thread() == Thread.currentThread(); + boolean removed = threads.remove(threadRef); + assert removed; + } + + /** + * Returns true if the given thread is tracked. + */ + public boolean contains(Thread thread) { + var threadRef = new ThreadRef(thread); + return threads.contains(threadRef); + } +} diff --git a/tests/test_data/std/jdk/internal/misc/Unsafe.class b/tests/test_data/std/jdk/internal/misc/Unsafe.class new file mode 100644 index 00000000..a1f1019d Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/Unsafe.class differ diff --git a/tests/test_data/std/jdk/internal/misc/Unsafe.java b/tests/test_data/std/jdk/internal/misc/Unsafe.java new file mode 100644 index 00000000..ec6b7591 --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/Unsafe.java @@ -0,0 +1,3865 @@ +/* + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import jdk.internal.ref.Cleaner; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.IntrinsicCandidate; +import sun.nio.ch.DirectBuffer; + +import java.lang.reflect.Field; +import java.security.ProtectionDomain; + +import static jdk.internal.misc.UnsafeConstants.*; + +/** + * A collection of methods for performing low-level, unsafe operations. + * Although the class and all methods are public, use of this class is + * limited because only trusted code can obtain instances of it. + * + * Note: It is the responsibility of the caller to make sure + * arguments are checked before methods of this class are + * called. While some rudimentary checks are performed on the input, + * the checks are best effort and when performance is an overriding + * priority, as when methods of this class are optimized by the + * runtime compiler, some or all checks (if any) may be elided. Hence, + * the caller must not rely on the checks and corresponding + * exceptions! + * + * @author John R. Rose + * @see #getUnsafe + */ + +public final class Unsafe { + + private static native void registerNatives(); + static { + registerNatives(); + } + + private Unsafe() {} + + private static final Unsafe theUnsafe = new Unsafe(); + + /** + * Provides the caller with the capability of performing unsafe + * operations. + * + *

The returned {@code Unsafe} object should be carefully guarded + * by the caller, since it can be used to read and write data at arbitrary + * memory addresses. It must never be passed to untrusted code. + * + *

Most methods in this class are very low-level, and correspond to a + * small number of hardware instructions (on typical machines). Compilers + * are encouraged to optimize these methods accordingly. + * + *

Here is a suggested idiom for using unsafe operations: + * + *

 {@code
+     * class MyTrustedClass {
+     *   private static final Unsafe unsafe = Unsafe.getUnsafe();
+     *   ...
+     *   private long myCountAddress = ...;
+     *   public int getCount() { return unsafe.getByte(myCountAddress); }
+     * }}
+ * + * (It may assist compilers to make the local variable {@code final}.) + */ + public static Unsafe getUnsafe() { + return theUnsafe; + } + + //--- peek and poke operations + // (compilers should optimize these to memory ops) + + // These work on object fields in the Java heap. + // They will not work on elements of packed arrays. + + /** + * Fetches a value from a given Java variable. + * More specifically, fetches a field or array element within the given + * object {@code o} at the given offset, or (if {@code o} is null) + * from the memory address whose numerical value is the given offset. + *

+ * The results are undefined unless one of the following cases is true: + *

    + *
  • The offset was obtained from {@link #objectFieldOffset} on + * the {@link java.lang.reflect.Field} of some Java field and the object + * referred to by {@code o} is of a class compatible with that + * field's class. + * + *
  • The offset and object reference {@code o} (either null or + * non-null) were both obtained via {@link #staticFieldOffset} + * and {@link #staticFieldBase} (respectively) from the + * reflective {@link Field} representation of some Java field. + * + *
  • The object referred to by {@code o} is an array, and the offset + * is an integer of the form {@code B+N*S}, where {@code N} is + * a valid index into the array, and {@code B} and {@code S} are + * the values obtained by {@link #arrayBaseOffset} and {@link + * #arrayIndexScale} (respectively) from the array's class. The value + * referred to is the {@code N}th element of the array. + * + *
+ *

+ * If one of the above cases is true, the call references a specific Java + * variable (field or array element). However, the results are undefined + * if that variable is not in fact of the type returned by this method. + *

+ * This method refers to a variable by means of two parameters, and so + * it provides (in effect) a double-register addressing mode + * for Java variables. When the object reference is null, this method + * uses its offset as an absolute address. This is similar in operation + * to methods such as {@link #getInt(long)}, which provide (in effect) a + * single-register addressing mode for non-Java variables. + * However, because Java variables may have a different layout in memory + * from non-Java variables, programmers should not assume that these + * two addressing modes are ever equivalent. Also, programmers should + * remember that offsets from the double-register addressing mode cannot + * be portably confused with longs used in the single-register addressing + * mode. + * + * @param o Java heap object in which the variable resides, if any, else + * null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * @return the value fetched from the indicated Java variable + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + */ + @IntrinsicCandidate + public native int getInt(Object o, long offset); + + /** + * Stores a value into a given Java variable. + *

+ * The first two parameters are interpreted exactly as with + * {@link #getInt(Object, long)} to refer to a specific + * Java variable (field or array element). The given value + * is stored into that variable. + *

+ * The variable must be of the same type as the method + * parameter {@code x}. + * + * @param o Java heap object in which the variable resides, if any, else + * null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * @param x the value to store into the indicated Java variable + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + */ + @IntrinsicCandidate + public native void putInt(Object o, long offset, int x); + + /** + * Fetches a reference value from a given Java variable. + * @see #getInt(Object, long) + */ + @IntrinsicCandidate + public native Object getReference(Object o, long offset); + + /** + * Stores a reference value into a given Java variable. + *

+ * Unless the reference {@code x} being stored is either null + * or matches the field type, the results are undefined. + * If the reference {@code o} is non-null, card marks or + * other store barriers for that object (if the VM requires them) + * are updated. + * @see #putInt(Object, long, int) + */ + @IntrinsicCandidate + public native void putReference(Object o, long offset, Object x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native boolean getBoolean(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putBoolean(Object o, long offset, boolean x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native byte getByte(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putByte(Object o, long offset, byte x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native short getShort(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putShort(Object o, long offset, short x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native char getChar(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putChar(Object o, long offset, char x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native long getLong(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putLong(Object o, long offset, long x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native float getFloat(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putFloat(Object o, long offset, float x); + + /** @see #getInt(Object, long) */ + @IntrinsicCandidate + public native double getDouble(Object o, long offset); + + /** @see #putInt(Object, long, int) */ + @IntrinsicCandidate + public native void putDouble(Object o, long offset, double x); + + /** + * Fetches a native pointer from a given memory address. If the address is + * zero, or does not point into a block obtained from {@link + * #allocateMemory}, the results are undefined. + * + *

If the native pointer is less than 64 bits wide, it is extended as + * an unsigned number to a Java long. The pointer may be indexed by any + * given byte offset, simply by adding that offset (as a simple integer) to + * the long representing the pointer. The number of bytes actually read + * from the target address may be determined by consulting {@link + * #addressSize}. + * + * @see #allocateMemory + * @see #getInt(Object, long) + */ + @ForceInline + public long getAddress(Object o, long offset) { + if (ADDRESS_SIZE == 4) { + return Integer.toUnsignedLong(getInt(o, offset)); + } else { + return getLong(o, offset); + } + } + + /** + * Stores a native pointer into a given memory address. If the address is + * zero, or does not point into a block obtained from {@link + * #allocateMemory}, the results are undefined. + * + *

The number of bytes actually written at the target address may be + * determined by consulting {@link #addressSize}. + * + * @see #allocateMemory + * @see #putInt(Object, long, int) + */ + @ForceInline + public void putAddress(Object o, long offset, long x) { + if (ADDRESS_SIZE == 4) { + putInt(o, offset, (int)x); + } else { + putLong(o, offset, x); + } + } + + // These read VM internal data. + + /** + * Fetches an uncompressed reference value from a given native variable + * ignoring the VM's compressed references mode. + * + * @param address a memory address locating the variable + * @return the value fetched from the indicated native variable + */ + public native Object getUncompressedObject(long address); + + // These work on values in the C heap. + + /** + * Fetches a value from a given memory address. If the address is zero, or + * does not point into a block obtained from {@link #allocateMemory}, the + * results are undefined. + * + * @see #allocateMemory + */ + @ForceInline + public byte getByte(long address) { + return getByte(null, address); + } + + /** + * Stores a value into a given memory address. If the address is zero, or + * does not point into a block obtained from {@link #allocateMemory}, the + * results are undefined. + * + * @see #getByte(long) + */ + @ForceInline + public void putByte(long address, byte x) { + putByte(null, address, x); + } + + /** @see #getByte(long) */ + @ForceInline + public short getShort(long address) { + return getShort(null, address); + } + + /** @see #putByte(long, byte) */ + @ForceInline + public void putShort(long address, short x) { + putShort(null, address, x); + } + + /** @see #getByte(long) */ + @ForceInline + public char getChar(long address) { + return getChar(null, address); + } + + /** @see #putByte(long, byte) */ + @ForceInline + public void putChar(long address, char x) { + putChar(null, address, x); + } + + /** @see #getByte(long) */ + @ForceInline + public int getInt(long address) { + return getInt(null, address); + } + + /** @see #putByte(long, byte) */ + @ForceInline + public void putInt(long address, int x) { + putInt(null, address, x); + } + + /** @see #getByte(long) */ + @ForceInline + public long getLong(long address) { + return getLong(null, address); + } + + /** @see #putByte(long, byte) */ + @ForceInline + public void putLong(long address, long x) { + putLong(null, address, x); + } + + /** @see #getByte(long) */ + @ForceInline + public float getFloat(long address) { + return getFloat(null, address); + } + + /** @see #putByte(long, byte) */ + @ForceInline + public void putFloat(long address, float x) { + putFloat(null, address, x); + } + + /** @see #getByte(long) */ + @ForceInline + public double getDouble(long address) { + return getDouble(null, address); + } + + /** @see #putByte(long, byte) */ + @ForceInline + public void putDouble(long address, double x) { + putDouble(null, address, x); + } + + /** @see #getAddress(Object, long) */ + @ForceInline + public long getAddress(long address) { + return getAddress(null, address); + } + + /** @see #putAddress(Object, long, long) */ + @ForceInline + public void putAddress(long address, long x) { + putAddress(null, address, x); + } + + + + //--- helper methods for validating various types of objects/values + + /** + * Create an exception reflecting that some of the input was invalid + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @return an exception object + */ + private RuntimeException invalidInput() { + return new IllegalArgumentException(); + } + + /** + * Check if a value is 32-bit clean (32 MSB are all zero) + * + * @param value the 64-bit value to check + * + * @return true if the value is 32-bit clean + */ + private boolean is32BitClean(long value) { + return value >>> 32 == 0; + } + + /** + * Check the validity of a size (the equivalent of a size_t) + * + * @throws RuntimeException if the size is invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void checkSize(long size) { + if (ADDRESS_SIZE == 4) { + // Note: this will also check for negative sizes + if (!is32BitClean(size)) { + throw invalidInput(); + } + } else if (size < 0) { + throw invalidInput(); + } + } + + /** + * Check the validity of a native address (the equivalent of void*) + * + * @throws RuntimeException if the address is invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void checkNativeAddress(long address) { + if (ADDRESS_SIZE == 4) { + // Accept both zero and sign extended pointers. A valid + // pointer will, after the +1 below, either have produced + // the value 0x0 or 0x1. Masking off the low bit allows + // for testing against 0. + if ((((address >> 32) + 1) & ~1) != 0) { + throw invalidInput(); + } + } + } + + /** + * Check the validity of an offset, relative to a base object + * + * @param o the base object + * @param offset the offset to check + * + * @throws RuntimeException if the size is invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void checkOffset(Object o, long offset) { + if (ADDRESS_SIZE == 4) { + // Note: this will also check for negative offsets + if (!is32BitClean(offset)) { + throw invalidInput(); + } + } else if (offset < 0) { + throw invalidInput(); + } + } + + /** + * Check the validity of a double-register pointer + * + * Note: This code deliberately does *not* check for NPE for (at + * least) three reasons: + * + * 1) NPE is not just NULL/0 - there is a range of values all + * resulting in an NPE, which is not trivial to check for + * + * 2) It is the responsibility of the callers of Unsafe methods + * to verify the input, so throwing an exception here is not really + * useful - passing in a NULL pointer is a critical error and the + * must not expect an exception to be thrown anyway. + * + * 3) the actual operations will detect NULL pointers anyway by + * means of traps and signals (like SIGSEGV). + * + * @param o Java heap object, or null + * @param offset indication of where the variable resides in a Java heap + * object, if any, else a memory address locating the variable + * statically + * + * @throws RuntimeException if the pointer is invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void checkPointer(Object o, long offset) { + if (o == null) { + checkNativeAddress(offset); + } else { + checkOffset(o, offset); + } + } + + /** + * Check if a type is a primitive array type + * + * @param c the type to check + * + * @return true if the type is a primitive array type + */ + private void checkPrimitiveArray(Class c) { + Class componentType = c.getComponentType(); + if (componentType == null || !componentType.isPrimitive()) { + throw invalidInput(); + } + } + + /** + * Check that a pointer is a valid primitive array type pointer + * + * Note: pointers off-heap are considered to be primitive arrays + * + * @throws RuntimeException if the pointer is invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void checkPrimitivePointer(Object o, long offset) { + checkPointer(o, offset); + + if (o != null) { + // If on heap, it must be a primitive array + checkPrimitiveArray(o.getClass()); + } + } + + + //--- wrappers for malloc, realloc, free: + + /** + * Round up allocation size to a multiple of HeapWordSize. + */ + private long alignToHeapWordSize(long bytes) { + if (bytes >= 0) { + return (bytes + ADDRESS_SIZE - 1) & ~(ADDRESS_SIZE - 1); + } else { + throw invalidInput(); + } + } + + /** + * Allocates a new block of native memory, of the given size in bytes. The + * contents of the memory are uninitialized; they will generally be + * garbage. The resulting native pointer will be zero if and only if the + * requested size is zero. The resulting native pointer will be aligned for + * all value types. Dispose of this memory by calling {@link #freeMemory} + * or resize it with {@link #reallocateMemory}. + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @throws RuntimeException if the size is negative or too large + * for the native size_t type + * + * @throws OutOfMemoryError if the allocation is refused by the system + * + * @see #getByte(long) + * @see #putByte(long, byte) + */ + public long allocateMemory(long bytes) { + bytes = alignToHeapWordSize(bytes); + + allocateMemoryChecks(bytes); + + if (bytes == 0) { + return 0; + } + + long p = allocateMemory0(bytes); + if (p == 0) { + throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes"); + } + + return p; + } + + /** + * Validate the arguments to allocateMemory + * + * @throws RuntimeException if the arguments are invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void allocateMemoryChecks(long bytes) { + checkSize(bytes); + } + + /** + * Resizes a new block of native memory, to the given size in bytes. The + * contents of the new block past the size of the old block are + * uninitialized; they will generally be garbage. The resulting native + * pointer will be zero if and only if the requested size is zero. The + * resulting native pointer will be aligned for all value types. Dispose + * of this memory by calling {@link #freeMemory}, or resize it with {@link + * #reallocateMemory}. The address passed to this method may be null, in + * which case an allocation will be performed. + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @throws RuntimeException if the size is negative or too large + * for the native size_t type + * + * @throws OutOfMemoryError if the allocation is refused by the system + * + * @see #allocateMemory + */ + public long reallocateMemory(long address, long bytes) { + bytes = alignToHeapWordSize(bytes); + + reallocateMemoryChecks(address, bytes); + + if (bytes == 0) { + freeMemory(address); + return 0; + } + + long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes); + if (p == 0) { + throw new OutOfMemoryError("Unable to allocate " + bytes + " bytes"); + } + + return p; + } + + /** + * Validate the arguments to reallocateMemory + * + * @throws RuntimeException if the arguments are invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void reallocateMemoryChecks(long address, long bytes) { + checkPointer(null, address); + checkSize(bytes); + } + + /** + * Sets all bytes in a given block of memory to a fixed value + * (usually zero). + * + *

This method determines a block's base address by means of two parameters, + * and so it provides (in effect) a double-register addressing mode, + * as discussed in {@link #getInt(Object,long)}. When the object reference is null, + * the offset supplies an absolute base address. + * + *

The stores are in coherent (atomic) units of a size determined + * by the address and length parameters. If the effective address and + * length are all even modulo 8, the stores take place in 'long' units. + * If the effective address and length are (resp.) even modulo 4 or 2, + * the stores take place in units of 'int' or 'short'. + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @throws RuntimeException if any of the arguments is invalid + * + * @since 1.7 + */ + public void setMemory(Object o, long offset, long bytes, byte value) { + setMemoryChecks(o, offset, bytes, value); + + if (bytes == 0) { + return; + } + + setMemory0(o, offset, bytes, value); + } + + /** + * Sets all bytes in a given block of memory to a fixed value + * (usually zero). This provides a single-register addressing mode, + * as discussed in {@link #getInt(Object,long)}. + * + *

Equivalent to {@code setMemory(null, address, bytes, value)}. + */ + public void setMemory(long address, long bytes, byte value) { + setMemory(null, address, bytes, value); + } + + /** + * Validate the arguments to setMemory + * + * @throws RuntimeException if the arguments are invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void setMemoryChecks(Object o, long offset, long bytes, byte value) { + checkPrimitivePointer(o, offset); + checkSize(bytes); + } + + /** + * Sets all bytes in a given block of memory to a copy of another + * block. + * + *

This method determines each block's base address by means of two parameters, + * and so it provides (in effect) a double-register addressing mode, + * as discussed in {@link #getInt(Object,long)}. When the object reference is null, + * the offset supplies an absolute base address. + * + *

The transfers are in coherent (atomic) units of a size determined + * by the address and length parameters. If the effective addresses and + * length are all even modulo 8, the transfer takes place in 'long' units. + * If the effective addresses and length are (resp.) even modulo 4 or 2, + * the transfer takes place in units of 'int' or 'short'. + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @throws RuntimeException if any of the arguments is invalid + * + * @since 1.7 + */ + public void copyMemory(Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes) { + copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes); + + if (bytes == 0) { + return; + } + + copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes); + } + + /** + * Sets all bytes in a given block of memory to a copy of another + * block. This provides a single-register addressing mode, + * as discussed in {@link #getInt(Object,long)}. + * + * Equivalent to {@code copyMemory(null, srcAddress, null, destAddress, bytes)}. + */ + public void copyMemory(long srcAddress, long destAddress, long bytes) { + copyMemory(null, srcAddress, null, destAddress, bytes); + } + + /** + * Validate the arguments to copyMemory + * + * @throws RuntimeException if any of the arguments is invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void copyMemoryChecks(Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes) { + checkSize(bytes); + checkPrimitivePointer(srcBase, srcOffset); + checkPrimitivePointer(destBase, destOffset); + } + + /** + * Copies all elements from one block of memory to another block, + * *unconditionally* byte swapping the elements on the fly. + * + *

This method determines each block's base address by means of two parameters, + * and so it provides (in effect) a double-register addressing mode, + * as discussed in {@link #getInt(Object,long)}. When the object reference is null, + * the offset supplies an absolute base address. + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @throws RuntimeException if any of the arguments is invalid + * + * @since 9 + */ + public void copySwapMemory(Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes, long elemSize) { + copySwapMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes, elemSize); + + if (bytes == 0) { + return; + } + + copySwapMemory0(srcBase, srcOffset, destBase, destOffset, bytes, elemSize); + } + + private void copySwapMemoryChecks(Object srcBase, long srcOffset, + Object destBase, long destOffset, + long bytes, long elemSize) { + checkSize(bytes); + + if (elemSize != 2 && elemSize != 4 && elemSize != 8) { + throw invalidInput(); + } + if (bytes % elemSize != 0) { + throw invalidInput(); + } + + checkPrimitivePointer(srcBase, srcOffset); + checkPrimitivePointer(destBase, destOffset); + } + + /** + * Copies all elements from one block of memory to another block, byte swapping the + * elements on the fly. + * + * This provides a single-register addressing mode, as + * discussed in {@link #getInt(Object,long)}. + * + * Equivalent to {@code copySwapMemory(null, srcAddress, null, destAddress, bytes, elemSize)}. + */ + public void copySwapMemory(long srcAddress, long destAddress, long bytes, long elemSize) { + copySwapMemory(null, srcAddress, null, destAddress, bytes, elemSize); + } + + /** + * Disposes of a block of native memory, as obtained from {@link + * #allocateMemory} or {@link #reallocateMemory}. The address passed to + * this method may be null, in which case no action is taken. + * + * Note: It is the responsibility of the caller to make + * sure arguments are checked before the methods are called. While + * some rudimentary checks are performed on the input, the checks + * are best effort and when performance is an overriding priority, + * as when methods of this class are optimized by the runtime + * compiler, some or all checks (if any) may be elided. Hence, the + * caller must not rely on the checks and corresponding + * exceptions! + * + * @throws RuntimeException if any of the arguments is invalid + * + * @see #allocateMemory + */ + public void freeMemory(long address) { + freeMemoryChecks(address); + + if (address == 0) { + return; + } + + freeMemory0(address); + } + + /** + * Validate the arguments to freeMemory + * + * @throws RuntimeException if the arguments are invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void freeMemoryChecks(long address) { + checkPointer(null, address); + } + + /** + * Ensure writeback of a specified virtual memory address range + * from cache to physical memory. All bytes in the address range + * are guaranteed to have been written back to physical memory on + * return from this call i.e. subsequently executed store + * instructions are guaranteed not to be visible before the + * writeback is completed. + * + * @param address + * the lowest byte address that must be guaranteed written + * back to memory. bytes at lower addresses may also be + * written back. + * + * @param length + * the length in bytes of the region starting at address + * that must be guaranteed written back to memory. + * + * @throws RuntimeException if memory writeback is not supported + * on the current hardware of if the arguments are invalid. + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + * + * @since 14 + */ + + public void writebackMemory(long address, long length) { + checkWritebackEnabled(); + checkWritebackMemory(address, length); + + // perform any required pre-writeback barrier + writebackPreSync0(); + + // write back one cache line at a time + long line = dataCacheLineAlignDown(address); + long end = address + length; + while (line < end) { + writeback0(line); + line += dataCacheLineFlushSize(); + } + + // perform any required post-writeback barrier + writebackPostSync0(); + } + + /** + * Validate the arguments to writebackMemory + * + * @throws RuntimeException if the arguments are invalid + * (Note: after optimization, invalid inputs may + * go undetected, which will lead to unpredictable + * behavior) + */ + private void checkWritebackMemory(long address, long length) { + checkNativeAddress(address); + checkSize(length); + } + + /** + * Validate that the current hardware supports memory writeback. + * (Note: this is a belt and braces check. Clients are + * expected to test whether writeback is enabled by calling + * ({@link isWritebackEnabled #isWritebackEnabled} and avoid + * calling method {@link writeback #writeback} if it is disabled). + * + * + * @throws RuntimeException if memory writeback is not supported + */ + private void checkWritebackEnabled() { + if (!isWritebackEnabled()) { + throw new RuntimeException("writebackMemory not enabled!"); + } + } + + /** + * force writeback of an individual cache line. + * + * @param address + * the start address of the cache line to be written back + */ + @IntrinsicCandidate + private native void writeback0(long address); + + /** + * Serialize writeback operations relative to preceding memory writes. + */ + @IntrinsicCandidate + private native void writebackPreSync0(); + + /** + * Serialize writeback operations relative to following memory writes. + */ + @IntrinsicCandidate + private native void writebackPostSync0(); + + //--- random queries + + /** + * This constant differs from all results that will ever be returned from + * {@link #staticFieldOffset}, {@link #objectFieldOffset}, + * or {@link #arrayBaseOffset}. + */ + public static final int INVALID_FIELD_OFFSET = -1; + + /** + * Reports the location of a given field in the storage allocation of its + * class. Do not expect to perform any sort of arithmetic on this offset; + * it is just a cookie which is passed to the unsafe heap memory accessors. + * + *

Any given field will always have the same offset and base, and no + * two distinct fields of the same class will ever have the same offset + * and base. + * + *

As of 1.4.1, offsets for fields are represented as long values, + * although the Sun JVM does not use the most significant 32 bits. + * However, JVM implementations which store static fields at absolute + * addresses can use long offsets and null base pointers to express + * the field locations in a form usable by {@link #getInt(Object,long)}. + * Therefore, code which will be ported to such JVMs on 64-bit platforms + * must preserve all bits of static field offsets. + * @see #getInt(Object, long) + */ + public long objectFieldOffset(Field f) { + if (f == null) { + throw new NullPointerException(); + } + + return objectFieldOffset0(f); + } + + /** + * Reports the location of the field with a given name in the storage + * allocation of its class. + * + * @throws NullPointerException if any parameter is {@code null}. + * @throws InternalError if there is no field named {@code name} declared + * in class {@code c}, i.e., if {@code c.getDeclaredField(name)} + * would throw {@code java.lang.NoSuchFieldException}. + * + * @see #objectFieldOffset(Field) + */ + public long objectFieldOffset(Class c, String name) { + if (c == null || name == null) { + throw new NullPointerException(); + } + + return objectFieldOffset1(c, name); + } + + /** + * Reports the location of a given static field, in conjunction with {@link + * #staticFieldBase}. + *

Do not expect to perform any sort of arithmetic on this offset; + * it is just a cookie which is passed to the unsafe heap memory accessors. + * + *

Any given field will always have the same offset, and no two distinct + * fields of the same class will ever have the same offset. + * + *

As of 1.4.1, offsets for fields are represented as long values, + * although the Sun JVM does not use the most significant 32 bits. + * It is hard to imagine a JVM technology which needs more than + * a few bits to encode an offset within a non-array object, + * However, for consistency with other methods in this class, + * this method reports its result as a long value. + * @see #getInt(Object, long) + */ + public long staticFieldOffset(Field f) { + if (f == null) { + throw new NullPointerException(); + } + + return staticFieldOffset0(f); + } + + /** + * Reports the location of a given static field, in conjunction with {@link + * #staticFieldOffset}. + *

Fetch the base "Object", if any, with which static fields of the + * given class can be accessed via methods like {@link #getInt(Object, + * long)}. This value may be null. This value may refer to an object + * which is a "cookie", not guaranteed to be a real Object, and it should + * not be used in any way except as argument to the get and put routines in + * this class. + */ + public Object staticFieldBase(Field f) { + if (f == null) { + throw new NullPointerException(); + } + + return staticFieldBase0(f); + } + + /** + * Detects if the given class may need to be initialized. This is often + * needed in conjunction with obtaining the static field base of a + * class. + * @return false only if a call to {@code ensureClassInitialized} would have no effect + */ + public boolean shouldBeInitialized(Class c) { + if (c == null) { + throw new NullPointerException(); + } + + return shouldBeInitialized0(c); + } + + /** + * Ensures the given class has been initialized (see JVMS-5.5 for details). + * This is often needed in conjunction with obtaining the static field base + * of a class. + * + * The call returns when either class {@code c} is fully initialized or + * class {@code c} is being initialized and the call is performed from + * the initializing thread. In the latter case a subsequent call to + * {@link #shouldBeInitialized} will return {@code true}. + */ + public void ensureClassInitialized(Class c) { + if (c == null) { + throw new NullPointerException(); + } + + ensureClassInitialized0(c); + } + + /** + * Reports the offset of the first element in the storage allocation of a + * given array class. If {@link #arrayIndexScale} returns a non-zero value + * for the same class, you may use that scale factor, together with this + * base offset, to form new offsets to access elements of arrays of the + * given class. + * + * @see #getInt(Object, long) + * @see #putInt(Object, long, int) + */ + public int arrayBaseOffset(Class arrayClass) { + if (arrayClass == null) { + throw new NullPointerException(); + } + + return arrayBaseOffset0(arrayClass); + } + + + /** The value of {@code arrayBaseOffset(boolean[].class)} */ + public static final int ARRAY_BOOLEAN_BASE_OFFSET + = theUnsafe.arrayBaseOffset(boolean[].class); + + /** The value of {@code arrayBaseOffset(byte[].class)} */ + public static final int ARRAY_BYTE_BASE_OFFSET + = theUnsafe.arrayBaseOffset(byte[].class); + + /** The value of {@code arrayBaseOffset(short[].class)} */ + public static final int ARRAY_SHORT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(short[].class); + + /** The value of {@code arrayBaseOffset(char[].class)} */ + public static final int ARRAY_CHAR_BASE_OFFSET + = theUnsafe.arrayBaseOffset(char[].class); + + /** The value of {@code arrayBaseOffset(int[].class)} */ + public static final int ARRAY_INT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(int[].class); + + /** The value of {@code arrayBaseOffset(long[].class)} */ + public static final int ARRAY_LONG_BASE_OFFSET + = theUnsafe.arrayBaseOffset(long[].class); + + /** The value of {@code arrayBaseOffset(float[].class)} */ + public static final int ARRAY_FLOAT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(float[].class); + + /** The value of {@code arrayBaseOffset(double[].class)} */ + public static final int ARRAY_DOUBLE_BASE_OFFSET + = theUnsafe.arrayBaseOffset(double[].class); + + /** The value of {@code arrayBaseOffset(Object[].class)} */ + public static final int ARRAY_OBJECT_BASE_OFFSET + = theUnsafe.arrayBaseOffset(Object[].class); + + /** + * Reports the scale factor for addressing elements in the storage + * allocation of a given array class. However, arrays of "narrow" types + * will generally not work properly with accessors like {@link + * #getByte(Object, long)}, so the scale factor for such classes is reported + * as zero. + * + * @see #arrayBaseOffset + * @see #getInt(Object, long) + * @see #putInt(Object, long, int) + */ + public int arrayIndexScale(Class arrayClass) { + if (arrayClass == null) { + throw new NullPointerException(); + } + + return arrayIndexScale0(arrayClass); + } + + + /** The value of {@code arrayIndexScale(boolean[].class)} */ + public static final int ARRAY_BOOLEAN_INDEX_SCALE + = theUnsafe.arrayIndexScale(boolean[].class); + + /** The value of {@code arrayIndexScale(byte[].class)} */ + public static final int ARRAY_BYTE_INDEX_SCALE + = theUnsafe.arrayIndexScale(byte[].class); + + /** The value of {@code arrayIndexScale(short[].class)} */ + public static final int ARRAY_SHORT_INDEX_SCALE + = theUnsafe.arrayIndexScale(short[].class); + + /** The value of {@code arrayIndexScale(char[].class)} */ + public static final int ARRAY_CHAR_INDEX_SCALE + = theUnsafe.arrayIndexScale(char[].class); + + /** The value of {@code arrayIndexScale(int[].class)} */ + public static final int ARRAY_INT_INDEX_SCALE + = theUnsafe.arrayIndexScale(int[].class); + + /** The value of {@code arrayIndexScale(long[].class)} */ + public static final int ARRAY_LONG_INDEX_SCALE + = theUnsafe.arrayIndexScale(long[].class); + + /** The value of {@code arrayIndexScale(float[].class)} */ + public static final int ARRAY_FLOAT_INDEX_SCALE + = theUnsafe.arrayIndexScale(float[].class); + + /** The value of {@code arrayIndexScale(double[].class)} */ + public static final int ARRAY_DOUBLE_INDEX_SCALE + = theUnsafe.arrayIndexScale(double[].class); + + /** The value of {@code arrayIndexScale(Object[].class)} */ + public static final int ARRAY_OBJECT_INDEX_SCALE + = theUnsafe.arrayIndexScale(Object[].class); + + /** + * Reports the size in bytes of a native pointer, as stored via {@link + * #putAddress}. This value will be either 4 or 8. Note that the sizes of + * other primitive types (as stored in native memory blocks) is determined + * fully by their information content. + */ + public int addressSize() { + return ADDRESS_SIZE; + } + + /** The value of {@code addressSize()} */ + public static final int ADDRESS_SIZE = ADDRESS_SIZE0; + + /** + * Reports the size in bytes of a native memory page (whatever that is). + * This value will always be a power of two. + */ + public int pageSize() { return PAGE_SIZE; } + + /** + * Reports the size in bytes of a data cache line written back by + * the hardware cache line flush operation available to the JVM or + * 0 if data cache line flushing is not enabled. + */ + public int dataCacheLineFlushSize() { return DATA_CACHE_LINE_FLUSH_SIZE; } + + /** + * Rounds down address to a data cache line boundary as + * determined by {@link #dataCacheLineFlushSize} + * @return the rounded down address + */ + public long dataCacheLineAlignDown(long address) { + return (address & ~(DATA_CACHE_LINE_FLUSH_SIZE - 1)); + } + + /** + * Returns true if data cache line writeback + */ + public static boolean isWritebackEnabled() { return DATA_CACHE_LINE_FLUSH_SIZE != 0; } + + //--- random trusted operations from JNI: + + /** + * Tells the VM to define a class, without security checks. By default, the + * class loader and protection domain come from the caller's class. + */ + public Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, + ProtectionDomain protectionDomain) { + if (b == null) { + throw new NullPointerException(); + } + if (len < 0) { + throw new ArrayIndexOutOfBoundsException(); + } + + return defineClass0(name, b, off, len, loader, protectionDomain); + } + + public native Class defineClass0(String name, byte[] b, int off, int len, + ClassLoader loader, + ProtectionDomain protectionDomain); + + /** + * Allocates an instance but does not run any constructor. + * Initializes the class if it has not yet been. + */ + @IntrinsicCandidate + public native Object allocateInstance(Class cls) + throws InstantiationException; + + /** + * Allocates an array of a given type, but does not do zeroing. + *

+ * This method should only be used in the very rare cases where a high-performance code + * overwrites the destination array completely, and compilers cannot assist in zeroing elimination. + * In an overwhelming majority of cases, a normal Java allocation should be used instead. + *

+ * Users of this method are required to overwrite the initial (garbage) array contents + * before allowing untrusted code, or code in other threads, to observe the reference + * to the newly allocated array. In addition, the publication of the array reference must be + * safe according to the Java Memory Model requirements. + *

+ * The safest approach to deal with an uninitialized array is to keep the reference to it in local + * variable at least until the initialization is complete, and then publish it once, either + * by writing it to a volatile field, or storing it into a final field in constructor, + * or issuing a {@link #storeFence} before publishing the reference. + *

+ * @implnote This method can only allocate primitive arrays, to avoid garbage reference + * elements that could break heap integrity. + * + * @param componentType array component type to allocate + * @param length array size to allocate + * @throws IllegalArgumentException if component type is null, or not a primitive class; + * or the length is negative + */ + public Object allocateUninitializedArray(Class componentType, int length) { + if (componentType == null) { + throw new IllegalArgumentException("Component type is null"); + } + if (!componentType.isPrimitive()) { + throw new IllegalArgumentException("Component type is not primitive"); + } + if (length < 0) { + throw new IllegalArgumentException("Negative length"); + } + return allocateUninitializedArray0(componentType, length); + } + + @IntrinsicCandidate + private Object allocateUninitializedArray0(Class componentType, int length) { + // These fallbacks provide zeroed arrays, but intrinsic is not required to + // return the zeroed arrays. + if (componentType == byte.class) return new byte[length]; + if (componentType == boolean.class) return new boolean[length]; + if (componentType == short.class) return new short[length]; + if (componentType == char.class) return new char[length]; + if (componentType == int.class) return new int[length]; + if (componentType == float.class) return new float[length]; + if (componentType == long.class) return new long[length]; + if (componentType == double.class) return new double[length]; + return null; + } + + /** Throws the exception without telling the verifier. */ + public native void throwException(Throwable ee); + + /** + * Atomically updates Java variable to {@code x} if it is currently + * holding {@code expected}. + * + *

This operation has memory semantics of a {@code volatile} read + * and write. Corresponds to C11 atomic_compare_exchange_strong. + * + * @return {@code true} if successful + */ + @IntrinsicCandidate + public final native boolean compareAndSetReference(Object o, long offset, + Object expected, + Object x); + + @IntrinsicCandidate + public final native Object compareAndExchangeReference(Object o, long offset, + Object expected, + Object x); + + @IntrinsicCandidate + public final Object compareAndExchangeReferenceAcquire(Object o, long offset, + Object expected, + Object x) { + return compareAndExchangeReference(o, offset, expected, x); + } + + @IntrinsicCandidate + public final Object compareAndExchangeReferenceRelease(Object o, long offset, + Object expected, + Object x) { + return compareAndExchangeReference(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetReferencePlain(Object o, long offset, + Object expected, + Object x) { + return compareAndSetReference(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetReferenceAcquire(Object o, long offset, + Object expected, + Object x) { + return compareAndSetReference(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetReferenceRelease(Object o, long offset, + Object expected, + Object x) { + return compareAndSetReference(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetReference(Object o, long offset, + Object expected, + Object x) { + return compareAndSetReference(o, offset, expected, x); + } + + /** + * Atomically updates Java variable to {@code x} if it is currently + * holding {@code expected}. + * + *

This operation has memory semantics of a {@code volatile} read + * and write. Corresponds to C11 atomic_compare_exchange_strong. + * + * @return {@code true} if successful + */ + @IntrinsicCandidate + public final native boolean compareAndSetInt(Object o, long offset, + int expected, + int x); + + @IntrinsicCandidate + public final native int compareAndExchangeInt(Object o, long offset, + int expected, + int x); + + @IntrinsicCandidate + public final int compareAndExchangeIntAcquire(Object o, long offset, + int expected, + int x) { + return compareAndExchangeInt(o, offset, expected, x); + } + + @IntrinsicCandidate + public final int compareAndExchangeIntRelease(Object o, long offset, + int expected, + int x) { + return compareAndExchangeInt(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetIntPlain(Object o, long offset, + int expected, + int x) { + return compareAndSetInt(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetIntAcquire(Object o, long offset, + int expected, + int x) { + return compareAndSetInt(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetIntRelease(Object o, long offset, + int expected, + int x) { + return compareAndSetInt(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetInt(Object o, long offset, + int expected, + int x) { + return compareAndSetInt(o, offset, expected, x); + } + + @IntrinsicCandidate + public final byte compareAndExchangeByte(Object o, long offset, + byte expected, + byte x) { + long wordOffset = offset & ~3; + int shift = (int) (offset & 3) << 3; + if (BIG_ENDIAN) { + shift = 24 - shift; + } + int mask = 0xFF << shift; + int maskedExpected = (expected & 0xFF) << shift; + int maskedX = (x & 0xFF) << shift; + int fullWord; + do { + fullWord = getIntVolatile(o, wordOffset); + if ((fullWord & mask) != maskedExpected) + return (byte) ((fullWord & mask) >> shift); + } while (!weakCompareAndSetInt(o, wordOffset, + fullWord, (fullWord & ~mask) | maskedX)); + return expected; + } + + @IntrinsicCandidate + public final boolean compareAndSetByte(Object o, long offset, + byte expected, + byte x) { + return compareAndExchangeByte(o, offset, expected, x) == expected; + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetByte(Object o, long offset, + byte expected, + byte x) { + return compareAndSetByte(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetByteAcquire(Object o, long offset, + byte expected, + byte x) { + return weakCompareAndSetByte(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetByteRelease(Object o, long offset, + byte expected, + byte x) { + return weakCompareAndSetByte(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetBytePlain(Object o, long offset, + byte expected, + byte x) { + return weakCompareAndSetByte(o, offset, expected, x); + } + + @IntrinsicCandidate + public final byte compareAndExchangeByteAcquire(Object o, long offset, + byte expected, + byte x) { + return compareAndExchangeByte(o, offset, expected, x); + } + + @IntrinsicCandidate + public final byte compareAndExchangeByteRelease(Object o, long offset, + byte expected, + byte x) { + return compareAndExchangeByte(o, offset, expected, x); + } + + @IntrinsicCandidate + public final short compareAndExchangeShort(Object o, long offset, + short expected, + short x) { + if ((offset & 3) == 3) { + throw new IllegalArgumentException("Update spans the word, not supported"); + } + long wordOffset = offset & ~3; + int shift = (int) (offset & 3) << 3; + if (BIG_ENDIAN) { + shift = 16 - shift; + } + int mask = 0xFFFF << shift; + int maskedExpected = (expected & 0xFFFF) << shift; + int maskedX = (x & 0xFFFF) << shift; + int fullWord; + do { + fullWord = getIntVolatile(o, wordOffset); + if ((fullWord & mask) != maskedExpected) { + return (short) ((fullWord & mask) >> shift); + } + } while (!weakCompareAndSetInt(o, wordOffset, + fullWord, (fullWord & ~mask) | maskedX)); + return expected; + } + + @IntrinsicCandidate + public final boolean compareAndSetShort(Object o, long offset, + short expected, + short x) { + return compareAndExchangeShort(o, offset, expected, x) == expected; + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetShort(Object o, long offset, + short expected, + short x) { + return compareAndSetShort(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetShortAcquire(Object o, long offset, + short expected, + short x) { + return weakCompareAndSetShort(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetShortRelease(Object o, long offset, + short expected, + short x) { + return weakCompareAndSetShort(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetShortPlain(Object o, long offset, + short expected, + short x) { + return weakCompareAndSetShort(o, offset, expected, x); + } + + + @IntrinsicCandidate + public final short compareAndExchangeShortAcquire(Object o, long offset, + short expected, + short x) { + return compareAndExchangeShort(o, offset, expected, x); + } + + @IntrinsicCandidate + public final short compareAndExchangeShortRelease(Object o, long offset, + short expected, + short x) { + return compareAndExchangeShort(o, offset, expected, x); + } + + @ForceInline + private char s2c(short s) { + return (char) s; + } + + @ForceInline + private short c2s(char s) { + return (short) s; + } + + @ForceInline + public final boolean compareAndSetChar(Object o, long offset, + char expected, + char x) { + return compareAndSetShort(o, offset, c2s(expected), c2s(x)); + } + + @ForceInline + public final char compareAndExchangeChar(Object o, long offset, + char expected, + char x) { + return s2c(compareAndExchangeShort(o, offset, c2s(expected), c2s(x))); + } + + @ForceInline + public final char compareAndExchangeCharAcquire(Object o, long offset, + char expected, + char x) { + return s2c(compareAndExchangeShortAcquire(o, offset, c2s(expected), c2s(x))); + } + + @ForceInline + public final char compareAndExchangeCharRelease(Object o, long offset, + char expected, + char x) { + return s2c(compareAndExchangeShortRelease(o, offset, c2s(expected), c2s(x))); + } + + @ForceInline + public final boolean weakCompareAndSetChar(Object o, long offset, + char expected, + char x) { + return weakCompareAndSetShort(o, offset, c2s(expected), c2s(x)); + } + + @ForceInline + public final boolean weakCompareAndSetCharAcquire(Object o, long offset, + char expected, + char x) { + return weakCompareAndSetShortAcquire(o, offset, c2s(expected), c2s(x)); + } + + @ForceInline + public final boolean weakCompareAndSetCharRelease(Object o, long offset, + char expected, + char x) { + return weakCompareAndSetShortRelease(o, offset, c2s(expected), c2s(x)); + } + + @ForceInline + public final boolean weakCompareAndSetCharPlain(Object o, long offset, + char expected, + char x) { + return weakCompareAndSetShortPlain(o, offset, c2s(expected), c2s(x)); + } + + /** + * The JVM converts integral values to boolean values using two + * different conventions, byte testing against zero and truncation + * to least-significant bit. + * + *

The JNI documents specify that, at least for returning + * values from native methods, a Java boolean value is converted + * to the value-set 0..1 by first truncating to a byte (0..255 or + * maybe -128..127) and then testing against zero. Thus, Java + * booleans in non-Java data structures are by convention + * represented as 8-bit containers containing either zero (for + * false) or any non-zero value (for true). + * + *

Java booleans in the heap are also stored in bytes, but are + * strongly normalized to the value-set 0..1 (i.e., they are + * truncated to the least-significant bit). + * + *

The main reason for having different conventions for + * conversion is performance: Truncation to the least-significant + * bit can be usually implemented with fewer (machine) + * instructions than byte testing against zero. + * + *

A number of Unsafe methods load boolean values from the heap + * as bytes. Unsafe converts those values according to the JNI + * rules (i.e, using the "testing against zero" convention). The + * method {@code byte2bool} implements that conversion. + * + * @param b the byte to be converted to boolean + * @return the result of the conversion + */ + @ForceInline + private boolean byte2bool(byte b) { + return b != 0; + } + + /** + * Convert a boolean value to a byte. The return value is strongly + * normalized to the value-set 0..1 (i.e., the value is truncated + * to the least-significant bit). See {@link #byte2bool(byte)} for + * more details on conversion conventions. + * + * @param b the boolean to be converted to byte (and then normalized) + * @return the result of the conversion + */ + @ForceInline + private byte bool2byte(boolean b) { + return b ? (byte)1 : (byte)0; + } + + @ForceInline + public final boolean compareAndSetBoolean(Object o, long offset, + boolean expected, + boolean x) { + return compareAndSetByte(o, offset, bool2byte(expected), bool2byte(x)); + } + + @ForceInline + public final boolean compareAndExchangeBoolean(Object o, long offset, + boolean expected, + boolean x) { + return byte2bool(compareAndExchangeByte(o, offset, bool2byte(expected), bool2byte(x))); + } + + @ForceInline + public final boolean compareAndExchangeBooleanAcquire(Object o, long offset, + boolean expected, + boolean x) { + return byte2bool(compareAndExchangeByteAcquire(o, offset, bool2byte(expected), bool2byte(x))); + } + + @ForceInline + public final boolean compareAndExchangeBooleanRelease(Object o, long offset, + boolean expected, + boolean x) { + return byte2bool(compareAndExchangeByteRelease(o, offset, bool2byte(expected), bool2byte(x))); + } + + @ForceInline + public final boolean weakCompareAndSetBoolean(Object o, long offset, + boolean expected, + boolean x) { + return weakCompareAndSetByte(o, offset, bool2byte(expected), bool2byte(x)); + } + + @ForceInline + public final boolean weakCompareAndSetBooleanAcquire(Object o, long offset, + boolean expected, + boolean x) { + return weakCompareAndSetByteAcquire(o, offset, bool2byte(expected), bool2byte(x)); + } + + @ForceInline + public final boolean weakCompareAndSetBooleanRelease(Object o, long offset, + boolean expected, + boolean x) { + return weakCompareAndSetByteRelease(o, offset, bool2byte(expected), bool2byte(x)); + } + + @ForceInline + public final boolean weakCompareAndSetBooleanPlain(Object o, long offset, + boolean expected, + boolean x) { + return weakCompareAndSetBytePlain(o, offset, bool2byte(expected), bool2byte(x)); + } + + /** + * Atomically updates Java variable to {@code x} if it is currently + * holding {@code expected}. + * + *

This operation has memory semantics of a {@code volatile} read + * and write. Corresponds to C11 atomic_compare_exchange_strong. + * + * @return {@code true} if successful + */ + @ForceInline + public final boolean compareAndSetFloat(Object o, long offset, + float expected, + float x) { + return compareAndSetInt(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + } + + @ForceInline + public final float compareAndExchangeFloat(Object o, long offset, + float expected, + float x) { + int w = compareAndExchangeInt(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + return Float.intBitsToFloat(w); + } + + @ForceInline + public final float compareAndExchangeFloatAcquire(Object o, long offset, + float expected, + float x) { + int w = compareAndExchangeIntAcquire(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + return Float.intBitsToFloat(w); + } + + @ForceInline + public final float compareAndExchangeFloatRelease(Object o, long offset, + float expected, + float x) { + int w = compareAndExchangeIntRelease(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + return Float.intBitsToFloat(w); + } + + @ForceInline + public final boolean weakCompareAndSetFloatPlain(Object o, long offset, + float expected, + float x) { + return weakCompareAndSetIntPlain(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + } + + @ForceInline + public final boolean weakCompareAndSetFloatAcquire(Object o, long offset, + float expected, + float x) { + return weakCompareAndSetIntAcquire(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + } + + @ForceInline + public final boolean weakCompareAndSetFloatRelease(Object o, long offset, + float expected, + float x) { + return weakCompareAndSetIntRelease(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + } + + @ForceInline + public final boolean weakCompareAndSetFloat(Object o, long offset, + float expected, + float x) { + return weakCompareAndSetInt(o, offset, + Float.floatToRawIntBits(expected), + Float.floatToRawIntBits(x)); + } + + /** + * Atomically updates Java variable to {@code x} if it is currently + * holding {@code expected}. + * + *

This operation has memory semantics of a {@code volatile} read + * and write. Corresponds to C11 atomic_compare_exchange_strong. + * + * @return {@code true} if successful + */ + @ForceInline + public final boolean compareAndSetDouble(Object o, long offset, + double expected, + double x) { + return compareAndSetLong(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + } + + @ForceInline + public final double compareAndExchangeDouble(Object o, long offset, + double expected, + double x) { + long w = compareAndExchangeLong(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + return Double.longBitsToDouble(w); + } + + @ForceInline + public final double compareAndExchangeDoubleAcquire(Object o, long offset, + double expected, + double x) { + long w = compareAndExchangeLongAcquire(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + return Double.longBitsToDouble(w); + } + + @ForceInline + public final double compareAndExchangeDoubleRelease(Object o, long offset, + double expected, + double x) { + long w = compareAndExchangeLongRelease(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + return Double.longBitsToDouble(w); + } + + @ForceInline + public final boolean weakCompareAndSetDoublePlain(Object o, long offset, + double expected, + double x) { + return weakCompareAndSetLongPlain(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + } + + @ForceInline + public final boolean weakCompareAndSetDoubleAcquire(Object o, long offset, + double expected, + double x) { + return weakCompareAndSetLongAcquire(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + } + + @ForceInline + public final boolean weakCompareAndSetDoubleRelease(Object o, long offset, + double expected, + double x) { + return weakCompareAndSetLongRelease(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + } + + @ForceInline + public final boolean weakCompareAndSetDouble(Object o, long offset, + double expected, + double x) { + return weakCompareAndSetLong(o, offset, + Double.doubleToRawLongBits(expected), + Double.doubleToRawLongBits(x)); + } + + /** + * Atomically updates Java variable to {@code x} if it is currently + * holding {@code expected}. + * + *

This operation has memory semantics of a {@code volatile} read + * and write. Corresponds to C11 atomic_compare_exchange_strong. + * + * @return {@code true} if successful + */ + @IntrinsicCandidate + public final native boolean compareAndSetLong(Object o, long offset, + long expected, + long x); + + @IntrinsicCandidate + public final native long compareAndExchangeLong(Object o, long offset, + long expected, + long x); + + @IntrinsicCandidate + public final long compareAndExchangeLongAcquire(Object o, long offset, + long expected, + long x) { + return compareAndExchangeLong(o, offset, expected, x); + } + + @IntrinsicCandidate + public final long compareAndExchangeLongRelease(Object o, long offset, + long expected, + long x) { + return compareAndExchangeLong(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetLongPlain(Object o, long offset, + long expected, + long x) { + return compareAndSetLong(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetLongAcquire(Object o, long offset, + long expected, + long x) { + return compareAndSetLong(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetLongRelease(Object o, long offset, + long expected, + long x) { + return compareAndSetLong(o, offset, expected, x); + } + + @IntrinsicCandidate + public final boolean weakCompareAndSetLong(Object o, long offset, + long expected, + long x) { + return compareAndSetLong(o, offset, expected, x); + } + + /** + * Fetches a reference value from a given Java variable, with volatile + * load semantics. Otherwise identical to {@link #getReference(Object, long)} + */ + @IntrinsicCandidate + public native Object getReferenceVolatile(Object o, long offset); + + /** + * Stores a reference value into a given Java variable, with + * volatile store semantics. Otherwise identical to {@link #putReference(Object, long, Object)} + */ + @IntrinsicCandidate + public native void putReferenceVolatile(Object o, long offset, Object x); + + /** Volatile version of {@link #getInt(Object, long)} */ + @IntrinsicCandidate + public native int getIntVolatile(Object o, long offset); + + /** Volatile version of {@link #putInt(Object, long, int)} */ + @IntrinsicCandidate + public native void putIntVolatile(Object o, long offset, int x); + + /** Volatile version of {@link #getBoolean(Object, long)} */ + @IntrinsicCandidate + public native boolean getBooleanVolatile(Object o, long offset); + + /** Volatile version of {@link #putBoolean(Object, long, boolean)} */ + @IntrinsicCandidate + public native void putBooleanVolatile(Object o, long offset, boolean x); + + /** Volatile version of {@link #getByte(Object, long)} */ + @IntrinsicCandidate + public native byte getByteVolatile(Object o, long offset); + + /** Volatile version of {@link #putByte(Object, long, byte)} */ + @IntrinsicCandidate + public native void putByteVolatile(Object o, long offset, byte x); + + /** Volatile version of {@link #getShort(Object, long)} */ + @IntrinsicCandidate + public native short getShortVolatile(Object o, long offset); + + /** Volatile version of {@link #putShort(Object, long, short)} */ + @IntrinsicCandidate + public native void putShortVolatile(Object o, long offset, short x); + + /** Volatile version of {@link #getChar(Object, long)} */ + @IntrinsicCandidate + public native char getCharVolatile(Object o, long offset); + + /** Volatile version of {@link #putChar(Object, long, char)} */ + @IntrinsicCandidate + public native void putCharVolatile(Object o, long offset, char x); + + /** Volatile version of {@link #getLong(Object, long)} */ + @IntrinsicCandidate + public native long getLongVolatile(Object o, long offset); + + /** Volatile version of {@link #putLong(Object, long, long)} */ + @IntrinsicCandidate + public native void putLongVolatile(Object o, long offset, long x); + + /** Volatile version of {@link #getFloat(Object, long)} */ + @IntrinsicCandidate + public native float getFloatVolatile(Object o, long offset); + + /** Volatile version of {@link #putFloat(Object, long, float)} */ + @IntrinsicCandidate + public native void putFloatVolatile(Object o, long offset, float x); + + /** Volatile version of {@link #getDouble(Object, long)} */ + @IntrinsicCandidate + public native double getDoubleVolatile(Object o, long offset); + + /** Volatile version of {@link #putDouble(Object, long, double)} */ + @IntrinsicCandidate + public native void putDoubleVolatile(Object o, long offset, double x); + + + + /** Acquire version of {@link #getReferenceVolatile(Object, long)} */ + @IntrinsicCandidate + public final Object getReferenceAcquire(Object o, long offset) { + return getReferenceVolatile(o, offset); + } + + /** Acquire version of {@link #getBooleanVolatile(Object, long)} */ + @IntrinsicCandidate + public final boolean getBooleanAcquire(Object o, long offset) { + return getBooleanVolatile(o, offset); + } + + /** Acquire version of {@link #getByteVolatile(Object, long)} */ + @IntrinsicCandidate + public final byte getByteAcquire(Object o, long offset) { + return getByteVolatile(o, offset); + } + + /** Acquire version of {@link #getShortVolatile(Object, long)} */ + @IntrinsicCandidate + public final short getShortAcquire(Object o, long offset) { + return getShortVolatile(o, offset); + } + + /** Acquire version of {@link #getCharVolatile(Object, long)} */ + @IntrinsicCandidate + public final char getCharAcquire(Object o, long offset) { + return getCharVolatile(o, offset); + } + + /** Acquire version of {@link #getIntVolatile(Object, long)} */ + @IntrinsicCandidate + public final int getIntAcquire(Object o, long offset) { + return getIntVolatile(o, offset); + } + + /** Acquire version of {@link #getFloatVolatile(Object, long)} */ + @IntrinsicCandidate + public final float getFloatAcquire(Object o, long offset) { + return getFloatVolatile(o, offset); + } + + /** Acquire version of {@link #getLongVolatile(Object, long)} */ + @IntrinsicCandidate + public final long getLongAcquire(Object o, long offset) { + return getLongVolatile(o, offset); + } + + /** Acquire version of {@link #getDoubleVolatile(Object, long)} */ + @IntrinsicCandidate + public final double getDoubleAcquire(Object o, long offset) { + return getDoubleVolatile(o, offset); + } + + /* + * Versions of {@link #putReferenceVolatile(Object, long, Object)} + * that do not guarantee immediate visibility of the store to + * other threads. This method is generally only useful if the + * underlying field is a Java volatile (or if an array cell, one + * that is otherwise only accessed using volatile accesses). + * + * Corresponds to C11 atomic_store_explicit(..., memory_order_release). + */ + + /** Release version of {@link #putReferenceVolatile(Object, long, Object)} */ + @IntrinsicCandidate + public final void putReferenceRelease(Object o, long offset, Object x) { + putReferenceVolatile(o, offset, x); + } + + /** Release version of {@link #putBooleanVolatile(Object, long, boolean)} */ + @IntrinsicCandidate + public final void putBooleanRelease(Object o, long offset, boolean x) { + putBooleanVolatile(o, offset, x); + } + + /** Release version of {@link #putByteVolatile(Object, long, byte)} */ + @IntrinsicCandidate + public final void putByteRelease(Object o, long offset, byte x) { + putByteVolatile(o, offset, x); + } + + /** Release version of {@link #putShortVolatile(Object, long, short)} */ + @IntrinsicCandidate + public final void putShortRelease(Object o, long offset, short x) { + putShortVolatile(o, offset, x); + } + + /** Release version of {@link #putCharVolatile(Object, long, char)} */ + @IntrinsicCandidate + public final void putCharRelease(Object o, long offset, char x) { + putCharVolatile(o, offset, x); + } + + /** Release version of {@link #putIntVolatile(Object, long, int)} */ + @IntrinsicCandidate + public final void putIntRelease(Object o, long offset, int x) { + putIntVolatile(o, offset, x); + } + + /** Release version of {@link #putFloatVolatile(Object, long, float)} */ + @IntrinsicCandidate + public final void putFloatRelease(Object o, long offset, float x) { + putFloatVolatile(o, offset, x); + } + + /** Release version of {@link #putLongVolatile(Object, long, long)} */ + @IntrinsicCandidate + public final void putLongRelease(Object o, long offset, long x) { + putLongVolatile(o, offset, x); + } + + /** Release version of {@link #putDoubleVolatile(Object, long, double)} */ + @IntrinsicCandidate + public final void putDoubleRelease(Object o, long offset, double x) { + putDoubleVolatile(o, offset, x); + } + + // ------------------------------ Opaque -------------------------------------- + + /** Opaque version of {@link #getReferenceVolatile(Object, long)} */ + @IntrinsicCandidate + public final Object getReferenceOpaque(Object o, long offset) { + return getReferenceVolatile(o, offset); + } + + /** Opaque version of {@link #getBooleanVolatile(Object, long)} */ + @IntrinsicCandidate + public final boolean getBooleanOpaque(Object o, long offset) { + return getBooleanVolatile(o, offset); + } + + /** Opaque version of {@link #getByteVolatile(Object, long)} */ + @IntrinsicCandidate + public final byte getByteOpaque(Object o, long offset) { + return getByteVolatile(o, offset); + } + + /** Opaque version of {@link #getShortVolatile(Object, long)} */ + @IntrinsicCandidate + public final short getShortOpaque(Object o, long offset) { + return getShortVolatile(o, offset); + } + + /** Opaque version of {@link #getCharVolatile(Object, long)} */ + @IntrinsicCandidate + public final char getCharOpaque(Object o, long offset) { + return getCharVolatile(o, offset); + } + + /** Opaque version of {@link #getIntVolatile(Object, long)} */ + @IntrinsicCandidate + public final int getIntOpaque(Object o, long offset) { + return getIntVolatile(o, offset); + } + + /** Opaque version of {@link #getFloatVolatile(Object, long)} */ + @IntrinsicCandidate + public final float getFloatOpaque(Object o, long offset) { + return getFloatVolatile(o, offset); + } + + /** Opaque version of {@link #getLongVolatile(Object, long)} */ + @IntrinsicCandidate + public final long getLongOpaque(Object o, long offset) { + return getLongVolatile(o, offset); + } + + /** Opaque version of {@link #getDoubleVolatile(Object, long)} */ + @IntrinsicCandidate + public final double getDoubleOpaque(Object o, long offset) { + return getDoubleVolatile(o, offset); + } + + /** Opaque version of {@link #putReferenceVolatile(Object, long, Object)} */ + @IntrinsicCandidate + public final void putReferenceOpaque(Object o, long offset, Object x) { + putReferenceVolatile(o, offset, x); + } + + /** Opaque version of {@link #putBooleanVolatile(Object, long, boolean)} */ + @IntrinsicCandidate + public final void putBooleanOpaque(Object o, long offset, boolean x) { + putBooleanVolatile(o, offset, x); + } + + /** Opaque version of {@link #putByteVolatile(Object, long, byte)} */ + @IntrinsicCandidate + public final void putByteOpaque(Object o, long offset, byte x) { + putByteVolatile(o, offset, x); + } + + /** Opaque version of {@link #putShortVolatile(Object, long, short)} */ + @IntrinsicCandidate + public final void putShortOpaque(Object o, long offset, short x) { + putShortVolatile(o, offset, x); + } + + /** Opaque version of {@link #putCharVolatile(Object, long, char)} */ + @IntrinsicCandidate + public final void putCharOpaque(Object o, long offset, char x) { + putCharVolatile(o, offset, x); + } + + /** Opaque version of {@link #putIntVolatile(Object, long, int)} */ + @IntrinsicCandidate + public final void putIntOpaque(Object o, long offset, int x) { + putIntVolatile(o, offset, x); + } + + /** Opaque version of {@link #putFloatVolatile(Object, long, float)} */ + @IntrinsicCandidate + public final void putFloatOpaque(Object o, long offset, float x) { + putFloatVolatile(o, offset, x); + } + + /** Opaque version of {@link #putLongVolatile(Object, long, long)} */ + @IntrinsicCandidate + public final void putLongOpaque(Object o, long offset, long x) { + putLongVolatile(o, offset, x); + } + + /** Opaque version of {@link #putDoubleVolatile(Object, long, double)} */ + @IntrinsicCandidate + public final void putDoubleOpaque(Object o, long offset, double x) { + putDoubleVolatile(o, offset, x); + } + + /** + * Unblocks the given thread blocked on {@code park}, or, if it is + * not blocked, causes the subsequent call to {@code park} not to + * block. Note: this operation is "unsafe" solely because the + * caller must somehow ensure that the thread has not been + * destroyed. Nothing special is usually required to ensure this + * when called from Java (in which there will ordinarily be a live + * reference to the thread) but this is not nearly-automatically + * so when calling from native code. + * + * @param thread the thread to unpark. + */ + @IntrinsicCandidate + public native void unpark(Object thread); + + /** + * Blocks current thread, returning when a balancing + * {@code unpark} occurs, or a balancing {@code unpark} has + * already occurred, or the thread is interrupted, or, if not + * absolute and time is not zero, the given time nanoseconds have + * elapsed, or if absolute, the given deadline in milliseconds + * since Epoch has passed, or spuriously (i.e., returning for no + * "reason"). Note: This operation is in the Unsafe class only + * because {@code unpark} is, so it would be strange to place it + * elsewhere. + */ + @IntrinsicCandidate + public native void park(boolean isAbsolute, long time); + + /** + * Gets the load average in the system run queue assigned + * to the available processors averaged over various periods of time. + * This method retrieves the given {@code nelem} samples and + * assigns to the elements of the given {@code loadavg} array. + * The system imposes a maximum of 3 samples, representing + * averages over the last 1, 5, and 15 minutes, respectively. + * + * @param loadavg an array of double of size nelems + * @param nelems the number of samples to be retrieved and + * must be 1 to 3. + * + * @return the number of samples actually retrieved; or -1 + * if the load average is unobtainable. + */ + public int getLoadAverage(double[] loadavg, int nelems) { + if (nelems < 0 || nelems > 3 || nelems > loadavg.length) { + throw new ArrayIndexOutOfBoundsException(); + } + + return getLoadAverage0(loadavg, nelems); + } + + // The following contain CAS-based Java implementations used on + // platforms not supporting native instructions + + /** + * Atomically adds the given value to the current value of a field + * or array element within the given object {@code o} + * at the given {@code offset}. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param delta the value to add + * @return the previous value + * @since 1.8 + */ + @IntrinsicCandidate + public final int getAndAddInt(Object o, long offset, int delta) { + int v; + do { + v = getIntVolatile(o, offset); + } while (!weakCompareAndSetInt(o, offset, v, v + delta)); + return v; + } + + @ForceInline + public final int getAndAddIntRelease(Object o, long offset, int delta) { + int v; + do { + v = getInt(o, offset); + } while (!weakCompareAndSetIntRelease(o, offset, v, v + delta)); + return v; + } + + @ForceInline + public final int getAndAddIntAcquire(Object o, long offset, int delta) { + int v; + do { + v = getIntAcquire(o, offset); + } while (!weakCompareAndSetIntAcquire(o, offset, v, v + delta)); + return v; + } + + /** + * Atomically adds the given value to the current value of a field + * or array element within the given object {@code o} + * at the given {@code offset}. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param delta the value to add + * @return the previous value + * @since 1.8 + */ + @IntrinsicCandidate + public final long getAndAddLong(Object o, long offset, long delta) { + long v; + do { + v = getLongVolatile(o, offset); + } while (!weakCompareAndSetLong(o, offset, v, v + delta)); + return v; + } + + @ForceInline + public final long getAndAddLongRelease(Object o, long offset, long delta) { + long v; + do { + v = getLong(o, offset); + } while (!weakCompareAndSetLongRelease(o, offset, v, v + delta)); + return v; + } + + @ForceInline + public final long getAndAddLongAcquire(Object o, long offset, long delta) { + long v; + do { + v = getLongAcquire(o, offset); + } while (!weakCompareAndSetLongAcquire(o, offset, v, v + delta)); + return v; + } + + @IntrinsicCandidate + public final byte getAndAddByte(Object o, long offset, byte delta) { + byte v; + do { + v = getByteVolatile(o, offset); + } while (!weakCompareAndSetByte(o, offset, v, (byte) (v + delta))); + return v; + } + + @ForceInline + public final byte getAndAddByteRelease(Object o, long offset, byte delta) { + byte v; + do { + v = getByte(o, offset); + } while (!weakCompareAndSetByteRelease(o, offset, v, (byte) (v + delta))); + return v; + } + + @ForceInline + public final byte getAndAddByteAcquire(Object o, long offset, byte delta) { + byte v; + do { + v = getByteAcquire(o, offset); + } while (!weakCompareAndSetByteAcquire(o, offset, v, (byte) (v + delta))); + return v; + } + + @IntrinsicCandidate + public final short getAndAddShort(Object o, long offset, short delta) { + short v; + do { + v = getShortVolatile(o, offset); + } while (!weakCompareAndSetShort(o, offset, v, (short) (v + delta))); + return v; + } + + @ForceInline + public final short getAndAddShortRelease(Object o, long offset, short delta) { + short v; + do { + v = getShort(o, offset); + } while (!weakCompareAndSetShortRelease(o, offset, v, (short) (v + delta))); + return v; + } + + @ForceInline + public final short getAndAddShortAcquire(Object o, long offset, short delta) { + short v; + do { + v = getShortAcquire(o, offset); + } while (!weakCompareAndSetShortAcquire(o, offset, v, (short) (v + delta))); + return v; + } + + @ForceInline + public final char getAndAddChar(Object o, long offset, char delta) { + return (char) getAndAddShort(o, offset, (short) delta); + } + + @ForceInline + public final char getAndAddCharRelease(Object o, long offset, char delta) { + return (char) getAndAddShortRelease(o, offset, (short) delta); + } + + @ForceInline + public final char getAndAddCharAcquire(Object o, long offset, char delta) { + return (char) getAndAddShortAcquire(o, offset, (short) delta); + } + + @ForceInline + public final float getAndAddFloat(Object o, long offset, float delta) { + int expectedBits; + float v; + do { + // Load and CAS with the raw bits to avoid issues with NaNs and + // possible bit conversion from signaling NaNs to quiet NaNs that + // may result in the loop not terminating. + expectedBits = getIntVolatile(o, offset); + v = Float.intBitsToFloat(expectedBits); + } while (!weakCompareAndSetInt(o, offset, + expectedBits, Float.floatToRawIntBits(v + delta))); + return v; + } + + @ForceInline + public final float getAndAddFloatRelease(Object o, long offset, float delta) { + int expectedBits; + float v; + do { + // Load and CAS with the raw bits to avoid issues with NaNs and + // possible bit conversion from signaling NaNs to quiet NaNs that + // may result in the loop not terminating. + expectedBits = getInt(o, offset); + v = Float.intBitsToFloat(expectedBits); + } while (!weakCompareAndSetIntRelease(o, offset, + expectedBits, Float.floatToRawIntBits(v + delta))); + return v; + } + + @ForceInline + public final float getAndAddFloatAcquire(Object o, long offset, float delta) { + int expectedBits; + float v; + do { + // Load and CAS with the raw bits to avoid issues with NaNs and + // possible bit conversion from signaling NaNs to quiet NaNs that + // may result in the loop not terminating. + expectedBits = getIntAcquire(o, offset); + v = Float.intBitsToFloat(expectedBits); + } while (!weakCompareAndSetIntAcquire(o, offset, + expectedBits, Float.floatToRawIntBits(v + delta))); + return v; + } + + @ForceInline + public final double getAndAddDouble(Object o, long offset, double delta) { + long expectedBits; + double v; + do { + // Load and CAS with the raw bits to avoid issues with NaNs and + // possible bit conversion from signaling NaNs to quiet NaNs that + // may result in the loop not terminating. + expectedBits = getLongVolatile(o, offset); + v = Double.longBitsToDouble(expectedBits); + } while (!weakCompareAndSetLong(o, offset, + expectedBits, Double.doubleToRawLongBits(v + delta))); + return v; + } + + @ForceInline + public final double getAndAddDoubleRelease(Object o, long offset, double delta) { + long expectedBits; + double v; + do { + // Load and CAS with the raw bits to avoid issues with NaNs and + // possible bit conversion from signaling NaNs to quiet NaNs that + // may result in the loop not terminating. + expectedBits = getLong(o, offset); + v = Double.longBitsToDouble(expectedBits); + } while (!weakCompareAndSetLongRelease(o, offset, + expectedBits, Double.doubleToRawLongBits(v + delta))); + return v; + } + + @ForceInline + public final double getAndAddDoubleAcquire(Object o, long offset, double delta) { + long expectedBits; + double v; + do { + // Load and CAS with the raw bits to avoid issues with NaNs and + // possible bit conversion from signaling NaNs to quiet NaNs that + // may result in the loop not terminating. + expectedBits = getLongAcquire(o, offset); + v = Double.longBitsToDouble(expectedBits); + } while (!weakCompareAndSetLongAcquire(o, offset, + expectedBits, Double.doubleToRawLongBits(v + delta))); + return v; + } + + /** + * Atomically exchanges the given value with the current value of + * a field or array element within the given object {@code o} + * at the given {@code offset}. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param newValue new value + * @return the previous value + * @since 1.8 + */ + @IntrinsicCandidate + public final int getAndSetInt(Object o, long offset, int newValue) { + int v; + do { + v = getIntVolatile(o, offset); + } while (!weakCompareAndSetInt(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final int getAndSetIntRelease(Object o, long offset, int newValue) { + int v; + do { + v = getInt(o, offset); + } while (!weakCompareAndSetIntRelease(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final int getAndSetIntAcquire(Object o, long offset, int newValue) { + int v; + do { + v = getIntAcquire(o, offset); + } while (!weakCompareAndSetIntAcquire(o, offset, v, newValue)); + return v; + } + + /** + * Atomically exchanges the given value with the current value of + * a field or array element within the given object {@code o} + * at the given {@code offset}. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param newValue new value + * @return the previous value + * @since 1.8 + */ + @IntrinsicCandidate + public final long getAndSetLong(Object o, long offset, long newValue) { + long v; + do { + v = getLongVolatile(o, offset); + } while (!weakCompareAndSetLong(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final long getAndSetLongRelease(Object o, long offset, long newValue) { + long v; + do { + v = getLong(o, offset); + } while (!weakCompareAndSetLongRelease(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final long getAndSetLongAcquire(Object o, long offset, long newValue) { + long v; + do { + v = getLongAcquire(o, offset); + } while (!weakCompareAndSetLongAcquire(o, offset, v, newValue)); + return v; + } + + /** + * Atomically exchanges the given reference value with the current + * reference value of a field or array element within the given + * object {@code o} at the given {@code offset}. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param newValue new value + * @return the previous value + * @since 1.8 + */ + @IntrinsicCandidate + public final Object getAndSetReference(Object o, long offset, Object newValue) { + Object v; + do { + v = getReferenceVolatile(o, offset); + } while (!weakCompareAndSetReference(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final Object getAndSetReferenceRelease(Object o, long offset, Object newValue) { + Object v; + do { + v = getReference(o, offset); + } while (!weakCompareAndSetReferenceRelease(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final Object getAndSetReferenceAcquire(Object o, long offset, Object newValue) { + Object v; + do { + v = getReferenceAcquire(o, offset); + } while (!weakCompareAndSetReferenceAcquire(o, offset, v, newValue)); + return v; + } + + @IntrinsicCandidate + public final byte getAndSetByte(Object o, long offset, byte newValue) { + byte v; + do { + v = getByteVolatile(o, offset); + } while (!weakCompareAndSetByte(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final byte getAndSetByteRelease(Object o, long offset, byte newValue) { + byte v; + do { + v = getByte(o, offset); + } while (!weakCompareAndSetByteRelease(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final byte getAndSetByteAcquire(Object o, long offset, byte newValue) { + byte v; + do { + v = getByteAcquire(o, offset); + } while (!weakCompareAndSetByteAcquire(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final boolean getAndSetBoolean(Object o, long offset, boolean newValue) { + return byte2bool(getAndSetByte(o, offset, bool2byte(newValue))); + } + + @ForceInline + public final boolean getAndSetBooleanRelease(Object o, long offset, boolean newValue) { + return byte2bool(getAndSetByteRelease(o, offset, bool2byte(newValue))); + } + + @ForceInline + public final boolean getAndSetBooleanAcquire(Object o, long offset, boolean newValue) { + return byte2bool(getAndSetByteAcquire(o, offset, bool2byte(newValue))); + } + + @IntrinsicCandidate + public final short getAndSetShort(Object o, long offset, short newValue) { + short v; + do { + v = getShortVolatile(o, offset); + } while (!weakCompareAndSetShort(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final short getAndSetShortRelease(Object o, long offset, short newValue) { + short v; + do { + v = getShort(o, offset); + } while (!weakCompareAndSetShortRelease(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final short getAndSetShortAcquire(Object o, long offset, short newValue) { + short v; + do { + v = getShortAcquire(o, offset); + } while (!weakCompareAndSetShortAcquire(o, offset, v, newValue)); + return v; + } + + @ForceInline + public final char getAndSetChar(Object o, long offset, char newValue) { + return s2c(getAndSetShort(o, offset, c2s(newValue))); + } + + @ForceInline + public final char getAndSetCharRelease(Object o, long offset, char newValue) { + return s2c(getAndSetShortRelease(o, offset, c2s(newValue))); + } + + @ForceInline + public final char getAndSetCharAcquire(Object o, long offset, char newValue) { + return s2c(getAndSetShortAcquire(o, offset, c2s(newValue))); + } + + @ForceInline + public final float getAndSetFloat(Object o, long offset, float newValue) { + int v = getAndSetInt(o, offset, Float.floatToRawIntBits(newValue)); + return Float.intBitsToFloat(v); + } + + @ForceInline + public final float getAndSetFloatRelease(Object o, long offset, float newValue) { + int v = getAndSetIntRelease(o, offset, Float.floatToRawIntBits(newValue)); + return Float.intBitsToFloat(v); + } + + @ForceInline + public final float getAndSetFloatAcquire(Object o, long offset, float newValue) { + int v = getAndSetIntAcquire(o, offset, Float.floatToRawIntBits(newValue)); + return Float.intBitsToFloat(v); + } + + @ForceInline + public final double getAndSetDouble(Object o, long offset, double newValue) { + long v = getAndSetLong(o, offset, Double.doubleToRawLongBits(newValue)); + return Double.longBitsToDouble(v); + } + + @ForceInline + public final double getAndSetDoubleRelease(Object o, long offset, double newValue) { + long v = getAndSetLongRelease(o, offset, Double.doubleToRawLongBits(newValue)); + return Double.longBitsToDouble(v); + } + + @ForceInline + public final double getAndSetDoubleAcquire(Object o, long offset, double newValue) { + long v = getAndSetLongAcquire(o, offset, Double.doubleToRawLongBits(newValue)); + return Double.longBitsToDouble(v); + } + + + // The following contain CAS-based Java implementations used on + // platforms not supporting native instructions + + @ForceInline + public final boolean getAndBitwiseOrBoolean(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseOrByte(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseOrBooleanRelease(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseOrByteRelease(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseOrBooleanAcquire(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseOrByteAcquire(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseAndBoolean(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseAndByte(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseAndBooleanRelease(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseAndByteRelease(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseAndBooleanAcquire(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseAndByteAcquire(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseXorBoolean(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseXorByte(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseXorBooleanRelease(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseXorByteRelease(o, offset, bool2byte(mask))); + } + + @ForceInline + public final boolean getAndBitwiseXorBooleanAcquire(Object o, long offset, boolean mask) { + return byte2bool(getAndBitwiseXorByteAcquire(o, offset, bool2byte(mask))); + } + + + @ForceInline + public final byte getAndBitwiseOrByte(Object o, long offset, byte mask) { + byte current; + do { + current = getByteVolatile(o, offset); + } while (!weakCompareAndSetByte(o, offset, + current, (byte) (current | mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseOrByteRelease(Object o, long offset, byte mask) { + byte current; + do { + current = getByte(o, offset); + } while (!weakCompareAndSetByteRelease(o, offset, + current, (byte) (current | mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseOrByteAcquire(Object o, long offset, byte mask) { + byte current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getByte(o, offset); + } while (!weakCompareAndSetByteAcquire(o, offset, + current, (byte) (current | mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseAndByte(Object o, long offset, byte mask) { + byte current; + do { + current = getByteVolatile(o, offset); + } while (!weakCompareAndSetByte(o, offset, + current, (byte) (current & mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseAndByteRelease(Object o, long offset, byte mask) { + byte current; + do { + current = getByte(o, offset); + } while (!weakCompareAndSetByteRelease(o, offset, + current, (byte) (current & mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseAndByteAcquire(Object o, long offset, byte mask) { + byte current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getByte(o, offset); + } while (!weakCompareAndSetByteAcquire(o, offset, + current, (byte) (current & mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseXorByte(Object o, long offset, byte mask) { + byte current; + do { + current = getByteVolatile(o, offset); + } while (!weakCompareAndSetByte(o, offset, + current, (byte) (current ^ mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseXorByteRelease(Object o, long offset, byte mask) { + byte current; + do { + current = getByte(o, offset); + } while (!weakCompareAndSetByteRelease(o, offset, + current, (byte) (current ^ mask))); + return current; + } + + @ForceInline + public final byte getAndBitwiseXorByteAcquire(Object o, long offset, byte mask) { + byte current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getByte(o, offset); + } while (!weakCompareAndSetByteAcquire(o, offset, + current, (byte) (current ^ mask))); + return current; + } + + + @ForceInline + public final char getAndBitwiseOrChar(Object o, long offset, char mask) { + return s2c(getAndBitwiseOrShort(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseOrCharRelease(Object o, long offset, char mask) { + return s2c(getAndBitwiseOrShortRelease(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseOrCharAcquire(Object o, long offset, char mask) { + return s2c(getAndBitwiseOrShortAcquire(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseAndChar(Object o, long offset, char mask) { + return s2c(getAndBitwiseAndShort(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseAndCharRelease(Object o, long offset, char mask) { + return s2c(getAndBitwiseAndShortRelease(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseAndCharAcquire(Object o, long offset, char mask) { + return s2c(getAndBitwiseAndShortAcquire(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseXorChar(Object o, long offset, char mask) { + return s2c(getAndBitwiseXorShort(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseXorCharRelease(Object o, long offset, char mask) { + return s2c(getAndBitwiseXorShortRelease(o, offset, c2s(mask))); + } + + @ForceInline + public final char getAndBitwiseXorCharAcquire(Object o, long offset, char mask) { + return s2c(getAndBitwiseXorShortAcquire(o, offset, c2s(mask))); + } + + + @ForceInline + public final short getAndBitwiseOrShort(Object o, long offset, short mask) { + short current; + do { + current = getShortVolatile(o, offset); + } while (!weakCompareAndSetShort(o, offset, + current, (short) (current | mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseOrShortRelease(Object o, long offset, short mask) { + short current; + do { + current = getShort(o, offset); + } while (!weakCompareAndSetShortRelease(o, offset, + current, (short) (current | mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseOrShortAcquire(Object o, long offset, short mask) { + short current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getShort(o, offset); + } while (!weakCompareAndSetShortAcquire(o, offset, + current, (short) (current | mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseAndShort(Object o, long offset, short mask) { + short current; + do { + current = getShortVolatile(o, offset); + } while (!weakCompareAndSetShort(o, offset, + current, (short) (current & mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseAndShortRelease(Object o, long offset, short mask) { + short current; + do { + current = getShort(o, offset); + } while (!weakCompareAndSetShortRelease(o, offset, + current, (short) (current & mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseAndShortAcquire(Object o, long offset, short mask) { + short current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getShort(o, offset); + } while (!weakCompareAndSetShortAcquire(o, offset, + current, (short) (current & mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseXorShort(Object o, long offset, short mask) { + short current; + do { + current = getShortVolatile(o, offset); + } while (!weakCompareAndSetShort(o, offset, + current, (short) (current ^ mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseXorShortRelease(Object o, long offset, short mask) { + short current; + do { + current = getShort(o, offset); + } while (!weakCompareAndSetShortRelease(o, offset, + current, (short) (current ^ mask))); + return current; + } + + @ForceInline + public final short getAndBitwiseXorShortAcquire(Object o, long offset, short mask) { + short current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getShort(o, offset); + } while (!weakCompareAndSetShortAcquire(o, offset, + current, (short) (current ^ mask))); + return current; + } + + + @ForceInline + public final int getAndBitwiseOrInt(Object o, long offset, int mask) { + int current; + do { + current = getIntVolatile(o, offset); + } while (!weakCompareAndSetInt(o, offset, + current, current | mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseOrIntRelease(Object o, long offset, int mask) { + int current; + do { + current = getInt(o, offset); + } while (!weakCompareAndSetIntRelease(o, offset, + current, current | mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseOrIntAcquire(Object o, long offset, int mask) { + int current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getInt(o, offset); + } while (!weakCompareAndSetIntAcquire(o, offset, + current, current | mask)); + return current; + } + + /** + * Atomically replaces the current value of a field or array element within + * the given object with the result of bitwise AND between the current value + * and mask. + * + * @param o object/array to update the field/element in + * @param offset field/element offset + * @param mask the mask value + * @return the previous value + * @since 9 + */ + @ForceInline + public final int getAndBitwiseAndInt(Object o, long offset, int mask) { + int current; + do { + current = getIntVolatile(o, offset); + } while (!weakCompareAndSetInt(o, offset, + current, current & mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseAndIntRelease(Object o, long offset, int mask) { + int current; + do { + current = getInt(o, offset); + } while (!weakCompareAndSetIntRelease(o, offset, + current, current & mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseAndIntAcquire(Object o, long offset, int mask) { + int current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getInt(o, offset); + } while (!weakCompareAndSetIntAcquire(o, offset, + current, current & mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseXorInt(Object o, long offset, int mask) { + int current; + do { + current = getIntVolatile(o, offset); + } while (!weakCompareAndSetInt(o, offset, + current, current ^ mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseXorIntRelease(Object o, long offset, int mask) { + int current; + do { + current = getInt(o, offset); + } while (!weakCompareAndSetIntRelease(o, offset, + current, current ^ mask)); + return current; + } + + @ForceInline + public final int getAndBitwiseXorIntAcquire(Object o, long offset, int mask) { + int current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getInt(o, offset); + } while (!weakCompareAndSetIntAcquire(o, offset, + current, current ^ mask)); + return current; + } + + + @ForceInline + public final long getAndBitwiseOrLong(Object o, long offset, long mask) { + long current; + do { + current = getLongVolatile(o, offset); + } while (!weakCompareAndSetLong(o, offset, + current, current | mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseOrLongRelease(Object o, long offset, long mask) { + long current; + do { + current = getLong(o, offset); + } while (!weakCompareAndSetLongRelease(o, offset, + current, current | mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseOrLongAcquire(Object o, long offset, long mask) { + long current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getLong(o, offset); + } while (!weakCompareAndSetLongAcquire(o, offset, + current, current | mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseAndLong(Object o, long offset, long mask) { + long current; + do { + current = getLongVolatile(o, offset); + } while (!weakCompareAndSetLong(o, offset, + current, current & mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseAndLongRelease(Object o, long offset, long mask) { + long current; + do { + current = getLong(o, offset); + } while (!weakCompareAndSetLongRelease(o, offset, + current, current & mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseAndLongAcquire(Object o, long offset, long mask) { + long current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getLong(o, offset); + } while (!weakCompareAndSetLongAcquire(o, offset, + current, current & mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseXorLong(Object o, long offset, long mask) { + long current; + do { + current = getLongVolatile(o, offset); + } while (!weakCompareAndSetLong(o, offset, + current, current ^ mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseXorLongRelease(Object o, long offset, long mask) { + long current; + do { + current = getLong(o, offset); + } while (!weakCompareAndSetLongRelease(o, offset, + current, current ^ mask)); + return current; + } + + @ForceInline + public final long getAndBitwiseXorLongAcquire(Object o, long offset, long mask) { + long current; + do { + // Plain read, the value is a hint, the acquire CAS does the work + current = getLong(o, offset); + } while (!weakCompareAndSetLongAcquire(o, offset, + current, current ^ mask)); + return current; + } + + + + /** + * Ensures that loads before the fence will not be reordered with loads and + * stores after the fence; a "LoadLoad plus LoadStore barrier". + * + * Corresponds to C11 atomic_thread_fence(memory_order_acquire) + * (an "acquire fence"). + * + * Provides a LoadLoad barrier followed by a LoadStore barrier. + * + * @since 1.8 + */ + @IntrinsicCandidate + public final void loadFence() { + // If loadFence intrinsic is not available, fall back to full fence. + fullFence(); + } + + /** + * Ensures that loads and stores before the fence will not be reordered with + * stores after the fence; a "StoreStore plus LoadStore barrier". + * + * Corresponds to C11 atomic_thread_fence(memory_order_release) + * (a "release fence"). + * + * Provides a StoreStore barrier followed by a LoadStore barrier. + * + * @since 1.8 + */ + @IntrinsicCandidate + public final void storeFence() { + // If storeFence intrinsic is not available, fall back to full fence. + fullFence(); + } + + /** + * Ensures that loads and stores before the fence will not be reordered + * with loads and stores after the fence. Implies the effects of both + * loadFence() and storeFence(), and in addition, the effect of a StoreLoad + * barrier. + * + * Corresponds to C11 atomic_thread_fence(memory_order_seq_cst). + * @since 1.8 + */ + @IntrinsicCandidate + public native void fullFence(); + + /** + * Ensures that loads before the fence will not be reordered with + * loads after the fence. + * + * @implNote + * This method is operationally equivalent to {@link #loadFence()}. + * + * @since 9 + */ + public final void loadLoadFence() { + loadFence(); + } + + /** + * Ensures that stores before the fence will not be reordered with + * stores after the fence. + * + * @since 9 + */ + @IntrinsicCandidate + public final void storeStoreFence() { + // If storeStoreFence intrinsic is not available, fall back to storeFence. + storeFence(); + } + + /** + * Throws IllegalAccessError; for use by the VM for access control + * error support. + * @since 1.8 + */ + private static void throwIllegalAccessError() { + throw new IllegalAccessError(); + } + + /** + * Throws NoSuchMethodError; for use by the VM for redefinition support. + * @since 13 + */ + private static void throwNoSuchMethodError() { + throw new NoSuchMethodError(); + } + + /** + * @return Returns true if the native byte ordering of this + * platform is big-endian, false if it is little-endian. + */ + public final boolean isBigEndian() { return BIG_ENDIAN; } + + /** + * @return Returns true if this platform is capable of performing + * accesses at addresses which are not aligned for the type of the + * primitive type being accessed, false otherwise. + */ + public final boolean unalignedAccess() { return UNALIGNED_ACCESS; } + + /** + * Fetches a value at some byte offset into a given Java object. + * More specifically, fetches a value within the given object + * o at the given offset, or (if o is + * null) from the memory address whose numerical value is the + * given offset.

+ * + * The specification of this method is the same as {@link + * #getLong(Object, long)} except that the offset does not need to + * have been obtained from {@link #objectFieldOffset} on the + * {@link java.lang.reflect.Field} of some Java field. The value + * in memory is raw data, and need not correspond to any Java + * variable. Unless o is null, the value accessed + * must be entirely within the allocated object. The endianness + * of the value in memory is the endianness of the native platform. + * + *

The read will be atomic with respect to the largest power + * of two that divides the GCD of the offset and the storage size. + * For example, getLongUnaligned will make atomic reads of 2-, 4-, + * or 8-byte storage units if the offset is zero mod 2, 4, or 8, + * respectively. There are no other guarantees of atomicity. + *

+ * 8-byte atomicity is only guaranteed on platforms on which + * support atomic accesses to longs. + * + * @param o Java heap object in which the value resides, if any, else + * null + * @param offset The offset in bytes from the start of the object + * @return the value fetched from the indicated object + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + * @since 9 + */ + @IntrinsicCandidate + public final long getLongUnaligned(Object o, long offset) { + if ((offset & 7) == 0) { + return getLong(o, offset); + } else if ((offset & 3) == 0) { + return makeLong(getInt(o, offset), + getInt(o, offset + 4)); + } else if ((offset & 1) == 0) { + return makeLong(getShort(o, offset), + getShort(o, offset + 2), + getShort(o, offset + 4), + getShort(o, offset + 6)); + } else { + return makeLong(getByte(o, offset), + getByte(o, offset + 1), + getByte(o, offset + 2), + getByte(o, offset + 3), + getByte(o, offset + 4), + getByte(o, offset + 5), + getByte(o, offset + 6), + getByte(o, offset + 7)); + } + } + /** + * As {@link #getLongUnaligned(Object, long)} but with an + * additional argument which specifies the endianness of the value + * as stored in memory. + * + * @param o Java heap object in which the variable resides + * @param offset The offset in bytes from the start of the object + * @param bigEndian The endianness of the value + * @return the value fetched from the indicated object + * @since 9 + */ + public final long getLongUnaligned(Object o, long offset, boolean bigEndian) { + return convEndian(bigEndian, getLongUnaligned(o, offset)); + } + + /** @see #getLongUnaligned(Object, long) */ + @IntrinsicCandidate + public final int getIntUnaligned(Object o, long offset) { + if ((offset & 3) == 0) { + return getInt(o, offset); + } else if ((offset & 1) == 0) { + return makeInt(getShort(o, offset), + getShort(o, offset + 2)); + } else { + return makeInt(getByte(o, offset), + getByte(o, offset + 1), + getByte(o, offset + 2), + getByte(o, offset + 3)); + } + } + /** @see #getLongUnaligned(Object, long, boolean) */ + public final int getIntUnaligned(Object o, long offset, boolean bigEndian) { + return convEndian(bigEndian, getIntUnaligned(o, offset)); + } + + /** @see #getLongUnaligned(Object, long) */ + @IntrinsicCandidate + public final short getShortUnaligned(Object o, long offset) { + if ((offset & 1) == 0) { + return getShort(o, offset); + } else { + return makeShort(getByte(o, offset), + getByte(o, offset + 1)); + } + } + /** @see #getLongUnaligned(Object, long, boolean) */ + public final short getShortUnaligned(Object o, long offset, boolean bigEndian) { + return convEndian(bigEndian, getShortUnaligned(o, offset)); + } + + /** @see #getLongUnaligned(Object, long) */ + @IntrinsicCandidate + public final char getCharUnaligned(Object o, long offset) { + if ((offset & 1) == 0) { + return getChar(o, offset); + } else { + return (char)makeShort(getByte(o, offset), + getByte(o, offset + 1)); + } + } + + /** @see #getLongUnaligned(Object, long, boolean) */ + public final char getCharUnaligned(Object o, long offset, boolean bigEndian) { + return convEndian(bigEndian, getCharUnaligned(o, offset)); + } + + /** + * Stores a value at some byte offset into a given Java object. + *

+ * The specification of this method is the same as {@link + * #getLong(Object, long)} except that the offset does not need to + * have been obtained from {@link #objectFieldOffset} on the + * {@link java.lang.reflect.Field} of some Java field. The value + * in memory is raw data, and need not correspond to any Java + * variable. The endianness of the value in memory is the + * endianness of the native platform. + *

+ * The write will be atomic with respect to the largest power of + * two that divides the GCD of the offset and the storage size. + * For example, putLongUnaligned will make atomic writes of 2-, 4-, + * or 8-byte storage units if the offset is zero mod 2, 4, or 8, + * respectively. There are no other guarantees of atomicity. + *

+ * 8-byte atomicity is only guaranteed on platforms on which + * support atomic accesses to longs. + * + * @param o Java heap object in which the value resides, if any, else + * null + * @param offset The offset in bytes from the start of the object + * @param x the value to store + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + * @since 9 + */ + @IntrinsicCandidate + public final void putLongUnaligned(Object o, long offset, long x) { + if ((offset & 7) == 0) { + putLong(o, offset, x); + } else if ((offset & 3) == 0) { + putLongParts(o, offset, + (int)(x >> 0), + (int)(x >>> 32)); + } else if ((offset & 1) == 0) { + putLongParts(o, offset, + (short)(x >>> 0), + (short)(x >>> 16), + (short)(x >>> 32), + (short)(x >>> 48)); + } else { + putLongParts(o, offset, + (byte)(x >>> 0), + (byte)(x >>> 8), + (byte)(x >>> 16), + (byte)(x >>> 24), + (byte)(x >>> 32), + (byte)(x >>> 40), + (byte)(x >>> 48), + (byte)(x >>> 56)); + } + } + + /** + * As {@link #putLongUnaligned(Object, long, long)} but with an additional + * argument which specifies the endianness of the value as stored in memory. + * @param o Java heap object in which the value resides + * @param offset The offset in bytes from the start of the object + * @param x the value to store + * @param bigEndian The endianness of the value + * @throws RuntimeException No defined exceptions are thrown, not even + * {@link NullPointerException} + * @since 9 + */ + public final void putLongUnaligned(Object o, long offset, long x, boolean bigEndian) { + putLongUnaligned(o, offset, convEndian(bigEndian, x)); + } + + /** @see #putLongUnaligned(Object, long, long) */ + @IntrinsicCandidate + public final void putIntUnaligned(Object o, long offset, int x) { + if ((offset & 3) == 0) { + putInt(o, offset, x); + } else if ((offset & 1) == 0) { + putIntParts(o, offset, + (short)(x >> 0), + (short)(x >>> 16)); + } else { + putIntParts(o, offset, + (byte)(x >>> 0), + (byte)(x >>> 8), + (byte)(x >>> 16), + (byte)(x >>> 24)); + } + } + /** @see #putLongUnaligned(Object, long, long, boolean) */ + public final void putIntUnaligned(Object o, long offset, int x, boolean bigEndian) { + putIntUnaligned(o, offset, convEndian(bigEndian, x)); + } + + /** @see #putLongUnaligned(Object, long, long) */ + @IntrinsicCandidate + public final void putShortUnaligned(Object o, long offset, short x) { + if ((offset & 1) == 0) { + putShort(o, offset, x); + } else { + putShortParts(o, offset, + (byte)(x >>> 0), + (byte)(x >>> 8)); + } + } + /** @see #putLongUnaligned(Object, long, long, boolean) */ + public final void putShortUnaligned(Object o, long offset, short x, boolean bigEndian) { + putShortUnaligned(o, offset, convEndian(bigEndian, x)); + } + + /** @see #putLongUnaligned(Object, long, long) */ + @IntrinsicCandidate + public final void putCharUnaligned(Object o, long offset, char x) { + putShortUnaligned(o, offset, (short)x); + } + /** @see #putLongUnaligned(Object, long, long, boolean) */ + public final void putCharUnaligned(Object o, long offset, char x, boolean bigEndian) { + putCharUnaligned(o, offset, convEndian(bigEndian, x)); + } + + private static int pickPos(int top, int pos) { return BIG_ENDIAN ? top - pos : pos; } + + // These methods construct integers from bytes. The byte ordering + // is the native endianness of this platform. + private static long makeLong(byte i0, byte i1, byte i2, byte i3, byte i4, byte i5, byte i6, byte i7) { + return ((toUnsignedLong(i0) << pickPos(56, 0)) + | (toUnsignedLong(i1) << pickPos(56, 8)) + | (toUnsignedLong(i2) << pickPos(56, 16)) + | (toUnsignedLong(i3) << pickPos(56, 24)) + | (toUnsignedLong(i4) << pickPos(56, 32)) + | (toUnsignedLong(i5) << pickPos(56, 40)) + | (toUnsignedLong(i6) << pickPos(56, 48)) + | (toUnsignedLong(i7) << pickPos(56, 56))); + } + private static long makeLong(short i0, short i1, short i2, short i3) { + return ((toUnsignedLong(i0) << pickPos(48, 0)) + | (toUnsignedLong(i1) << pickPos(48, 16)) + | (toUnsignedLong(i2) << pickPos(48, 32)) + | (toUnsignedLong(i3) << pickPos(48, 48))); + } + private static long makeLong(int i0, int i1) { + return (toUnsignedLong(i0) << pickPos(32, 0)) + | (toUnsignedLong(i1) << pickPos(32, 32)); + } + private static int makeInt(short i0, short i1) { + return (toUnsignedInt(i0) << pickPos(16, 0)) + | (toUnsignedInt(i1) << pickPos(16, 16)); + } + private static int makeInt(byte i0, byte i1, byte i2, byte i3) { + return ((toUnsignedInt(i0) << pickPos(24, 0)) + | (toUnsignedInt(i1) << pickPos(24, 8)) + | (toUnsignedInt(i2) << pickPos(24, 16)) + | (toUnsignedInt(i3) << pickPos(24, 24))); + } + private static short makeShort(byte i0, byte i1) { + return (short)((toUnsignedInt(i0) << pickPos(8, 0)) + | (toUnsignedInt(i1) << pickPos(8, 8))); + } + + private static byte pick(byte le, byte be) { return BIG_ENDIAN ? be : le; } + private static short pick(short le, short be) { return BIG_ENDIAN ? be : le; } + private static int pick(int le, int be) { return BIG_ENDIAN ? be : le; } + + // These methods write integers to memory from smaller parts + // provided by their caller. The ordering in which these parts + // are written is the native endianness of this platform. + private void putLongParts(Object o, long offset, byte i0, byte i1, byte i2, byte i3, byte i4, byte i5, byte i6, byte i7) { + putByte(o, offset + 0, pick(i0, i7)); + putByte(o, offset + 1, pick(i1, i6)); + putByte(o, offset + 2, pick(i2, i5)); + putByte(o, offset + 3, pick(i3, i4)); + putByte(o, offset + 4, pick(i4, i3)); + putByte(o, offset + 5, pick(i5, i2)); + putByte(o, offset + 6, pick(i6, i1)); + putByte(o, offset + 7, pick(i7, i0)); + } + private void putLongParts(Object o, long offset, short i0, short i1, short i2, short i3) { + putShort(o, offset + 0, pick(i0, i3)); + putShort(o, offset + 2, pick(i1, i2)); + putShort(o, offset + 4, pick(i2, i1)); + putShort(o, offset + 6, pick(i3, i0)); + } + private void putLongParts(Object o, long offset, int i0, int i1) { + putInt(o, offset + 0, pick(i0, i1)); + putInt(o, offset + 4, pick(i1, i0)); + } + private void putIntParts(Object o, long offset, short i0, short i1) { + putShort(o, offset + 0, pick(i0, i1)); + putShort(o, offset + 2, pick(i1, i0)); + } + private void putIntParts(Object o, long offset, byte i0, byte i1, byte i2, byte i3) { + putByte(o, offset + 0, pick(i0, i3)); + putByte(o, offset + 1, pick(i1, i2)); + putByte(o, offset + 2, pick(i2, i1)); + putByte(o, offset + 3, pick(i3, i0)); + } + private void putShortParts(Object o, long offset, byte i0, byte i1) { + putByte(o, offset + 0, pick(i0, i1)); + putByte(o, offset + 1, pick(i1, i0)); + } + + // Zero-extend an integer + private static int toUnsignedInt(byte n) { return n & 0xff; } + private static int toUnsignedInt(short n) { return n & 0xffff; } + private static long toUnsignedLong(byte n) { return n & 0xffl; } + private static long toUnsignedLong(short n) { return n & 0xffffl; } + private static long toUnsignedLong(int n) { return n & 0xffffffffl; } + + // Maybe byte-reverse an integer + private static char convEndian(boolean big, char n) { return big == BIG_ENDIAN ? n : Character.reverseBytes(n); } + private static short convEndian(boolean big, short n) { return big == BIG_ENDIAN ? n : Short.reverseBytes(n) ; } + private static int convEndian(boolean big, int n) { return big == BIG_ENDIAN ? n : Integer.reverseBytes(n) ; } + private static long convEndian(boolean big, long n) { return big == BIG_ENDIAN ? n : Long.reverseBytes(n) ; } + + + + private native long allocateMemory0(long bytes); + private native long reallocateMemory0(long address, long bytes); + private native void freeMemory0(long address); + @IntrinsicCandidate + private native void setMemory0(Object o, long offset, long bytes, byte value); + @IntrinsicCandidate + private native void copyMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); + private native void copySwapMemory0(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes, long elemSize); + private native long objectFieldOffset0(Field f); + private native long objectFieldOffset1(Class c, String name); + private native long staticFieldOffset0(Field f); + private native Object staticFieldBase0(Field f); + private native boolean shouldBeInitialized0(Class c); + private native void ensureClassInitialized0(Class c); + private native int arrayBaseOffset0(Class arrayClass); + private native int arrayIndexScale0(Class arrayClass); + private native int getLoadAverage0(double[] loadavg, int nelems); + + + /** + * Invokes the given direct byte buffer's cleaner, if any. + * + * @param directBuffer a direct byte buffer + * @throws NullPointerException if {@code directBuffer} is null + * @throws IllegalArgumentException if {@code directBuffer} is non-direct, + * or is a {@link java.nio.Buffer#slice slice}, or is a + * {@link java.nio.Buffer#duplicate duplicate} + */ + public void invokeCleaner(java.nio.ByteBuffer directBuffer) { + if (!directBuffer.isDirect()) + throw new IllegalArgumentException("buffer is non-direct"); + + DirectBuffer db = (DirectBuffer) directBuffer; + if (db.attachment() != null) + throw new IllegalArgumentException("duplicate or slice"); + + Cleaner cleaner = db.cleaner(); + if (cleaner != null) { + cleaner.clean(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/misc/UnsafeConstants.class b/tests/test_data/std/jdk/internal/misc/UnsafeConstants.class new file mode 100644 index 00000000..198baf81 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/UnsafeConstants.class differ diff --git a/tests/test_data/std/jdk/internal/misc/UnsafeConstants.java b/tests/test_data/std/jdk/internal/misc/UnsafeConstants.java new file mode 100644 index 00000000..65ee7b3a --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/UnsafeConstants.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +/** + * A class used to expose details of the underlying hardware that + * configure the operation of class Unsafe. This class is + * package-private as the only intended client is class Unsafe. + * All fields in this class must be static final constants. + * + * @since 13 + * + * @implNote + * + * The JVM injects hardware-specific values into all the static fields + * of this class during JVM initialization. The static initialization + * block is executed when the class is initialized then JVM injection + * updates the fields with the correct constants. The static block + * is required to prevent the fields from being considered constant + * variables, so the field values will be not be compiled directly into + * any class that uses them. + */ + +final class UnsafeConstants { + + /** + * This constructor is private because the class is not meant to + * be instantiated. + */ + private UnsafeConstants() {} + + /** + * The size in bytes of a native pointer, as stored via {@link + * #putAddress}. This value will be either 4 or 8. Note that the + * sizes of other primitive types (as stored in native memory + * blocks) is determined fully by their information content. + * + * @implNote + * The actual value for this field is injected by the JVM. + */ + + static final int ADDRESS_SIZE0; + + /** + * The size in bytes of a native memory page (whatever that is). + * This value will always be a power of two. + * + * @implNote + * The actual value for this field is injected by the JVM. + */ + + static final int PAGE_SIZE; + + /** + * Flag whose value is true if and only if the native endianness + * of this platform is big. + * + * @implNote + * The actual value for this field is injected by the JVM. + */ + + static final boolean BIG_ENDIAN; + + /** + * Flag whose value is true if and only if the platform can + * perform unaligned accesses + * + * @implNote + * The actual value for this field is injected by the JVM. + */ + + static final boolean UNALIGNED_ACCESS; + + /** + * The size of an L1 data cache line which will be either a power + * of two or zero. + * + *

A non-zero value indicates that writeback to memory is + * enabled for the current processor. The value defines the + * natural alignment and size of any data cache line committed to + * memory by a single writeback operation. If data cache line + * writeback is not enabled for the current hardware the field + * will have value 0. + * + * @implNote + * The actual value for this field is injected by the JVM. + */ + + static final int DATA_CACHE_LINE_FLUSH_SIZE; + + static { + ADDRESS_SIZE0 = 0; + PAGE_SIZE = 0; + BIG_ENDIAN = false; + UNALIGNED_ACCESS = false; + DATA_CACHE_LINE_FLUSH_SIZE = 0; + } +} diff --git a/tests/test_data/std/jdk/internal/misc/VM$BufferPool.class b/tests/test_data/std/jdk/internal/misc/VM$BufferPool.class new file mode 100644 index 00000000..ae537aed Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/VM$BufferPool.class differ diff --git a/tests/test_data/std/jdk/internal/misc/VM$BufferPoolsHolder.class b/tests/test_data/std/jdk/internal/misc/VM$BufferPoolsHolder.class new file mode 100644 index 00000000..73d5a1cd Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/VM$BufferPoolsHolder.class differ diff --git a/tests/test_data/std/jdk/internal/misc/VM.class b/tests/test_data/std/jdk/internal/misc/VM.class new file mode 100644 index 00000000..93129e06 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/VM.class differ diff --git a/tests/test_data/std/jdk/internal/misc/VM.java b/tests/test_data/std/jdk/internal/misc/VM.java new file mode 100644 index 00000000..de6f011f --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/VM.java @@ -0,0 +1,507 @@ +/* + * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import static java.lang.Thread.State.*; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.vm.annotation.Stable; +import sun.nio.ch.FileChannelImpl; + +public class VM { + + // the init level when the VM is fully initialized + private static final int JAVA_LANG_SYSTEM_INITED = 1; + private static final int MODULE_SYSTEM_INITED = 2; + private static final int SYSTEM_LOADER_INITIALIZING = 3; + private static final int SYSTEM_BOOTED = 4; + private static final int SYSTEM_SHUTDOWN = 5; + + // 0, 1, 2, ... + private static volatile int initLevel; + private static final Object lock = new Object(); + + /** + * Sets the init level. + * + * @see java.lang.System#initPhase1 + * @see java.lang.System#initPhase2 + * @see java.lang.System#initPhase3 + */ + public static void initLevel(int value) { + synchronized (lock) { + if (value <= initLevel || value > SYSTEM_SHUTDOWN) + throw new InternalError("Bad level: " + value); + initLevel = value; + lock.notifyAll(); + } + } + + /** + * Returns the current init level. + */ + public static int initLevel() { + return initLevel; + } + + /** + * Waits for the init level to get the given value. + */ + public static void awaitInitLevel(int value) throws InterruptedException { + synchronized (lock) { + while (initLevel < value) { + lock.wait(); + } + } + } + + /** + * Returns {@code true} if the module system has been initialized. + * @see java.lang.System#initPhase2 + */ + public static boolean isModuleSystemInited() { + return initLevel >= MODULE_SYSTEM_INITED; + } + + private static @Stable boolean javaLangInvokeInited; + public static void setJavaLangInvokeInited() { + if (javaLangInvokeInited) { + throw new InternalError("java.lang.invoke already inited"); + } + javaLangInvokeInited = true; + } + + public static boolean isJavaLangInvokeInited() { + return javaLangInvokeInited; + } + + /** + * Returns {@code true} if the VM is fully initialized. + */ + public static boolean isBooted() { + return initLevel >= SYSTEM_BOOTED; + } + + /** + * Set shutdown state. Shutdown completes when all registered shutdown + * hooks have been run. + * + * @see java.lang.Shutdown + */ + public static void shutdown() { + initLevel(SYSTEM_SHUTDOWN); + } + + /** + * Returns {@code true} if the VM has been shutdown + */ + public static boolean isShutdown() { + return initLevel == SYSTEM_SHUTDOWN; + } + + // A user-settable upper limit on the maximum amount of allocatable direct + // buffer memory. This value may be changed during VM initialization if + // "java" is launched with "-XX:MaxDirectMemorySize=". + // + // The initial value of this field is arbitrary; during JRE initialization + // it will be reset to the value specified on the command line, if any, + // otherwise to Runtime.getRuntime().maxMemory(). + // + private static long directMemory = 64 * 1024 * 1024; + + // Returns the maximum amount of allocatable direct buffer memory. + // The directMemory variable is initialized during system initialization + // in the saveAndRemoveProperties method. + // + public static long maxDirectMemory() { + return directMemory; + } + + // User-controllable flag that determines if direct buffers should be page + // aligned. The "-XX:+PageAlignDirectMemory" option can be used to force + // buffers, allocated by ByteBuffer.allocateDirect, to be page aligned. + @Stable + private static boolean pageAlignDirectMemory; + + // Returns {@code true} if the direct buffers should be page aligned. This + // variable is initialized by saveAndRemoveProperties. + public static boolean isDirectMemoryPageAligned() { + return pageAlignDirectMemory; + } + + private static int classFileMajorVersion; + private static int classFileMinorVersion; + private static final int PREVIEW_MINOR_VERSION = 65535; + + /** + * Tests if the given version is a supported {@code class} + * file version. + * + * A {@code class} file depends on the preview features of Java SE {@code N} + * if the major version is {@code N} and the minor version is 65535. + * This method returns {@code true} if the given version is a supported + * {@code class} file version regardless of whether the preview features + * are enabled or not. + * + * @jvms 4.1 Table 4.1-A. class file format major versions + */ + public static boolean isSupportedClassFileVersion(int major, int minor) { + if (major < 45 || major > classFileMajorVersion) return false; + // for major version is between 45 and 55 inclusive, the minor version may be any value + if (major < 56) return true; + // otherwise, the minor version must be 0 or 65535 + return minor == 0 || minor == PREVIEW_MINOR_VERSION; + } + + /** + * Tests if the given version is a supported {@code class} + * file version for module descriptor. + * + * major.minor version >= 53.0 + */ + public static boolean isSupportedModuleDescriptorVersion(int major, int minor) { + if (major < 53 || major > classFileMajorVersion) return false; + // for major version is between 45 and 55 inclusive, the minor version may be any value + if (major < 56) return true; + // otherwise, the minor version must be 0 or 65535 + // preview features do not apply to module-info.class but JVMS allows it + return minor == 0 || minor == PREVIEW_MINOR_VERSION; + } + + /** + * Returns true if the given class loader is the bootstrap class loader + * or the platform class loader. + */ + public static boolean isSystemDomainLoader(ClassLoader loader) { + return loader == null || loader == ClassLoader.getPlatformClassLoader(); + } + + /** + * Returns the system property of the specified key saved at + * system initialization time. This method should only be used + * for the system properties that are not changed during runtime. + * + * Note that the saved system properties do not include + * the ones set by java.lang.VersionProps.init(). + */ + public static String getSavedProperty(String key) { + if (savedProps == null) + throw new IllegalStateException("Not yet initialized"); + + return savedProps.get(key); + } + + /** + * Gets an unmodifiable view of the system properties saved at system + * initialization time. This method should only be used + * for the system properties that are not changed during runtime. + * + * Note that the saved system properties do not include + * the ones set by java.lang.VersionProps.init(). + */ + public static Map getSavedProperties() { + if (savedProps == null) + throw new IllegalStateException("Not yet initialized"); + + return Collections.unmodifiableMap(savedProps); + } + + private static Map savedProps; + + // Save a private copy of the system properties and remove + // the system properties that are not intended for public access. + // + // This method can only be invoked during system initialization. + public static void saveProperties(Map props) { + if (initLevel() != 0) + throw new IllegalStateException("Wrong init level"); + + // only main thread is running at this time, so savedProps and + // its content will be correctly published to threads started later + if (savedProps == null) { + savedProps = props; + } + + // Set the maximum amount of direct memory. This value is controlled + // by the vm option -XX:MaxDirectMemorySize=. + // The maximum amount of allocatable direct buffer memory (in bytes) + // from the system property sun.nio.MaxDirectMemorySize set by the VM. + // If not set or set to -1, the max memory will be used + // The system property will be removed. + String s = props.get("sun.nio.MaxDirectMemorySize"); + if (s == null || s.isEmpty() || s.equals("-1")) { + // -XX:MaxDirectMemorySize not given, take default + directMemory = Runtime.getRuntime().maxMemory(); + } else { + long l = Long.parseLong(s); + if (l > -1) + directMemory = l; + } + + // Check if direct buffers should be page aligned + s = props.get("sun.nio.PageAlignDirectMemory"); + if ("true".equals(s)) + pageAlignDirectMemory = true; + + s = props.get("java.class.version"); + int index = s.indexOf('.'); + try { + classFileMajorVersion = Integer.parseInt(s.substring(0, index)); + classFileMinorVersion = Integer.parseInt(s.substring(index + 1)); + } catch (NumberFormatException e) { + throw new InternalError(e); + } + } + + // Initialize any miscellaneous operating system settings that need to be + // set for the class libraries. + // + public static void initializeOSEnvironment() { + if (initLevel() == 0) { + OSEnvironment.initialize(); + } + } + + /* Current count of objects pending for finalization */ + private static volatile int finalRefCount; + + /* Peak count of objects pending for finalization */ + private static volatile int peakFinalRefCount; + + /* + * Gets the number of objects pending for finalization. + * + * @return the number of objects pending for finalization. + */ + public static int getFinalRefCount() { + return finalRefCount; + } + + /* + * Gets the peak number of objects pending for finalization. + * + * @return the peak number of objects pending for finalization. + */ + public static int getPeakFinalRefCount() { + return peakFinalRefCount; + } + + /* + * Add {@code n} to the objects pending for finalization count. + * + * @param n an integer value to be added to the objects pending + * for finalization count + */ + public static void addFinalRefCount(int n) { + // The caller must hold lock to synchronize the update. + + finalRefCount += n; + if (finalRefCount > peakFinalRefCount) { + peakFinalRefCount = finalRefCount; + } + } + + /** + * Returns Thread.State for the given threadStatus + */ + public static Thread.State toThreadState(int threadStatus) { + if ((threadStatus & JVMTI_THREAD_STATE_RUNNABLE) != 0) { + return RUNNABLE; + } else if ((threadStatus & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER) != 0) { + return BLOCKED; + } else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_INDEFINITELY) != 0) { + return WAITING; + } else if ((threadStatus & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT) != 0) { + return TIMED_WAITING; + } else if ((threadStatus & JVMTI_THREAD_STATE_TERMINATED) != 0) { + return TERMINATED; + } else if ((threadStatus & JVMTI_THREAD_STATE_ALIVE) == 0) { + return NEW; + } else { + return RUNNABLE; + } + } + + /* The threadStatus field is set by the VM at state transition + * in the hotspot implementation. Its value is set according to + * the JVM TI specification GetThreadState function. + */ + private static final int JVMTI_THREAD_STATE_ALIVE = 0x0001; + private static final int JVMTI_THREAD_STATE_TERMINATED = 0x0002; + private static final int JVMTI_THREAD_STATE_RUNNABLE = 0x0004; + private static final int JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400; + private static final int JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010; + private static final int JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020; + + /* + * Returns the first user-defined class loader up the execution stack, + * or the platform class loader if only code from the platform or + * bootstrap class loader is on the stack. + */ + public static ClassLoader latestUserDefinedLoader() { + ClassLoader loader = latestUserDefinedLoader0(); + return loader != null ? loader : ClassLoader.getPlatformClassLoader(); + } + + /* + * Returns the first user-defined class loader up the execution stack, + * or null if only code from the platform or bootstrap class loader is + * on the stack. VM does not keep a reference of platform loader and so + * it returns null. + * + * This method should be replaced with StackWalker::walk and then we can + * remove the logic in the VM. + */ + private static native ClassLoader latestUserDefinedLoader0(); + + /** + * Returns {@code true} if we are in a set UID program. + */ + public static boolean isSetUID() { + long uid = getuid(); + long euid = geteuid(); + long gid = getgid(); + long egid = getegid(); + return uid != euid || gid != egid; + } + + /** + * Returns the real user ID of the calling process, + * or -1 if the value is not available. + */ + public static native long getuid(); + + /** + * Returns the effective user ID of the calling process, + * or -1 if the value is not available. + */ + public static native long geteuid(); + + /** + * Returns the real group ID of the calling process, + * or -1 if the value is not available. + */ + public static native long getgid(); + + /** + * Returns the effective group ID of the calling process, + * or -1 if the value is not available. + */ + public static native long getegid(); + + /** + * Get a nanosecond time stamp adjustment in the form of a single long. + * + * This value can be used to create an instant using + * {@link java.time.Instant#ofEpochSecond(long, long) + * java.time.Instant.ofEpochSecond(offsetInSeconds, + * getNanoTimeAdjustment(offsetInSeconds))}. + *

+ * The value returned has the best resolution available to the JVM on + * the current system. + * This is usually down to microseconds - or tenth of microseconds - + * depending on the OS/Hardware and the JVM implementation. + * + * @param offsetInSeconds The offset in seconds from which the nanosecond + * time stamp should be computed. + * + * @apiNote The offset should be recent enough - so that + * {@code offsetInSeconds} is within {@code +/- 2^32} seconds of the + * current UTC time. If the offset is too far off, {@code -1} will be + * returned. As such, {@code -1} must not be considered as a valid + * nano time adjustment, but as an exception value indicating + * that an offset closer to the current time should be used. + * + * @return A nanosecond time stamp adjustment in the form of a single long. + * If the offset is too far off the current time, this method returns -1. + * In that case, the caller should call this method again, passing a + * more accurate offset. + */ + public static native long getNanoTimeAdjustment(long offsetInSeconds); + + /** + * Returns the VM arguments for this runtime environment. + * + * @implNote + * The HotSpot JVM processes the input arguments from multiple sources + * in the following order: + * 1. JAVA_TOOL_OPTIONS environment variable + * 2. Options from JNI Invocation API + * 3. _JAVA_OPTIONS environment variable + * + * If VM options file is specified via -XX:VMOptionsFile, the vm options + * file is read and expanded in place of -XX:VMOptionFile option. + */ + public static native String[] getRuntimeArguments(); + + static { + initialize(); + } + private static native void initialize(); + + /** + * Provides access to information on buffer usage. + */ + public interface BufferPool { + String getName(); + long getCount(); + long getTotalCapacity(); + long getMemoryUsed(); + } + + private static class BufferPoolsHolder { + static final List BUFFER_POOLS; + + static { + ArrayList bufferPools = new ArrayList<>(3); + bufferPools.add(SharedSecrets.getJavaNioAccess().getDirectBufferPool()); + bufferPools.add(FileChannelImpl.getMappedBufferPool()); + bufferPools.add(FileChannelImpl.getSyncMappedBufferPool()); + + BUFFER_POOLS = Collections.unmodifiableList(bufferPools); + } + } + + /** + * @return the list of buffer pools. + */ + public static List getBufferPools() { + return BufferPoolsHolder.BUFFER_POOLS; + } + + /** + * Return the initial value of System.err that was set during VM initialization. + */ + public static PrintStream initialErr() { + return SharedSecrets.getJavaLangAccess().initialSystemErr(); + } +} diff --git a/tests/test_data/std/jdk/internal/misc/VirtualThreads.class b/tests/test_data/std/jdk/internal/misc/VirtualThreads.class new file mode 100644 index 00000000..ffb83813 Binary files /dev/null and b/tests/test_data/std/jdk/internal/misc/VirtualThreads.class differ diff --git a/tests/test_data/std/jdk/internal/misc/VirtualThreads.java b/tests/test_data/std/jdk/internal/misc/VirtualThreads.java new file mode 100644 index 00000000..04a93afa --- /dev/null +++ b/tests/test_data/std/jdk/internal/misc/VirtualThreads.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.RejectedExecutionException; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + +/** + * Defines static methods to support execution in the context of a virtual thread. + */ +public final class VirtualThreads { + private static final JavaLangAccess JLA; + static { + JLA = SharedSecrets.getJavaLangAccess(); + if (JLA == null) { + throw new InternalError("JavaLangAccess not setup"); + } + } + private VirtualThreads() { } + + /** + * Parks the current virtual thread until it is unparked or interrupted. + * If already unparked then the parking permit is consumed and this method + * completes immediately (meaning it doesn't yield). It also completes + * immediately if the interrupt status is set. + * @throws WrongThreadException if the current thread is not a virtual thread + */ + public static void park() { + JLA.parkVirtualThread(); + } + + /** + * Parks the current virtual thread up to the given waiting time or until it + * is unparked or interrupted. If already unparked then the parking permit is + * consumed and this method completes immediately (meaning it doesn't yield). + * It also completes immediately if the interrupt status is set or the waiting + * time is {@code <= 0}. + * @param nanos the maximum number of nanoseconds to wait + * @throws WrongThreadException if the current thread is not a virtual thread + */ + public static void park(long nanos) { + JLA.parkVirtualThread(nanos); + } + + /** + * Parks the current virtual thread until the given deadline or until is is + * unparked or interrupted. If already unparked then the parking permit is + * consumed and this method completes immediately (meaning it doesn't yield). + * It also completes immediately if the interrupt status is set or the + * deadline has past. + * @param deadline absolute time, in milliseconds, from the epoch + * @throws WrongThreadException if the current thread is not a virtual thread + */ + public static void parkUntil(long deadline) { + long millis = deadline - System.currentTimeMillis(); + long nanos = TimeUnit.NANOSECONDS.convert(millis, TimeUnit.MILLISECONDS); + park(nanos); + } + + /** + * Re-enables a virtual thread for scheduling. If the thread was parked then + * it will be unblocked, otherwise its next attempt to park will not block + * @param thread the virtual thread to unpark + * @throws IllegalArgumentException if the thread is not a virtual thread + * @throws RejectedExecutionException if the scheduler cannot accept a task + */ + public static void unpark(Thread thread) { + JLA.unparkVirtualThread(thread); + } +} diff --git a/tests/test_data/std/jdk/internal/module/ArchivedBootLayer.class b/tests/test_data/std/jdk/internal/module/ArchivedBootLayer.class new file mode 100644 index 00000000..6f03738c Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ArchivedBootLayer.class differ diff --git a/tests/test_data/std/jdk/internal/module/ArchivedBootLayer.java b/tests/test_data/std/jdk/internal/module/ArchivedBootLayer.java new file mode 100644 index 00000000..5c806f81 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ArchivedBootLayer.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import jdk.internal.misc.CDS; + +/** + * Used by ModuleBootstrap for archiving the boot layer. + */ +class ArchivedBootLayer { + private static ArchivedBootLayer archivedBootLayer; + + private final ModuleLayer bootLayer; + + private ArchivedBootLayer(ModuleLayer bootLayer) { + this.bootLayer = bootLayer; + } + + ModuleLayer bootLayer() { + return bootLayer; + } + + static ArchivedBootLayer get() { + return archivedBootLayer; + } + + static void archive(ModuleLayer layer) { + archivedBootLayer = new ArchivedBootLayer(layer); + } + + static { + CDS.initializeFromArchive(ArchivedBootLayer.class); + } +} diff --git a/tests/test_data/std/jdk/internal/module/ArchivedModuleGraph.class b/tests/test_data/std/jdk/internal/module/ArchivedModuleGraph.class new file mode 100644 index 00000000..6ecfdf2d Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ArchivedModuleGraph.class differ diff --git a/tests/test_data/std/jdk/internal/module/ArchivedModuleGraph.java b/tests/test_data/std/jdk/internal/module/ArchivedModuleGraph.java new file mode 100644 index 00000000..8b91ab67 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ArchivedModuleGraph.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import java.util.Objects; +import java.util.function.Function; +import java.lang.module.Configuration; +import java.lang.module.ModuleFinder; +import jdk.internal.misc.CDS; + +/** + * Used by ModuleBootstrap for archiving the configuration for the boot layer, + * and the system module finder. + */ +class ArchivedModuleGraph { + private static ArchivedModuleGraph archivedModuleGraph; + + private final boolean hasSplitPackages; + private final boolean hasIncubatorModules; + private final ModuleFinder finder; + private final Configuration configuration; + private final Function classLoaderFunction; + private final String mainModule; + + private ArchivedModuleGraph(boolean hasSplitPackages, + boolean hasIncubatorModules, + ModuleFinder finder, + Configuration configuration, + Function classLoaderFunction, + String mainModule) { + this.hasSplitPackages = hasSplitPackages; + this.hasIncubatorModules = hasIncubatorModules; + this.finder = finder; + this.configuration = configuration; + this.classLoaderFunction = classLoaderFunction; + this.mainModule = mainModule; + } + + ModuleFinder finder() { + return finder; + } + + Configuration configuration() { + return configuration; + } + + Function classLoaderFunction() { + return classLoaderFunction; + } + + boolean hasSplitPackages() { + return hasSplitPackages; + } + + boolean hasIncubatorModules() { + return hasIncubatorModules; + } + + /** + * Returns the ArchivedModuleGraph for the given initial module. + */ + static ArchivedModuleGraph get(String mainModule) { + ArchivedModuleGraph graph = archivedModuleGraph; + if ((graph != null) && Objects.equals(graph.mainModule, mainModule)) { + return graph; + } else { + return null; + } + } + + /** + * Archive the module graph for the given initial module. + */ + static void archive(boolean hasSplitPackages, + boolean hasIncubatorModules, + ModuleFinder finder, + Configuration configuration, + Function classLoaderFunction, + String mainModule) { + archivedModuleGraph = new ArchivedModuleGraph(hasSplitPackages, + hasIncubatorModules, + finder, + configuration, + classLoaderFunction, + mainModule); + } + + static { + CDS.initializeFromArchive(ArchivedModuleGraph.class); + } +} diff --git a/tests/test_data/std/jdk/internal/module/Builder.class b/tests/test_data/std/jdk/internal/module/Builder.class new file mode 100644 index 00000000..e810190c Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/Builder.class differ diff --git a/tests/test_data/std/jdk/internal/module/Builder.java b/tests/test_data/std/jdk/internal/module/Builder.java new file mode 100644 index 00000000..f12e2297 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/Builder.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleDescriptor.Exports; +import java.lang.module.ModuleDescriptor.Opens; +import java.lang.module.ModuleDescriptor.Provides; +import java.lang.module.ModuleDescriptor.Requires; +import java.lang.module.ModuleDescriptor.Version; +import java.util.List; +import java.util.Set; + +import jdk.internal.access.JavaLangModuleAccess; +import jdk.internal.access.SharedSecrets; + +/** + * This builder is optimized for reconstituting the {@code ModuleDescriptor}s + * for system modules. The validation should be done at jlink time. + * + * 1. skip name validation + * 2. ignores dependency hashes. + * 3. ModuleDescriptor skips the defensive copy and directly uses the + * sets/maps created in this Builder. + * + * SystemModules should contain modules for the boot layer. + */ +final class Builder { + private static final JavaLangModuleAccess JLMA = + SharedSecrets.getJavaLangModuleAccess(); + + // Static cache of the most recently seen Version to cheaply deduplicate + // most Version objects. JDK modules have the same version. + static Version cachedVersion; + + /** + * Returns a {@link Requires} for a dependence on a module with the given + * (and possibly empty) set of modifiers, and optionally the version + * recorded at compile time. + */ + public static Requires newRequires(Set mods, + String mn, + String compiledVersion) + { + Version version = null; + if (compiledVersion != null) { + // use the cached version if the same version string + Version ver = cachedVersion; + if (ver != null && compiledVersion.equals(ver.toString())) { + version = ver; + } else { + version = Version.parse(compiledVersion); + } + } + return JLMA.newRequires(mods, mn, version); + } + + /** + * Returns a {@link Requires} for a dependence on a module with the given + * (and possibly empty) set of modifiers, and optionally the version + * recorded at compile time. + */ + public static Requires newRequires(Set mods, + String mn) + { + return newRequires(mods, mn, null); + } + + /** + * Returns a {@link Exports} for a qualified export, with + * the given (and possibly empty) set of modifiers, + * to a set of target modules. + */ + public static Exports newExports(Set ms, + String pn, + Set targets) { + return JLMA.newExports(ms, pn, targets); + } + + /** + * Returns an {@link Opens} for an unqualified open with a given set of + * modifiers. + */ + public static Opens newOpens(Set ms, String pn) { + return JLMA.newOpens(ms, pn); + } + + /** + * Returns an {@link Opens} for a qualified opens, with + * the given (and possibly empty) set of modifiers, + * to a set of target modules. + */ + public static Opens newOpens(Set ms, + String pn, + Set targets) { + return JLMA.newOpens(ms, pn, targets); + } + + /** + * Returns a {@link Exports} for an unqualified export with a given set + * of modifiers. + */ + public static Exports newExports(Set ms, String pn) { + return JLMA.newExports(ms, pn); + } + + /** + * Returns a {@link Provides} for a service with a given list of + * implementation classes. + */ + public static Provides newProvides(String st, List pcs) { + return JLMA.newProvides(st, pcs); + } + + final String name; + boolean open, synthetic, mandated; + Set requires; + Set exports; + Set opens; + Set packages; + Set uses; + Set provides; + Version version; + String mainClass; + + Builder(String name) { + this.name = name; + this.requires = Set.of(); + this.exports = Set.of(); + this.opens = Set.of(); + this.provides = Set.of(); + this.uses = Set.of(); + } + + Builder open(boolean value) { + this.open = value; + return this; + } + + Builder synthetic(boolean value) { + this.synthetic = value; + return this; + } + + Builder mandated(boolean value) { + this.mandated = value; + return this; + } + + /** + * Sets module exports. + */ + public Builder exports(Exports[] exports) { + this.exports = Set.of(exports); + return this; + } + + /** + * Sets module opens. + */ + public Builder opens(Opens[] opens) { + this.opens = Set.of(opens); + return this; + } + + /** + * Sets module requires. + */ + public Builder requires(Requires[] requires) { + this.requires = Set.of(requires); + return this; + } + + /** + * Adds a set of (possible empty) packages. + */ + public Builder packages(Set packages) { + this.packages = packages; + return this; + } + + /** + * Sets the set of service dependences. + */ + public Builder uses(Set uses) { + this.uses = uses; + return this; + } + + /** + * Sets module provides. + */ + public Builder provides(Provides[] provides) { + this.provides = Set.of(provides); + return this; + } + + /** + * Sets the module version. + * + * @throws IllegalArgumentException if {@code v} is null or cannot be + * parsed as a version string + * + * @see Version#parse(String) + */ + public Builder version(String v) { + Version ver = cachedVersion; + if (ver != null && v.equals(ver.toString())) { + version = ver; + } else { + cachedVersion = version = Version.parse(v); + } + return this; + } + + /** + * Sets the module main class. + */ + public Builder mainClass(String mc) { + mainClass = mc; + return this; + } + + /** + * Returns an immutable set of the module modifiers derived from the flags. + */ + private Set modifiers() { + int n = 0; + if (open) n++; + if (synthetic) n++; + if (mandated) n++; + if (n == 0) { + return Set.of(); + } else { + ModuleDescriptor.Modifier[] mods = new ModuleDescriptor.Modifier[n]; + if (open) mods[--n] = ModuleDescriptor.Modifier.OPEN; + if (synthetic) mods[--n] = ModuleDescriptor.Modifier.SYNTHETIC; + if (mandated) mods[--n] = ModuleDescriptor.Modifier.MANDATED; + return Set.of(mods); + } + } + + /** + * Builds a {@code ModuleDescriptor} from the components. + */ + public ModuleDescriptor build(int hashCode) { + assert name != null; + return JLMA.newModuleDescriptor(name, + version, + modifiers(), + requires, + exports, + opens, + uses, + provides, + packages, + mainClass, + hashCode); + } +} diff --git a/tests/test_data/std/jdk/internal/module/Checks.class b/tests/test_data/std/jdk/internal/module/Checks.class new file mode 100644 index 00000000..7bacba63 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/Checks.class differ diff --git a/tests/test_data/std/jdk/internal/module/Checks.java b/tests/test_data/std/jdk/internal/module/Checks.java new file mode 100644 index 00000000..7965391f --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/Checks.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.util.Set; + +/** + * Utility class for checking module, package, and class names. + */ + +public final class Checks { + + private Checks() { } + + /** + * Checks a name to ensure that it's a legal module name. + * + * @throws IllegalArgumentException if name is null or not a legal + * module name + */ + public static String requireModuleName(String name) { + if (name == null) + throw new IllegalArgumentException("Null module name"); + int next; + int off = 0; + while ((next = name.indexOf('.', off)) != -1) { + String id = name.substring(off, next); + if (!isJavaIdentifier(id)) { + throw new IllegalArgumentException(name + ": Invalid module name" + + ": '" + id + "' is not a Java identifier"); + } + off = next+1; + } + String last = name.substring(off); + if (!isJavaIdentifier(last)) { + throw new IllegalArgumentException(name + ": Invalid module name" + + ": '" + last + "' is not a Java identifier"); + } + return name; + } + + /** + * Checks a name to ensure that it's a legal package name. + * + * @throws IllegalArgumentException if name is null or not a legal + * package name + */ + public static String requirePackageName(String name) { + return requireTypeName("package name", name); + } + + /** + * Returns {@code true} if the given name is a legal package name. + */ + public static boolean isPackageName(String name) { + return isTypeName(name); + } + + /** + * Checks a name to ensure that it's a legal qualified class name + * + * @throws IllegalArgumentException if name is null or not a legal + * qualified class name + */ + public static String requireServiceTypeName(String name) { + return requireQualifiedClassName("service type name", name); + } + + /** + * Checks a name to ensure that it's a legal qualified class name. + * + * @throws IllegalArgumentException if name is null or not a legal + * qualified class name + */ + public static String requireServiceProviderName(String name) { + return requireQualifiedClassName("service provider name", name); + } + + /** + * Checks a name to ensure that it's a legal qualified class name in + * a named package. + * + * @throws IllegalArgumentException if name is null or not a legal + * qualified class name in a named package + */ + public static String requireQualifiedClassName(String what, String name) { + requireTypeName(what, name); + if (name.indexOf('.') == -1) + throw new IllegalArgumentException(name + ": is not a qualified name of" + + " a Java class in a named package"); + return name; + } + + /** + * Returns {@code true} if the given name is a legal class name. + */ + public static boolean isClassName(String name) { + return isTypeName(name); + } + + /** + * Returns {@code true} if the given name is a legal type name. + */ + private static boolean isTypeName(String name) { + int next; + int off = 0; + while ((next = name.indexOf('.', off)) != -1) { + String id = name.substring(off, next); + if (!isJavaIdentifier(id)) + return false; + off = next+1; + } + String last = name.substring(off); + return isJavaIdentifier(last); + } + + /** + * Checks if the given name is a legal type name. + * + * @throws IllegalArgumentException if name is null or not a legal + * type name + */ + private static String requireTypeName(String what, String name) { + if (name == null) + throw new IllegalArgumentException("Null " + what); + int next; + int off = 0; + while ((next = name.indexOf('.', off)) != -1) { + String id = name.substring(off, next); + if (!isJavaIdentifier(id)) { + throw new IllegalArgumentException(name + ": Invalid " + what + + ": '" + id + "' is not a Java identifier"); + } + off = next + 1; + } + String last = name.substring(off); + if (!isJavaIdentifier(last)) { + throw new IllegalArgumentException(name + ": Invalid " + what + + ": '" + last + "' is not a Java identifier"); + } + return name; + } + + /** + * Returns true if the given string is a legal Java identifier, + * otherwise false. + */ + public static boolean isJavaIdentifier(String str) { + if (str.isEmpty() || RESERVED.contains(str)) + return false; + + int first = Character.codePointAt(str, 0); + if (!Character.isJavaIdentifierStart(first)) + return false; + + int i = Character.charCount(first); + while (i < str.length()) { + int cp = Character.codePointAt(str, i); + if (!Character.isJavaIdentifierPart(cp)) + return false; + i += Character.charCount(cp); + } + + return true; + } + + // keywords, boolean and null literals, not allowed in identifiers + private static final Set RESERVED = Set.of( + "abstract", + "assert", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "default", + "do", + "double", + "else", + "enum", + "extends", + "final", + "finally", + "float", + "for", + "goto", + "if", + "implements", + "import", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "strictfp", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "try", + "void", + "volatile", + "while", + "true", + "false", + "null", + "_" + ); +} diff --git a/tests/test_data/std/jdk/internal/module/ClassFileConstants.class b/tests/test_data/std/jdk/internal/module/ClassFileConstants.class new file mode 100644 index 00000000..703e5448 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ClassFileConstants.class differ diff --git a/tests/test_data/std/jdk/internal/module/ClassFileConstants.java b/tests/test_data/std/jdk/internal/module/ClassFileConstants.java new file mode 100644 index 00000000..66e241ee --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ClassFileConstants.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + + +// Constants in module-info.class files + +public class ClassFileConstants { + + private ClassFileConstants() { } + + // Attribute names + public static final String MODULE = "Module"; + public static final String SOURCE_FILE = "SourceFile"; + public static final String SDE = "SourceDebugExtension"; + + public static final String MODULE_PACKAGES = "ModulePackages"; + public static final String MODULE_MAIN_CLASS = "ModuleMainClass"; + public static final String MODULE_TARGET = "ModuleTarget"; + public static final String MODULE_HASHES = "ModuleHashes"; + public static final String MODULE_RESOLUTION = "ModuleResolution"; + + // access, requires, exports, and opens flags + public static final int ACC_MODULE = 0x8000; + public static final int ACC_OPEN = 0x0020; + public static final int ACC_TRANSITIVE = 0x0020; + public static final int ACC_STATIC_PHASE = 0x0040; + public static final int ACC_SYNTHETIC = 0x1000; + public static final int ACC_MANDATED = 0x8000; + + // ModuleResolution_attribute resolution flags + public static final int DO_NOT_RESOLVE_BY_DEFAULT = 0x0001; + public static final int WARN_DEPRECATED = 0x0002; + public static final int WARN_DEPRECATED_FOR_REMOVAL = 0x0004; + public static final int WARN_INCUBATING = 0x0008; + +} diff --git a/tests/test_data/std/jdk/internal/module/DefaultRoots.class b/tests/test_data/std/jdk/internal/module/DefaultRoots.class new file mode 100644 index 00000000..18d0d311 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/DefaultRoots.class differ diff --git a/tests/test_data/std/jdk/internal/module/DefaultRoots.java b/tests/test_data/std/jdk/internal/module/DefaultRoots.java new file mode 100644 index 00000000..54c7a2c1 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/DefaultRoots.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Defines methods to compute the default set of root modules for the unnamed + * module. + */ + +public final class DefaultRoots { + private DefaultRoots() { } + + /** + * Returns the default set of root modules for the unnamed module from the + * modules observable with the intersection of two module finders. + * + * The first module finder should be the module finder that finds modules on + * the upgrade module path or among the system modules. The second module + * finder should be the module finder that finds all modules on the module + * path, or a subset of when using --limit-modules. + */ + static Set compute(ModuleFinder finder1, ModuleFinder finder2) { + return finder1.findAll().stream() + .filter(mref -> !ModuleResolution.doNotResolveByDefault(mref)) + .map(ModuleReference::descriptor) + .filter(descriptor -> finder2.find(descriptor.name()).isPresent() + && exportsAPI(descriptor)) + .map(ModuleDescriptor::name) + .collect(Collectors.toSet()); + } + + /** + * Returns the default set of root modules for the unnamed module from the + * modules observable with the given module finder. + * + * This method is used by the jlink system modules plugin. + */ + public static Set compute(ModuleFinder finder) { + return compute(finder, finder); + } + + /** + * Returns true if the given module exports a package to all modules + */ + private static boolean exportsAPI(ModuleDescriptor descriptor) { + return descriptor.exports() + .stream() + .filter(e -> !e.isQualified()) + .findAny() + .isPresent(); + } +} diff --git a/tests/test_data/std/jdk/internal/module/ExplodedSystemModules.class b/tests/test_data/std/jdk/internal/module/ExplodedSystemModules.class new file mode 100644 index 00000000..a2212e8b Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ExplodedSystemModules.class differ diff --git a/tests/test_data/std/jdk/internal/module/ExplodedSystemModules.java b/tests/test_data/std/jdk/internal/module/ExplodedSystemModules.java new file mode 100644 index 00000000..c276647e --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ExplodedSystemModules.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.module.ModuleDescriptor; +import java.util.Map; +import java.util.Set; + +/** + * A dummy SystemModules for use with exploded builds or testing. + */ + +class ExplodedSystemModules implements SystemModules { + @Override + public boolean hasSplitPackages() { + return true; // not known + } + + @Override + public boolean hasIncubatorModules() { + return true; // not known + } + + @Override + public ModuleDescriptor[] moduleDescriptors() { + throw new InternalError(); + } + + @Override + public ModuleTarget[] moduleTargets() { + throw new InternalError(); + } + + @Override + public ModuleHashes[] moduleHashes() { + throw new InternalError(); + } + + @Override + public ModuleResolution[] moduleResolutions() { + throw new InternalError(); + } + + @Override + public Map> moduleReads() { + throw new InternalError(); + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleBootstrap$1.class b/tests/test_data/std/jdk/internal/module/ModuleBootstrap$1.class new file mode 100644 index 00000000..b4b64071 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleBootstrap$1.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleBootstrap$Counters.class b/tests/test_data/std/jdk/internal/module/ModuleBootstrap$Counters.class new file mode 100644 index 00000000..3a0d61a5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleBootstrap$Counters.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleBootstrap$SafeModuleFinder.class b/tests/test_data/std/jdk/internal/module/ModuleBootstrap$SafeModuleFinder.class new file mode 100644 index 00000000..2fd1cd54 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleBootstrap$SafeModuleFinder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleBootstrap.class b/tests/test_data/std/jdk/internal/module/ModuleBootstrap.class new file mode 100644 index 00000000..0c163aab Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleBootstrap.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleBootstrap.java b/tests/test_data/std/jdk/internal/module/ModuleBootstrap.java new file mode 100644 index 00000000..b97b0a2d --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleBootstrap.java @@ -0,0 +1,1088 @@ +/* + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.File; +import java.io.PrintStream; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.JavaLangModuleAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.loader.BootLoader; +import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.loader.ClassLoaders; +import jdk.internal.misc.CDS; +import jdk.internal.perf.PerfCounter; + +/** + * Initializes/boots the module system. + * + * The {@link #boot() boot} method is called early in the startup to initialize + * the module system. In summary, the boot method creates a Configuration by + * resolving a set of module names specified via the launcher (or equivalent) + * -m and --add-modules options. The modules are located on a module path that + * is constructed from the upgrade module path, system modules, and application + * module path. The Configuration is instantiated as the boot layer with each + * module in the configuration defined to a class loader. + */ + +public final class ModuleBootstrap { + private ModuleBootstrap() { } + + private static final String JAVA_BASE = "java.base"; + + // the token for "all default modules" + private static final String ALL_DEFAULT = "ALL-DEFAULT"; + + // the token for "all unnamed modules" + private static final String ALL_UNNAMED = "ALL-UNNAMED"; + + // the token for "all system modules" + private static final String ALL_SYSTEM = "ALL-SYSTEM"; + + // the token for "all modules on the module path" + private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; + + // access to java.lang/module + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess(); + + // The ModulePatcher for the initial configuration + private static final ModulePatcher patcher = initModulePatcher(); + + /** + * Returns the ModulePatcher for the initial configuration. + */ + public static ModulePatcher patcher() { + return patcher; + } + + // ModuleFinders for the initial configuration + private static volatile ModuleFinder unlimitedFinder; + private static volatile ModuleFinder limitedFinder; + + /** + * Returns the ModuleFinder for the initial configuration before + * observability is limited by the --limit-modules command line option. + * + * @apiNote Used to support locating modules {@code java.instrument} and + * {@code jdk.management.agent} modules when they are loaded dynamically. + */ + public static ModuleFinder unlimitedFinder() { + ModuleFinder finder = unlimitedFinder; + if (finder == null) { + return ModuleFinder.ofSystem(); + } else { + return finder; + } + } + + /** + * Returns the ModuleFinder for the initial configuration. + * + * @apiNote Used to support "{@code java --list-modules}". + */ + public static ModuleFinder limitedFinder() { + ModuleFinder finder = limitedFinder; + if (finder == null) { + return unlimitedFinder(); + } else { + return finder; + } + } + + /** + * Returns true if the archived boot layer can be used. The system properties + * are checked in the order that they are used by boot2. + */ + private static boolean canUseArchivedBootLayer() { + return getProperty("jdk.module.upgrade.path") == null && + getProperty("jdk.module.path") == null && + getProperty("jdk.module.patch.0") == null && // --patch-module + getProperty("jdk.module.addmods.0") == null && // --add-modules + getProperty("jdk.module.limitmods") == null && // --limit-modules + getProperty("jdk.module.addreads.0") == null && // --add-reads + getProperty("jdk.module.addexports.0") == null && // --add-exports + getProperty("jdk.module.addopens.0") == null; // --add-opens + } + + /** + * Initialize the module system, returning the boot layer. The boot layer + * is obtained from the CDS archive if possible, otherwise it is generated + * from the module graph. + * + * @see java.lang.System#initPhase2(boolean, boolean) + */ + public static ModuleLayer boot() { + Counters.start(); + + ModuleLayer bootLayer; + ArchivedBootLayer archivedBootLayer = ArchivedBootLayer.get(); + if (archivedBootLayer != null) { + assert canUseArchivedBootLayer(); + bootLayer = archivedBootLayer.bootLayer(); + BootLoader.getUnnamedModule(); // trigger of BootLoader. + CDS.defineArchivedModules(ClassLoaders.platformClassLoader(), ClassLoaders.appClassLoader()); + + // assume boot layer has at least one module providing a service + // that is mapped to the application class loader. + JLA.bindToLoader(bootLayer, ClassLoaders.appClassLoader()); + } else { + bootLayer = boot2(); + } + + Counters.publish("jdk.module.boot.totalTime"); + return bootLayer; + } + + private static ModuleLayer boot2() { + // Step 0: Command line options + + ModuleFinder upgradeModulePath = finderFor("jdk.module.upgrade.path"); + ModuleFinder appModulePath = finderFor("jdk.module.path"); + boolean isPatched = patcher.hasPatches(); + String mainModule = System.getProperty("jdk.module.main"); + Set addModules = addModules(); + Set limitModules = limitModules(); + + PrintStream traceOutput = null; + String trace = getAndRemoveProperty("jdk.module.showModuleResolution"); + if (trace != null && Boolean.parseBoolean(trace)) + traceOutput = System.out; + + Counters.add("jdk.module.boot.0.commandLineTime"); + + // Step 1: The observable system modules, either all system modules + // or the system modules pre-generated for the initial module (the + // initial module may be the unnamed module). If the system modules + // are pre-generated for the initial module then resolution can be + // skipped. + + SystemModules systemModules = null; + ModuleFinder systemModuleFinder; + + boolean haveModulePath = (appModulePath != null || upgradeModulePath != null); + boolean needResolution = true; + boolean canArchive = false; + boolean hasSplitPackages; + boolean hasIncubatorModules; + + // If the java heap was archived at CDS dump time and the environment + // at dump time matches the current environment then use the archived + // system modules and finder. + ArchivedModuleGraph archivedModuleGraph = ArchivedModuleGraph.get(mainModule); + if (archivedModuleGraph != null + && !haveModulePath + && addModules.isEmpty() + && limitModules.isEmpty() + && !isPatched) { + systemModuleFinder = archivedModuleGraph.finder(); + hasSplitPackages = archivedModuleGraph.hasSplitPackages(); + hasIncubatorModules = archivedModuleGraph.hasIncubatorModules(); + needResolution = (traceOutput != null); + } else { + if (!haveModulePath && addModules.isEmpty() && limitModules.isEmpty()) { + systemModules = SystemModuleFinders.systemModules(mainModule); + if (systemModules != null && !isPatched) { + needResolution = (traceOutput != null); + if (CDS.isDumpingStaticArchive()) + canArchive = true; + } + } + if (systemModules == null) { + // all system modules are observable + systemModules = SystemModuleFinders.allSystemModules(); + } + if (systemModules != null) { + // images build + systemModuleFinder = SystemModuleFinders.of(systemModules); + } else { + // exploded build or testing + systemModules = new ExplodedSystemModules(); + systemModuleFinder = SystemModuleFinders.ofSystem(); + } + + hasSplitPackages = systemModules.hasSplitPackages(); + hasIncubatorModules = systemModules.hasIncubatorModules(); + // not using the archived module graph - avoid accidental use + archivedModuleGraph = null; + } + + Counters.add("jdk.module.boot.1.systemModulesTime"); + + // Step 2: Define and load java.base. This patches all classes loaded + // to date so that they are members of java.base. Once java.base is + // loaded then resources in java.base are available for error messages + // needed from here on. + + ModuleReference base = systemModuleFinder.find(JAVA_BASE).orElse(null); + if (base == null) + throw new InternalError(JAVA_BASE + " not found"); + URI baseUri = base.location().orElse(null); + if (baseUri == null) + throw new InternalError(JAVA_BASE + " does not have a location"); + BootLoader.loadModule(base); + + Module baseModule = Modules.defineModule(null, base.descriptor(), baseUri); + JLA.addEnableNativeAccess(baseModule); + + // Step 2a: Scan all modules when --validate-modules specified + + if (getAndRemoveProperty("jdk.module.validation") != null) { + int errors = ModulePathValidator.scanAllModules(System.out); + if (errors > 0) { + fail("Validation of module path failed"); + } + } + + Counters.add("jdk.module.boot.2.defineBaseTime"); + + // Step 3: If resolution is needed then create the module finder and + // the set of root modules to resolve. + + ModuleFinder savedModuleFinder = null; + ModuleFinder finder; + Set roots; + if (needResolution) { + + // upgraded modules override the modules in the run-time image + if (upgradeModulePath != null) + systemModuleFinder = ModuleFinder.compose(upgradeModulePath, + systemModuleFinder); + + // The module finder: [--upgrade-module-path] system [--module-path] + if (appModulePath != null) { + finder = ModuleFinder.compose(systemModuleFinder, appModulePath); + } else { + finder = systemModuleFinder; + } + + // The root modules to resolve + roots = new HashSet<>(); + + // launcher -m option to specify the main/initial module + if (mainModule != null) + roots.add(mainModule); + + // additional module(s) specified by --add-modules + boolean addAllDefaultModules = false; + boolean addAllSystemModules = false; + boolean addAllApplicationModules = false; + for (String mod : addModules) { + switch (mod) { + case ALL_DEFAULT: + addAllDefaultModules = true; + break; + case ALL_SYSTEM: + addAllSystemModules = true; + break; + case ALL_MODULE_PATH: + addAllApplicationModules = true; + break; + default: + roots.add(mod); + } + } + + // --limit-modules + savedModuleFinder = finder; + if (!limitModules.isEmpty()) { + finder = limitFinder(finder, limitModules, roots); + } + + // If there is no initial module specified then assume that the initial + // module is the unnamed module of the application class loader. This + // is implemented by resolving all observable modules that export an + // API. Modules that have the DO_NOT_RESOLVE_BY_DEFAULT bit set in + // their ModuleResolution attribute flags are excluded from the + // default set of roots. + if (mainModule == null || addAllDefaultModules) { + roots.addAll(DefaultRoots.compute(systemModuleFinder, finder)); + } + + // If `--add-modules ALL-SYSTEM` is specified then all observable system + // modules will be resolved. + if (addAllSystemModules) { + ModuleFinder f = finder; // observable modules + systemModuleFinder.findAll() + .stream() + .map(ModuleReference::descriptor) + .map(ModuleDescriptor::name) + .filter(mn -> f.find(mn).isPresent()) // observable + .forEach(mn -> roots.add(mn)); + } + + // If `--add-modules ALL-MODULE-PATH` is specified then all observable + // modules on the application module path will be resolved. + if (appModulePath != null && addAllApplicationModules) { + ModuleFinder f = finder; // observable modules + appModulePath.findAll() + .stream() + .map(ModuleReference::descriptor) + .map(ModuleDescriptor::name) + .filter(mn -> f.find(mn).isPresent()) // observable + .forEach(mn -> roots.add(mn)); + } + } else { + // no resolution case + finder = systemModuleFinder; + roots = null; + } + + Counters.add("jdk.module.boot.3.optionsAndRootsTime"); + + // Step 4: Resolve the root modules, with service binding, to create + // the configuration for the boot layer. If resolution is not needed + // then create the configuration for the boot layer from the + // readability graph created at link time. + + Configuration cf; + if (needResolution) { + cf = Modules.newBootLayerConfiguration(finder, roots, traceOutput); + } else { + if (archivedModuleGraph != null) { + cf = archivedModuleGraph.configuration(); + } else { + Map> map = systemModules.moduleReads(); + cf = JLMA.newConfiguration(systemModuleFinder, map); + } + } + + // check that modules specified to --patch-module are resolved + if (isPatched) { + patcher.patchedModules() + .stream() + .filter(mn -> cf.findModule(mn).isEmpty()) + .forEach(mn -> warnUnknownModule(PATCH_MODULE, mn)); + } + + Counters.add("jdk.module.boot.4.resolveTime"); + + // Step 5: Map the modules in the configuration to class loaders. + // The static configuration provides the mapping of standard and JDK + // modules to the boot and platform loaders. All other modules (JDK + // tool modules, and both explicit and automatic modules on the + // application module path) are defined to the application class + // loader. + + // mapping of modules to class loaders + Function clf; + if (archivedModuleGraph != null) { + clf = archivedModuleGraph.classLoaderFunction(); + } else { + clf = ModuleLoaderMap.mappingFunction(cf); + } + + // check that all modules to be mapped to the boot loader will be + // loaded from the runtime image + if (haveModulePath) { + for (ResolvedModule resolvedModule : cf.modules()) { + ModuleReference mref = resolvedModule.reference(); + String name = mref.descriptor().name(); + ClassLoader cl = clf.apply(name); + if (cl == null) { + if (upgradeModulePath != null + && upgradeModulePath.find(name).isPresent()) + fail(name + ": cannot be loaded from upgrade module path"); + if (systemModuleFinder.find(name).isEmpty()) + fail(name + ": cannot be loaded from application module path"); + } + } + } + + // check for split packages in the modules mapped to the built-in loaders + if (hasSplitPackages || isPatched || haveModulePath) { + checkSplitPackages(cf, clf); + } + + // load/register the modules with the built-in class loaders + loadModules(cf, clf); + Counters.add("jdk.module.boot.5.loadModulesTime"); + + // Step 6: Define all modules to the VM + + ModuleLayer bootLayer = ModuleLayer.empty().defineModules(cf, clf); + Counters.add("jdk.module.boot.6.layerCreateTime"); + + // Step 7: Miscellaneous + + // check incubating status + if (hasIncubatorModules || haveModulePath) { + checkIncubatingStatus(cf); + } + + // --add-reads, --add-exports/--add-opens + addExtraReads(bootLayer); + boolean extraExportsOrOpens = addExtraExportsAndOpens(bootLayer); + + // add enable native access + addEnableNativeAccess(bootLayer); + + Counters.add("jdk.module.boot.7.adjustModulesTime"); + + // save module finders for later use + if (savedModuleFinder != null) { + unlimitedFinder = new SafeModuleFinder(savedModuleFinder); + if (savedModuleFinder != finder) + limitedFinder = new SafeModuleFinder(finder); + } + + // If -Xshare:dump and mainModule are specified, check if the mainModule + // is in the runtime image and not on the upgrade module path. If so, + // set canArchive to true so that the module graph can be archived. + if (CDS.isDumpingStaticArchive() && mainModule != null) { + String scheme = systemModuleFinder.find(mainModule) + .stream() + .map(ModuleReference::location) + .flatMap(Optional::stream) + .findAny() + .map(URI::getScheme) + .orElse(null); + if ("jrt".equalsIgnoreCase(scheme)) { + canArchive = true; + } + } + + // Archive module graph and boot layer can be archived at CDS dump time. + if (canArchive) { + ArchivedModuleGraph.archive(hasSplitPackages, + hasIncubatorModules, + systemModuleFinder, + cf, + clf, + mainModule); + if (!hasSplitPackages && !hasIncubatorModules) { + ArchivedBootLayer.archive(bootLayer); + } + } + + return bootLayer; + } + + /** + * Load/register the modules to the built-in class loaders. + */ + private static void loadModules(Configuration cf, + Function clf) { + for (ResolvedModule resolvedModule : cf.modules()) { + ModuleReference mref = resolvedModule.reference(); + String name = resolvedModule.name(); + ClassLoader loader = clf.apply(name); + if (loader == null) { + // skip java.base as it is already loaded + if (!name.equals(JAVA_BASE)) { + BootLoader.loadModule(mref); + } + } else if (loader instanceof BuiltinClassLoader) { + ((BuiltinClassLoader) loader).loadModule(mref); + } + } + } + + /** + * Checks for split packages between modules defined to the built-in class + * loaders. + */ + private static void checkSplitPackages(Configuration cf, + Function clf) { + Map packageToModule = new HashMap<>(); + for (ResolvedModule resolvedModule : cf.modules()) { + ModuleDescriptor descriptor = resolvedModule.reference().descriptor(); + String name = descriptor.name(); + ClassLoader loader = clf.apply(name); + if (loader == null || loader instanceof BuiltinClassLoader) { + for (String p : descriptor.packages()) { + String other = packageToModule.putIfAbsent(p, name); + if (other != null) { + String msg = "Package " + p + " in both module " + + name + " and module " + other; + throw new LayerInstantiationException(msg); + } + } + } + } + } + + /** + * Returns a ModuleFinder that limits observability to the given root + * modules, their transitive dependences, plus a set of other modules. + */ + private static ModuleFinder limitFinder(ModuleFinder finder, + Set roots, + Set otherMods) + { + // resolve all root modules + Configuration cf = Configuration.empty().resolve(finder, + ModuleFinder.of(), + roots); + + // module name -> reference + Map map = new HashMap<>(); + + // root modules and their transitive dependences + cf.modules().stream() + .map(ResolvedModule::reference) + .forEach(mref -> map.put(mref.descriptor().name(), mref)); + + // additional modules + otherMods.stream() + .map(finder::find) + .flatMap(Optional::stream) + .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref)); + + // set of modules that are observable + Set mrefs = new HashSet<>(map.values()); + + return new ModuleFinder() { + @Override + public Optional find(String name) { + return Optional.ofNullable(map.get(name)); + } + @Override + public Set findAll() { + return mrefs; + } + }; + } + + /** + * Creates a finder from the module path that is the value of the given + * system property and optionally patched by --patch-module + */ + private static ModuleFinder finderFor(String prop) { + String s = System.getProperty(prop); + if (s == null) { + return null; + } else { + String[] dirs = s.split(File.pathSeparator); + Path[] paths = new Path[dirs.length]; + int i = 0; + for (String dir: dirs) { + paths[i++] = Path.of(dir); + } + return ModulePath.of(patcher, paths); + } + } + + /** + * Initialize the module patcher for the initial configuration passed on the + * value of the --patch-module options. + */ + private static ModulePatcher initModulePatcher() { + Map> map = decode("jdk.module.patch.", + File.pathSeparator, + false); + return new ModulePatcher(map); + } + + /** + * Returns the set of module names specified by --add-module options. + */ + private static Set addModules() { + String prefix = "jdk.module.addmods."; + int index = 0; + // the system property is removed after decoding + String value = getAndRemoveProperty(prefix + index); + if (value == null) { + return Set.of(); + } else { + Set modules = new HashSet<>(); + while (value != null) { + for (String s : value.split(",")) { + if (!s.isEmpty()) + modules.add(s); + } + index++; + value = getAndRemoveProperty(prefix + index); + } + return modules; + } + } + + /** + * Returns the set of module names specified by --limit-modules. + */ + private static Set limitModules() { + String value = getAndRemoveProperty("jdk.module.limitmods"); + if (value == null) { + return Set.of(); + } else { + Set names = new HashSet<>(); + for (String name : value.split(",")) { + if (name.length() > 0) names.add(name); + } + return names; + } + } + + /** + * Process the --add-reads options to add any additional read edges that + * are specified on the command-line. + */ + private static void addExtraReads(ModuleLayer bootLayer) { + + // decode the command line options + Map> map = decode("jdk.module.addreads."); + if (map.isEmpty()) + return; + + for (Map.Entry> e : map.entrySet()) { + + // the key is $MODULE + String mn = e.getKey(); + Optional om = bootLayer.findModule(mn); + if (om.isEmpty()) { + warnUnknownModule(ADD_READS, mn); + continue; + } + Module m = om.get(); + + // the value is the set of other modules (by name) + for (String name : e.getValue()) { + if (ALL_UNNAMED.equals(name)) { + Modules.addReadsAllUnnamed(m); + } else { + om = bootLayer.findModule(name); + if (om.isPresent()) { + Modules.addReads(m, om.get()); + } else { + warnUnknownModule(ADD_READS, name); + } + } + } + } + } + + /** + * Process the --add-exports and --add-opens options to export/open + * additional packages specified on the command-line. + */ + private static boolean addExtraExportsAndOpens(ModuleLayer bootLayer) { + boolean extraExportsOrOpens = false; + + // --add-exports + String prefix = "jdk.module.addexports."; + Map> extraExports = decode(prefix); + if (!extraExports.isEmpty()) { + addExtraExportsOrOpens(bootLayer, extraExports, false); + extraExportsOrOpens = true; + } + + + // --add-opens + prefix = "jdk.module.addopens."; + Map> extraOpens = decode(prefix); + if (!extraOpens.isEmpty()) { + addExtraExportsOrOpens(bootLayer, extraOpens, true); + extraExportsOrOpens = true; + } + + return extraExportsOrOpens; + } + + private static void addExtraExportsOrOpens(ModuleLayer bootLayer, + Map> map, + boolean opens) + { + String option = opens ? ADD_OPENS : ADD_EXPORTS; + for (Map.Entry> e : map.entrySet()) { + + // the key is $MODULE/$PACKAGE + String key = e.getKey(); + String[] s = key.split("/"); + if (s.length != 2) + fail(unableToParse(option, "/", key)); + + String mn = s[0]; + String pn = s[1]; + if (mn.isEmpty() || pn.isEmpty()) + fail(unableToParse(option, "/", key)); + + // The exporting module is in the boot layer + Module m; + Optional om = bootLayer.findModule(mn); + if (om.isEmpty()) { + warnUnknownModule(option, mn); + continue; + } + + m = om.get(); + + if (!m.getDescriptor().packages().contains(pn)) { + warn("package " + pn + " not in " + mn); + continue; + } + + // the value is the set of modules to export to (by name) + for (String name : e.getValue()) { + boolean allUnnamed = false; + Module other = null; + if (ALL_UNNAMED.equals(name)) { + allUnnamed = true; + } else { + om = bootLayer.findModule(name); + if (om.isPresent()) { + other = om.get(); + } else { + warnUnknownModule(option, name); + continue; + } + } + if (allUnnamed) { + if (opens) { + Modules.addOpensToAllUnnamed(m, pn); + } else { + Modules.addExportsToAllUnnamed(m, pn); + } + } else { + if (opens) { + Modules.addOpens(m, pn, other); + } else { + Modules.addExports(m, pn, other); + } + } + } + } + } + + private static final boolean HAS_ENABLE_NATIVE_ACCESS_FLAG; + private static final Set USER_NATIVE_ACCESS_MODULES; + private static final Set JDK_NATIVE_ACCESS_MODULES; + + public static boolean hasEnableNativeAccessFlag() { + return HAS_ENABLE_NATIVE_ACCESS_FLAG; + } + + static { + USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess(); + HAS_ENABLE_NATIVE_ACCESS_FLAG = !USER_NATIVE_ACCESS_MODULES.isEmpty(); + JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules(); + } + + /** + * Grants native access to modules selected using the --enable-native-access + * command line option, and also to JDK modules that need the access. + */ + private static void addEnableNativeAccess(ModuleLayer layer) { + addEnableNativeAccess(layer, USER_NATIVE_ACCESS_MODULES, true); + addEnableNativeAccess(layer, JDK_NATIVE_ACCESS_MODULES, false); + } + + /** + * Grants native access for the given modules in the given layer. + * Warns optionally about modules that were specified, but not present in the layer. + */ + private static void addEnableNativeAccess(ModuleLayer layer, Set moduleNames, boolean shouldWarn) { + for (String name : moduleNames) { + if (name.equals("ALL-UNNAMED")) { + JLA.addEnableNativeAccessToAllUnnamed(); + } else if (!JLA.addEnableNativeAccess(layer, name) && shouldWarn) { + warnUnknownModule(ENABLE_NATIVE_ACCESS, name); + } + } + } + + /** + * Returns the set of module names specified by --enable-native-access options. + */ + private static Set decodeEnableNativeAccess() { + String prefix = "jdk.module.enable.native.access."; + int index = 0; + // the system property is removed after decoding + String value = getAndRemoveProperty(prefix + index); + Set modules = new HashSet<>(); + if (value == null) { + return modules; + } + while (value != null) { + for (String s : value.split(",")) { + if (!s.isEmpty()) + modules.add(s); + } + index++; + value = getAndRemoveProperty(prefix + index); + } + return modules; + } + + /** + * Decodes the values of --add-reads, -add-exports, --add-opens or + * --patch-modules options that are encoded in system properties. + * + * @param prefix the system property prefix + * @praam regex the regex for splitting the RHS of the option value + */ + private static Map> decode(String prefix, + String regex, + boolean allowDuplicates) { + int index = 0; + // the system property is removed after decoding + String value = getAndRemoveProperty(prefix + index); + if (value == null) + return Map.of(); + + Map> map = new HashMap<>(); + + while (value != null) { + + int pos = value.indexOf('='); + if (pos == -1) + fail(unableToParse(option(prefix), "=", value)); + if (pos == 0) + fail(unableToParse(option(prefix), "=", value)); + + // key is or / + String key = value.substring(0, pos); + + String rhs = value.substring(pos+1); + if (rhs.isEmpty()) + fail(unableToParse(option(prefix), "=", value)); + + // value is (,)* or ()* + if (!allowDuplicates && map.containsKey(key)) + fail(key + " specified more than once to " + option(prefix)); + List values = map.computeIfAbsent(key, k -> new ArrayList<>()); + int ntargets = 0; + for (String s : rhs.split(regex)) { + if (!s.isEmpty()) { + values.add(s); + ntargets++; + } + } + if (ntargets == 0) + fail("Target must be specified: " + option(prefix) + " " + value); + + index++; + value = getAndRemoveProperty(prefix + index); + } + + return map; + } + + /** + * Decodes the values of --add-reads, -add-exports or --add-opens + * which use the "," to separate the RHS of the option value. + */ + private static Map> decode(String prefix) { + return decode(prefix, ",", true); + } + + + /** + * Gets the named system property + */ + private static String getProperty(String key) { + return System.getProperty(key); + } + + /** + * Gets and remove the named system property + */ + private static String getAndRemoveProperty(String key) { + return (String) System.getProperties().remove(key); + } + + /** + * Checks incubating status of modules in the configuration + */ + private static void checkIncubatingStatus(Configuration cf) { + String incubating = null; + for (ResolvedModule resolvedModule : cf.modules()) { + ModuleReference mref = resolvedModule.reference(); + + // emit warning if the WARN_INCUBATING module resolution bit set + if (ModuleResolution.hasIncubatingWarning(mref)) { + String mn = mref.descriptor().name(); + if (incubating == null) { + incubating = mn; + } else { + incubating += ", " + mn; + } + } + } + if (incubating != null) + warn("Using incubator modules: " + incubating); + } + + /** + * Throws a RuntimeException with the given message + */ + static void fail(String m) { + throw new RuntimeException(m); + } + + static void warn(String m) { + System.err.println("WARNING: " + m); + } + + static void warnUnknownModule(String option, String mn) { + warn("Unknown module: " + mn + " specified to " + option); + } + + static String unableToParse(String option, String text, String value) { + return "Unable to parse " + option + " " + text + ": " + value; + } + + private static final String ADD_MODULES = "--add-modules"; + private static final String ADD_EXPORTS = "--add-exports"; + private static final String ADD_OPENS = "--add-opens"; + private static final String ADD_READS = "--add-reads"; + private static final String PATCH_MODULE = "--patch-module"; + private static final String ENABLE_NATIVE_ACCESS = "--enable-native-access"; + + /* + * Returns the command-line option name corresponds to the specified + * system property prefix. + */ + static String option(String prefix) { + switch (prefix) { + case "jdk.module.addexports.": + return ADD_EXPORTS; + case "jdk.module.addopens.": + return ADD_OPENS; + case "jdk.module.addreads.": + return ADD_READS; + case "jdk.module.patch.": + return PATCH_MODULE; + case "jdk.module.addmods.": + return ADD_MODULES; + default: + throw new IllegalArgumentException(prefix); + } + } + + /** + * Wraps a (potentially not thread safe) ModuleFinder created during startup + * for use after startup. + */ + static class SafeModuleFinder implements ModuleFinder { + private final Set mrefs; + private volatile Map nameToModule; + + SafeModuleFinder(ModuleFinder finder) { + this.mrefs = Collections.unmodifiableSet(finder.findAll()); + } + @Override + public Optional find(String name) { + Objects.requireNonNull(name); + Map nameToModule = this.nameToModule; + if (nameToModule == null) { + this.nameToModule = nameToModule = mrefs.stream() + .collect(Collectors.toMap(m -> m.descriptor().name(), + Function.identity())); + } + return Optional.ofNullable(nameToModule.get(name)); + } + @Override + public Set findAll() { + return mrefs; + } + } + + /** + * Counters for startup performance analysis. + */ + static class Counters { + private static final boolean PUBLISH_COUNTERS; + private static final boolean PRINT_COUNTERS; + private static Map counters; + private static long startTime; + private static long previousTime; + + static { + String s = System.getProperty("jdk.module.boot.usePerfData"); + if (s == null) { + PUBLISH_COUNTERS = false; + PRINT_COUNTERS = false; + } else { + PUBLISH_COUNTERS = true; + PRINT_COUNTERS = s.equals("debug"); + counters = new LinkedHashMap<>(); // preserve insert order + } + } + + /** + * Start counting time. + */ + static void start() { + if (PUBLISH_COUNTERS) { + startTime = previousTime = System.nanoTime(); + } + } + + /** + * Add a counter - storing the time difference between now and the + * previous add or the start. + */ + static void add(String name) { + if (PUBLISH_COUNTERS) { + long current = System.nanoTime(); + long elapsed = current - previousTime; + previousTime = current; + counters.put(name, elapsed); + } + } + + /** + * Publish the counters to the instrumentation buffer or stdout. + */ + static void publish(String totalTimeName) { + if (PUBLISH_COUNTERS) { + long currentTime = System.nanoTime(); + for (Map.Entry e : counters.entrySet()) { + String name = e.getKey(); + long value = e.getValue(); + PerfCounter.newPerfCounter(name).set(value); + if (PRINT_COUNTERS) + System.out.println(name + " = " + value); + } + long elapsedTotal = currentTime - startTime; + PerfCounter.newPerfCounter(totalTimeName).set(elapsedTotal); + if (PRINT_COUNTERS) + System.out.println(totalTimeName + " = " + elapsedTotal); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashes$Builder.class b/tests/test_data/std/jdk/internal/module/ModuleHashes$Builder.class new file mode 100644 index 00000000..d3b22263 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashes$Builder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashes$HashSupplier.class b/tests/test_data/std/jdk/internal/module/ModuleHashes$HashSupplier.class new file mode 100644 index 00000000..0dc1acdb Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashes$HashSupplier.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashes.class b/tests/test_data/std/jdk/internal/module/ModuleHashes.class new file mode 100644 index 00000000..cebda888 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashes.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashes.java b/tests/test_data/std/jdk/internal/module/ModuleHashes.java new file mode 100644 index 00000000..a3eaea5b --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleHashes.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * The result of hashing the contents of a number of module artifacts. + */ + +public final class ModuleHashes { + + /** + * A supplier of a message digest. + */ + public static interface HashSupplier { + byte[] generate(String algorithm); + } + + private final String algorithm; + private final Map nameToHash; + + /** + * Creates a {@code ModuleHashes}. + * + * @param algorithm the algorithm used to create the hashes + * @param nameToHash the map of module name to hash value + */ + ModuleHashes(String algorithm, Map nameToHash) { + this.algorithm = Objects.requireNonNull(algorithm); + this.nameToHash = Collections.unmodifiableMap(nameToHash); + } + + /** + * Returns the algorithm used to hash the modules ("SHA-256" for example). + */ + public String algorithm() { + return algorithm; + } + + /** + * Returns the set of module names for which hashes are recorded. + */ + public Set names() { + return nameToHash.keySet(); + } + + /** + * Returns the hash for the given module name, {@code null} + * if there is no hash recorded for the module. + */ + public byte[] hashFor(String mn) { + return nameToHash.get(mn); + } + + /** + * Returns unmodifiable map of module name to hash + */ + public Map hashes() { + return nameToHash; + } + + /** + * Computes a hash from the names and content of a module. + * + * @param reader the module reader to access the module content + * @param algorithm the name of the message digest algorithm to use + * @return the hash + * @throws IllegalArgumentException if digest algorithm is not supported + * @throws UncheckedIOException if an I/O error occurs + */ + private static byte[] computeHash(ModuleReader reader, String algorithm) { + MessageDigest md; + try { + md = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + byte[] buf = new byte[32*1024]; + try (Stream stream = reader.list()) { + stream.sorted().forEach(rn -> { + md.update(rn.getBytes(StandardCharsets.UTF_8)); + try (InputStream in = reader.open(rn).orElseThrow()) { + int n; + while ((n = in.read(buf)) > 0) { + md.update(buf, 0, n); + } + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + }); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + return md.digest(); + } + + /** + * Computes a hash from the names and content of a module. + * + * @param supplier supplies the module reader to access the module content + * @param algorithm the name of the message digest algorithm to use + * @return the hash + * @throws IllegalArgumentException if digest algorithm is not supported + * @throws UncheckedIOException if an I/O error occurs + */ + static byte[] computeHash(Supplier supplier, String algorithm) { + try (ModuleReader reader = supplier.get()) { + return computeHash(reader, algorithm); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + /** + * Computes the hash from the names and content of a set of modules. Returns + * a {@code ModuleHashes} to encapsulate the result. + * + * @param mrefs the set of modules + * @param algorithm the name of the message digest algorithm to use + * @return ModuleHashes that encapsulates the hashes + * @throws IllegalArgumentException if digest algorithm is not supported + * @throws UncheckedIOException if an I/O error occurs + */ + static ModuleHashes generate(Set mrefs, String algorithm) { + Map nameToHash = new TreeMap<>(); + for (ModuleReference mref : mrefs) { + try (ModuleReader reader = mref.open()) { + byte[] hash = computeHash(reader, algorithm); + nameToHash.put(mref.descriptor().name(), hash); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + return new ModuleHashes(algorithm, nameToHash); + } + + @Override + public int hashCode() { + int h = algorithm.hashCode(); + for (Map.Entry e : nameToHash.entrySet()) { + h = h * 31 + e.getKey().hashCode(); + h = h * 31 + Arrays.hashCode(e.getValue()); + } + return h; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ModuleHashes)) + return false; + ModuleHashes other = (ModuleHashes) obj; + if (!algorithm.equals(other.algorithm) + || nameToHash.size() != other.nameToHash.size()) + return false; + for (Map.Entry e : nameToHash.entrySet()) { + String name = e.getKey(); + byte[] hash = e.getValue(); + if (!Arrays.equals(hash, other.nameToHash.get(name))) + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(algorithm); + sb.append(" "); + nameToHash.entrySet() + .stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> { + sb.append(e.getKey()); + sb.append("="); + byte[] ba = e.getValue(); + for (byte b : ba) { + sb.append(String.format("%02x", b & 0xff)); + } + }); + return sb.toString(); + } + + /** + * This is used by jdk.internal.module.SystemModules class + * generated at link time. + */ + public static class Builder { + final String algorithm; + final Map nameToHash; + + Builder(String algorithm, int initialCapacity) { + this.nameToHash = new HashMap<>(initialCapacity); + this.algorithm = Objects.requireNonNull(algorithm); + } + + /** + * Sets the module hash for the given module name + */ + public Builder hashForModule(String mn, byte[] hash) { + nameToHash.put(mn, hash); + return this; + } + + /** + * Builds a {@code ModuleHashes}. + */ + public ModuleHashes build() { + if (!nameToHash.isEmpty()) { + return new ModuleHashes(algorithm, nameToHash); + } else { + return null; + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$Graph$Builder.class b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$Graph$Builder.class new file mode 100644 index 00000000..be45e161 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$Graph$Builder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$Graph.class b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$Graph.class new file mode 100644 index 00000000..1e115259 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$Graph.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$TopoSorter.class b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$TopoSorter.class new file mode 100644 index 00000000..753647b7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder$TopoSorter.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder.class b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder.class new file mode 100644 index 00000000..255ef186 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder.java b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder.java new file mode 100644 index 00000000..cebca6fb --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleHashesBuilder.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.PrintStream; +import java.lang.module.Configuration; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.TreeMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Stream; +import static java.util.stream.Collectors.*; + +/** + * A Builder to compute ModuleHashes from a given configuration + */ +public class ModuleHashesBuilder { + private final Configuration configuration; + private final Set hashModuleCandidates; + + /** + * Constructs a ModuleHashesBuilder that finds the packaged modules + * from the location of ModuleReference found from the given Configuration. + * + * @param config Configuration for building module hashes + * @param modules the candidate modules to be hashed + */ + public ModuleHashesBuilder(Configuration config, Set modules) { + this.configuration = config; + this.hashModuleCandidates = modules; + } + + /** + * Returns a map of a module M to ModuleHashes for the modules + * that depend upon M directly or indirectly. + * + * The key for each entry in the returned map is a module M that has + * no outgoing edges to any of the candidate modules to be hashed + * i.e. M is a leaf node in a connected subgraph containing M and + * other candidate modules from the module graph filtering + * the outgoing edges from M to non-candidate modules. + */ + public Map computeHashes(Set roots) { + // build a graph containing the packaged modules and + // its transitive dependences matching --hash-modules + Graph.Builder builder = new Graph.Builder<>(); + Deque todo = new ArrayDeque<>(configuration.modules()); + Set visited = new HashSet<>(); + ResolvedModule rm; + while ((rm = todo.poll()) != null) { + if (visited.add(rm)) { + builder.addNode(rm.name()); + for (ResolvedModule dm : rm.reads()) { + if (!visited.contains(dm)) { + todo.push(dm); + } + builder.addEdge(rm.name(), dm.name()); + } + } + } + + // each node in a transposed graph is a matching packaged module + // in which the hash of the modules that depend upon it is recorded + Graph transposedGraph = builder.build().transpose(); + + // traverse the modules in topological order that will identify + // the modules to record the hashes - it is the first matching + // module and has not been hashed during the traversal. + Set mods = new HashSet<>(); + Map hashes = new TreeMap<>(); + builder.build() + .orderedNodes() + .filter(mn -> roots.contains(mn) && !mods.contains(mn)) + .forEach(mn -> { + // Compute hashes of the modules that depend on mn directly and + // indirectly excluding itself. + Set ns = transposedGraph.dfs(mn) + .stream() + .filter(n -> !n.equals(mn) && hashModuleCandidates.contains(n)) + .collect(toSet()); + mods.add(mn); + mods.addAll(ns); + + if (!ns.isEmpty()) { + Set mrefs = ns.stream() + .map(name -> configuration.findModule(name) + .orElseThrow(InternalError::new)) + .map(ResolvedModule::reference) + .collect(toSet()); + hashes.put(mn, ModuleHashes.generate(mrefs, "SHA-256")); + } + }); + return hashes; + } + + /* + * Utility class + */ + static class Graph { + private final Set nodes; + private final Map> edges; + + public Graph(Set nodes, Map> edges) { + this.nodes = Collections.unmodifiableSet(nodes); + this.edges = Collections.unmodifiableMap(edges); + } + + public Set nodes() { + return nodes; + } + + public Map> edges() { + return edges; + } + + public Set adjacentNodes(T u) { + return edges.get(u); + } + + public boolean contains(T u) { + return nodes.contains(u); + } + + /** + * Returns nodes sorted in topological order. + */ + public Stream orderedNodes() { + TopoSorter sorter = new TopoSorter<>(this); + return sorter.result.stream(); + } + + /** + * Traverses this graph and performs the given action in topological order. + */ + public void ordered(Consumer action) { + TopoSorter sorter = new TopoSorter<>(this); + sorter.ordered(action); + } + + /** + * Traverses this graph and performs the given action in reverse topological order. + */ + public void reverse(Consumer action) { + TopoSorter sorter = new TopoSorter<>(this); + sorter.reverse(action); + } + + /** + * Returns a transposed graph from this graph. + */ + public Graph transpose() { + Builder builder = new Builder<>(); + nodes.forEach(builder::addNode); + // reverse edges + edges.keySet().forEach(u -> { + edges.get(u).forEach(v -> builder.addEdge(v, u)); + }); + return builder.build(); + } + + /** + * Returns all nodes reachable from the given root. + */ + public Set dfs(T root) { + return dfs(Set.of(root)); + } + + /** + * Returns all nodes reachable from the given set of roots. + */ + public Set dfs(Set roots) { + ArrayDeque todo = new ArrayDeque<>(roots); + Set visited = new HashSet<>(); + T u; + while ((u = todo.poll()) != null) { + if (visited.add(u) && contains(u)) { + adjacentNodes(u).stream() + .filter(v -> !visited.contains(v)) + .forEach(todo::push); + } + } + return visited; + } + + public void printGraph(PrintStream out) { + out.println("graph for " + nodes); + nodes + .forEach(u -> adjacentNodes(u) + .forEach(v -> out.format(" %s -> %s%n", u, v))); + } + + static class Builder { + final Set nodes = new HashSet<>(); + final Map> edges = new HashMap<>(); + + public void addNode(T node) { + if (nodes.add(node)) { + edges.computeIfAbsent(node, _e -> new HashSet<>()); + } + } + + public void addEdge(T u, T v) { + addNode(u); + addNode(v); + edges.get(u).add(v); + } + + public Graph build() { + return new Graph(nodes, edges); + } + } + } + + /** + * Topological sort + */ + private static class TopoSorter { + final Deque result = new ArrayDeque<>(); + final Graph graph; + + TopoSorter(Graph graph) { + this.graph = graph; + sort(); + } + + public void ordered(Consumer action) { + result.forEach(action); + } + + public void reverse(Consumer action) { + result.descendingIterator().forEachRemaining(action); + } + + private void sort() { + Set visited = new HashSet<>(); + Deque stack = new ArrayDeque<>(); + graph.nodes.forEach(node -> visit(node, visited, stack)); + } + + private Set children(T node) { + return graph.edges().get(node); + } + + private void visit(T node, Set visited, Deque stack) { + if (visited.add(node)) { + stack.push(node); + children(node).forEach(child -> visit(child, visited, stack)); + stack.pop(); + result.addLast(node); + } + else if (stack.contains(node)) { + throw new IllegalArgumentException( + "Cycle detected: " + node + " -> " + children(node)); + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$Attributes.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$Attributes.class new file mode 100644 index 00000000..236aa9eb Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$Attributes.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$Entry.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$Entry.class new file mode 100644 index 00000000..fe6f05f0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$Entry.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$Index2Entry.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$Index2Entry.class new file mode 100644 index 00000000..39f4349c Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$Index2Entry.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$IndexEntry.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$IndexEntry.class new file mode 100644 index 00000000..d56767a0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$IndexEntry.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$ValueEntry.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$ValueEntry.class new file mode 100644 index 00000000..d3a6e198 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool$ValueEntry.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool.class new file mode 100644 index 00000000..99cab957 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$ConstantPool.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$CountingDataInput.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$CountingDataInput.class new file mode 100644 index 00000000..d4c80f2d Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$CountingDataInput.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo$DataInputWrapper.class b/tests/test_data/std/jdk/internal/module/ModuleInfo$DataInputWrapper.class new file mode 100644 index 00000000..7dbfe632 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo$DataInputWrapper.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo.class b/tests/test_data/std/jdk/internal/module/ModuleInfo.class new file mode 100644 index 00000000..30c29c38 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfo.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfo.java b/tests/test_data/std/jdk/internal/module/ModuleInfo.java new file mode 100644 index 00000000..26cb12f4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleInfo.java @@ -0,0 +1,1227 @@ +/* + * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.InvalidModuleDescriptorException; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleDescriptor.Builder; +import java.lang.module.ModuleDescriptor.Requires; +import java.lang.module.ModuleDescriptor.Exports; +import java.lang.module.ModuleDescriptor.Opens; +import java.nio.ByteBuffer; +import java.nio.BufferUnderflowException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import jdk.internal.access.JavaLangModuleAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.misc.VM; + +import static jdk.internal.module.ClassFileConstants.*; + + +/** + * Read module information from a {@code module-info} class file. + * + * @implNote The rationale for the hand-coded reader is startup performance + * and fine control over the throwing of InvalidModuleDescriptorException. + */ + +public final class ModuleInfo { + + private static final JavaLangModuleAccess JLMA + = SharedSecrets.getJavaLangModuleAccess(); + + // supplies the set of packages when ModulePackages attribute not present + private final Supplier> packageFinder; + + // indicates if the ModuleHashes attribute should be parsed + private final boolean parseHashes; + + private ModuleInfo(Supplier> pf, boolean ph) { + packageFinder = pf; + parseHashes = ph; + } + + private ModuleInfo(Supplier> pf) { + this(pf, true); + } + + /** + * A holder class for the ModuleDescriptor that is created by reading the + * Module and other standard class file attributes. It also holds the objects + * that represent the non-standard class file attributes that are read from + * the class file. + */ + public static final class Attributes { + private final ModuleDescriptor descriptor; + private final ModuleTarget target; + private final ModuleHashes recordedHashes; + private final ModuleResolution moduleResolution; + Attributes(ModuleDescriptor descriptor, + ModuleTarget target, + ModuleHashes recordedHashes, + ModuleResolution moduleResolution) { + this.descriptor = descriptor; + this.target = target; + this.recordedHashes = recordedHashes; + this.moduleResolution = moduleResolution; + } + public ModuleDescriptor descriptor() { + return descriptor; + } + public ModuleTarget target() { + return target; + } + public ModuleHashes recordedHashes() { + return recordedHashes; + } + public ModuleResolution moduleResolution() { + return moduleResolution; + } + } + + + /** + * Reads a {@code module-info.class} from the given input stream. + * + * @throws InvalidModuleDescriptorException + * @throws IOException + */ + public static Attributes read(InputStream in, Supplier> pf) + throws IOException + { + try { + return new ModuleInfo(pf).doRead(new DataInputStream(in)); + } catch (IllegalArgumentException | IllegalStateException e) { + throw invalidModuleDescriptor(e.getMessage()); + } catch (EOFException x) { + throw truncatedModuleDescriptor(); + } + } + + /** + * Reads a {@code module-info.class} from the given byte buffer. + * + * @throws InvalidModuleDescriptorException + * @throws UncheckedIOException + */ + public static Attributes read(ByteBuffer bb, Supplier> pf) { + try { + return new ModuleInfo(pf).doRead(new DataInputWrapper(bb)); + } catch (IllegalArgumentException | IllegalStateException e) { + throw invalidModuleDescriptor(e.getMessage()); + } catch (EOFException x) { + throw truncatedModuleDescriptor(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + /** + * Reads a {@code module-info.class} from the given byte buffer + * but ignore the {@code ModuleHashes} attribute. + * + * @throws InvalidModuleDescriptorException + * @throws UncheckedIOException + */ + public static Attributes readIgnoringHashes(ByteBuffer bb, Supplier> pf) { + try { + return new ModuleInfo(pf, false).doRead(new DataInputWrapper(bb)); + } catch (IllegalArgumentException | IllegalStateException e) { + throw invalidModuleDescriptor(e.getMessage()); + } catch (EOFException x) { + throw truncatedModuleDescriptor(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + /** + * Reads the input as a module-info class file. + * + * @throws IOException + * @throws InvalidModuleDescriptorException + * @throws IllegalArgumentException if thrown by the ModuleDescriptor.Builder + * because an identifier is not a legal Java identifier, duplicate + * exports, and many other reasons + */ + private Attributes doRead(DataInput input) throws IOException { + var in = new CountingDataInput(input); + + int magic = in.readInt(); + if (magic != 0xCAFEBABE) + throw invalidModuleDescriptor("Bad magic number"); + + int minor_version = in.readUnsignedShort(); + int major_version = in.readUnsignedShort(); + if (!VM.isSupportedModuleDescriptorVersion(major_version, minor_version)) { + throw invalidModuleDescriptor("Unsupported major.minor version " + + major_version + "." + minor_version); + } + + ConstantPool cpool = new ConstantPool(in); + + int access_flags = in.readUnsignedShort(); + if (access_flags != ACC_MODULE) + throw invalidModuleDescriptor("access_flags should be ACC_MODULE"); + + int this_class = in.readUnsignedShort(); + String mn = cpool.getClassName(this_class); + if (!"module-info".equals(mn)) + throw invalidModuleDescriptor("this_class should be module-info"); + + int super_class = in.readUnsignedShort(); + if (super_class > 0) + throw invalidModuleDescriptor("bad #super_class"); + + int interfaces_count = in.readUnsignedShort(); + if (interfaces_count > 0) + throw invalidModuleDescriptor("Bad #interfaces"); + + int fields_count = in.readUnsignedShort(); + if (fields_count > 0) + throw invalidModuleDescriptor("Bad #fields"); + + int methods_count = in.readUnsignedShort(); + if (methods_count > 0) + throw invalidModuleDescriptor("Bad #methods"); + + int attributes_count = in.readUnsignedShort(); + + // the names of the attributes found in the class file + Set attributes = new HashSet<>(); + + Builder builder = null; + Set allPackages = null; + String mainClass = null; + ModuleTarget moduleTarget = null; + ModuleHashes moduleHashes = null; + ModuleResolution moduleResolution = null; + + for (int i = 0; i < attributes_count ; i++) { + int name_index = in.readUnsignedShort(); + String attribute_name = cpool.getUtf8(name_index); + int length = in.readInt(); + + boolean added = attributes.add(attribute_name); + if (!added && isAttributeAtMostOnce(attribute_name)) { + throw invalidModuleDescriptor("More than one " + + attribute_name + " attribute"); + } + + long initialPosition = in.count(); + + switch (attribute_name) { + case MODULE : + builder = readModuleAttribute(in, cpool, major_version); + break; + + case MODULE_PACKAGES : + allPackages = readModulePackagesAttribute(in, cpool); + break; + + case MODULE_MAIN_CLASS : + mainClass = readModuleMainClassAttribute(in, cpool); + break; + + case MODULE_TARGET : + moduleTarget = readModuleTargetAttribute(in, cpool); + break; + + case MODULE_HASHES : + if (parseHashes) { + moduleHashes = readModuleHashesAttribute(in, cpool); + } else { + in.skipBytes(length); + } + break; + + case MODULE_RESOLUTION : + moduleResolution = readModuleResolution(in, cpool); + break; + + default: + if (isAttributeDisallowed(attribute_name)) { + throw invalidModuleDescriptor(attribute_name + + " attribute not allowed"); + } else { + in.skipBytes(length); + } + } + + long newPosition = in.count(); + if ((newPosition - initialPosition) != length) { + // attribute length does not match actual attribute size + throw invalidModuleDescriptor("Attribute " + attribute_name + + " does not match its expected length"); + } + + } + + // the Module attribute is required + if (builder == null) { + throw invalidModuleDescriptor(MODULE + " attribute not found"); + } + + // ModuleMainClass attribute + if (mainClass != null) { + builder.mainClass(mainClass); + } + + // If the ModulePackages attribute is not present then the packageFinder + // is used to find the set of packages + boolean usedPackageFinder = false; + if (allPackages == null && packageFinder != null) { + try { + allPackages = packageFinder.get(); + } catch (UncheckedIOException x) { + throw x.getCause(); + } + usedPackageFinder = true; + } + if (allPackages != null) { + Set knownPackages = JLMA.packages(builder); + if (!allPackages.containsAll(knownPackages)) { + Set missingPackages = new HashSet<>(knownPackages); + missingPackages.removeAll(allPackages); + assert !missingPackages.isEmpty(); + String missingPackage = missingPackages.iterator().next(); + String tail; + if (usedPackageFinder) { + tail = " not found in module"; + } else { + tail = " missing from ModulePackages class file attribute"; + } + throw invalidModuleDescriptor("Package " + missingPackage + tail); + + } + builder.packages(allPackages); + } + + ModuleDescriptor descriptor = builder.build(); + return new Attributes(descriptor, + moduleTarget, + moduleHashes, + moduleResolution); + } + + /** + * Reads the Module attribute, returning the ModuleDescriptor.Builder to + * build the corresponding ModuleDescriptor. + */ + private Builder readModuleAttribute(DataInput in, ConstantPool cpool, int major) + throws IOException + { + // module_name + int module_name_index = in.readUnsignedShort(); + String mn = cpool.getModuleName(module_name_index); + + int module_flags = in.readUnsignedShort(); + + Set modifiers = new HashSet<>(); + boolean open = ((module_flags & ACC_OPEN) != 0); + if (open) + modifiers.add(ModuleDescriptor.Modifier.OPEN); + if ((module_flags & ACC_SYNTHETIC) != 0) + modifiers.add(ModuleDescriptor.Modifier.SYNTHETIC); + if ((module_flags & ACC_MANDATED) != 0) + modifiers.add(ModuleDescriptor.Modifier.MANDATED); + + Builder builder = JLMA.newModuleBuilder(mn, false, modifiers); + + int module_version_index = in.readUnsignedShort(); + if (module_version_index != 0) { + String vs = cpool.getUtf8(module_version_index); + builder.version(vs); + } + + int requires_count = in.readUnsignedShort(); + boolean requiresJavaBase = false; + for (int i=0; i mods; + if (requires_flags == 0) { + mods = Set.of(); + } else { + mods = new HashSet<>(); + if ((requires_flags & ACC_TRANSITIVE) != 0) + mods.add(Requires.Modifier.TRANSITIVE); + if ((requires_flags & ACC_STATIC_PHASE) != 0) + mods.add(Requires.Modifier.STATIC); + if ((requires_flags & ACC_SYNTHETIC) != 0) + mods.add(Requires.Modifier.SYNTHETIC); + if ((requires_flags & ACC_MANDATED) != 0) + mods.add(Requires.Modifier.MANDATED); + } + + int requires_version_index = in.readUnsignedShort(); + if (requires_version_index == 0) { + builder.requires(mods, dn); + } else { + String vs = cpool.getUtf8(requires_version_index); + JLMA.requires(builder, mods, dn, vs); + } + + if (dn.equals("java.base")) { + if (mods.contains(Requires.Modifier.SYNTHETIC)) { + throw invalidModuleDescriptor("The requires entry for java.base" + + " has ACC_SYNTHETIC set"); + } + if (major >= 54 + && (mods.contains(Requires.Modifier.TRANSITIVE) + || mods.contains(Requires.Modifier.STATIC))) { + String flagName; + if (mods.contains(Requires.Modifier.TRANSITIVE)) { + flagName = "ACC_TRANSITIVE"; + } else { + flagName = "ACC_STATIC_PHASE"; + } + throw invalidModuleDescriptor("The requires entry for java.base" + + " has " + flagName + " set"); + } + requiresJavaBase = true; + } + } + if (mn.equals("java.base")) { + if (requires_count > 0) { + throw invalidModuleDescriptor("The requires table for java.base" + + " must be 0 length"); + } + } else if (!requiresJavaBase) { + throw invalidModuleDescriptor("The requires table must have" + + " an entry for java.base"); + } + + int exports_count = in.readUnsignedShort(); + if (exports_count > 0) { + for (int i=0; i mods; + int exports_flags = in.readUnsignedShort(); + if (exports_flags == 0) { + mods = Set.of(); + } else { + mods = new HashSet<>(); + if ((exports_flags & ACC_SYNTHETIC) != 0) + mods.add(Exports.Modifier.SYNTHETIC); + if ((exports_flags & ACC_MANDATED) != 0) + mods.add(Exports.Modifier.MANDATED); + } + + int exports_to_count = in.readUnsignedShort(); + if (exports_to_count > 0) { + Set targets = HashSet.newHashSet(exports_to_count); + for (int j=0; j 0) { + if (open) { + throw invalidModuleDescriptor("The opens table for an open" + + " module must be 0 length"); + } + for (int i=0; i mods; + int opens_flags = in.readUnsignedShort(); + if (opens_flags == 0) { + mods = Set.of(); + } else { + mods = new HashSet<>(); + if ((opens_flags & ACC_SYNTHETIC) != 0) + mods.add(Opens.Modifier.SYNTHETIC); + if ((opens_flags & ACC_MANDATED) != 0) + mods.add(Opens.Modifier.MANDATED); + } + + int open_to_count = in.readUnsignedShort(); + if (open_to_count > 0) { + Set targets = HashSet.newHashSet(open_to_count); + for (int j=0; j 0) { + for (int i=0; i 0) { + for (int i=0; i providers = new ArrayList<>(with_count); + for (int j=0; j readModulePackagesAttribute(DataInput in, ConstantPool cpool) + throws IOException + { + int package_count = in.readUnsignedShort(); + Set packages = HashSet.newHashSet(package_count); + for (int i=0; i map = HashMap.newHashMap(hash_count); + for (int i=0; i notAllowed = predefinedNotAllowed; + if (notAllowed == null) { + notAllowed = Set.of( + "ConstantValue", + "Code", + "Deprecated", + "StackMapTable", + "Exceptions", + "EnclosingMethod", + "Signature", + "LineNumberTable", + "LocalVariableTable", + "LocalVariableTypeTable", + "RuntimeVisibleParameterAnnotations", + "RuntimeInvisibleParameterAnnotations", + "RuntimeVisibleTypeAnnotations", + "RuntimeInvisibleTypeAnnotations", + "Synthetic", + "AnnotationDefault", + "BootstrapMethods", + "MethodParameters"); + predefinedNotAllowed = notAllowed; + } + return notAllowed.contains(name); + } + + // lazily created set the pre-defined attributes that are not allowed + private static volatile Set predefinedNotAllowed; + + + /** + * The constant pool in a class file. + */ + private static class ConstantPool { + static final int CONSTANT_Utf8 = 1; + static final int CONSTANT_Integer = 3; + static final int CONSTANT_Float = 4; + static final int CONSTANT_Long = 5; + static final int CONSTANT_Double = 6; + static final int CONSTANT_Class = 7; + static final int CONSTANT_String = 8; + static final int CONSTANT_Fieldref = 9; + static final int CONSTANT_Methodref = 10; + static final int CONSTANT_InterfaceMethodref = 11; + static final int CONSTANT_NameAndType = 12; + static final int CONSTANT_MethodHandle = 15; + static final int CONSTANT_MethodType = 16; + static final int CONSTANT_InvokeDynamic = 18; + static final int CONSTANT_Module = 19; + static final int CONSTANT_Package = 20; + + private static class Entry { + protected Entry(int tag) { + this.tag = tag; + } + final int tag; + } + + private static class IndexEntry extends Entry { + IndexEntry(int tag, int index) { + super(tag); + this.index = index; + } + final int index; + } + + private static class Index2Entry extends Entry { + Index2Entry(int tag, int index1, int index2) { + super(tag); + this.index1 = index1; + this.index2 = index2; + } + final int index1, index2; + } + + private static class ValueEntry extends Entry { + ValueEntry(int tag, Object value) { + super(tag); + this.value = value; + } + final Object value; + } + + final Entry[] pool; + + ConstantPool(DataInput in) throws IOException { + int count = in.readUnsignedShort(); + pool = new Entry[count]; + + for (int i = 1; i < count; i++) { + int tag = in.readUnsignedByte(); + switch (tag) { + + case CONSTANT_Utf8: + String svalue = in.readUTF(); + pool[i] = new ValueEntry(tag, svalue); + break; + + case CONSTANT_Class: + case CONSTANT_Package: + case CONSTANT_Module: + case CONSTANT_String: + int index = in.readUnsignedShort(); + pool[i] = new IndexEntry(tag, index); + break; + + case CONSTANT_Double: + double dvalue = in.readDouble(); + pool[i] = new ValueEntry(tag, dvalue); + i++; + break; + + case CONSTANT_Fieldref: + case CONSTANT_InterfaceMethodref: + case CONSTANT_Methodref: + case CONSTANT_InvokeDynamic: + case CONSTANT_NameAndType: + int index1 = in.readUnsignedShort(); + int index2 = in.readUnsignedShort(); + pool[i] = new Index2Entry(tag, index1, index2); + break; + + case CONSTANT_MethodHandle: + int refKind = in.readUnsignedByte(); + index = in.readUnsignedShort(); + pool[i] = new Index2Entry(tag, refKind, index); + break; + + case CONSTANT_MethodType: + index = in.readUnsignedShort(); + pool[i] = new IndexEntry(tag, index); + break; + + case CONSTANT_Float: + float fvalue = in.readFloat(); + pool[i] = new ValueEntry(tag, fvalue); + break; + + case CONSTANT_Integer: + int ivalue = in.readInt(); + pool[i] = new ValueEntry(tag, ivalue); + break; + + case CONSTANT_Long: + long lvalue = in.readLong(); + pool[i] = new ValueEntry(tag, lvalue); + i++; + break; + + default: + throw invalidModuleDescriptor("Bad constant pool entry: " + + i); + } + } + } + + String getClassName(int index) { + checkIndex(index); + Entry e = pool[index]; + if (e.tag != CONSTANT_Class) { + throw invalidModuleDescriptor("CONSTANT_Class expected at entry: " + + index); + } + String value = getUtf8(((IndexEntry) e).index); + checkUnqualifiedName("CONSTANT_Class", index, value); + return value.replace('/', '.'); // internal form -> binary name + } + + String getPackageName(int index) { + checkIndex(index); + Entry e = pool[index]; + if (e.tag != CONSTANT_Package) { + throw invalidModuleDescriptor("CONSTANT_Package expected at entry: " + + index); + } + String value = getUtf8(((IndexEntry) e).index); + checkUnqualifiedName("CONSTANT_Package", index, value); + return value.replace('/', '.'); // internal form -> binary name + } + + String getModuleName(int index) { + checkIndex(index); + Entry e = pool[index]; + if (e.tag != CONSTANT_Module) { + throw invalidModuleDescriptor("CONSTANT_Module expected at entry: " + + index); + } + String value = getUtf8(((IndexEntry) e).index); + return decodeModuleName(index, value); + } + + String getUtf8(int index) { + checkIndex(index); + Entry e = pool[index]; + if (e.tag != CONSTANT_Utf8) { + throw invalidModuleDescriptor("CONSTANT_Utf8 expected at entry: " + + index); + } + return (String) (((ValueEntry) e).value); + } + + void checkIndex(int index) { + if (index < 1 || index >= pool.length) + throw invalidModuleDescriptor("Index into constant pool out of range"); + } + + void checkUnqualifiedName(String what, int index, String value) { + int len = value.length(); + if (len == 0) { + throw invalidModuleDescriptor(what + " at entry " + index + + " has zero length"); + } + for (int i=0; i= len) { + throw invalidModuleDescriptor("CONSTANT_Module at entry " + + index + " has illegal " + + "escape sequence"); + } + int next = value.codePointAt(j); + if (next != '\\' && next != ':' && next != '@') { + throw invalidModuleDescriptor("CONSTANT_Module at entry " + + index + " has illegal " + + "escape sequence"); + } + sb.appendCodePoint(next); + i += Character.charCount(next); + } else { + sb.appendCodePoint(cp); + } + + i += Character.charCount(cp); + } + return sb.toString(); + } + } + + /** + * A DataInput implementation that reads from a ByteBuffer. + */ + private static class DataInputWrapper implements DataInput { + private final ByteBuffer bb; + + DataInputWrapper(ByteBuffer bb) { + this.bb = bb; + } + + @Override + public void readFully(byte b[]) throws IOException { + readFully(b, 0, b.length); + } + + @Override + public void readFully(byte b[], int off, int len) throws IOException { + try { + bb.get(b, off, len); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public int skipBytes(int n) { + int skip = Math.min(n, bb.remaining()); + bb.position(bb.position() + skip); + return skip; + } + + @Override + public boolean readBoolean() throws IOException { + try { + int ch = bb.get(); + return (ch != 0); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public byte readByte() throws IOException { + try { + return bb.get(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public int readUnsignedByte() throws IOException { + try { + return ((int) bb.get()) & 0xff; + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public short readShort() throws IOException { + try { + return bb.getShort(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public int readUnsignedShort() throws IOException { + try { + return ((int) bb.getShort()) & 0xffff; + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public char readChar() throws IOException { + try { + return bb.getChar(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public int readInt() throws IOException { + try { + return bb.getInt(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public long readLong() throws IOException { + try { + return bb.getLong(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public float readFloat() throws IOException { + try { + return bb.getFloat(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public double readDouble() throws IOException { + try { + return bb.getDouble(); + } catch (BufferUnderflowException e) { + throw new EOFException(e.getMessage()); + } + } + + @Override + public String readLine() { + throw new RuntimeException("not implemented"); + } + + @Override + public String readUTF() throws IOException { + // ### Need to measure the performance and feasibility of using + // the UTF-8 decoder instead. + return DataInputStream.readUTF(this); + } + } + + /** + * A DataInput implementation that reads from another DataInput and counts + * the number of bytes read. + */ + private static class CountingDataInput implements DataInput { + private final DataInput delegate; + private long count; + + CountingDataInput(DataInput delegate) { + this.delegate = delegate; + } + + long count() { + return count; + } + + @Override + public void readFully(byte b[]) throws IOException { + delegate.readFully(b, 0, b.length); + count += b.length; + } + + @Override + public void readFully(byte b[], int off, int len) throws IOException { + delegate.readFully(b, off, len); + count += len; + } + + @Override + public int skipBytes(int n) throws IOException { + int skip = delegate.skipBytes(n); + count += skip; + return skip; + } + + @Override + public boolean readBoolean() throws IOException { + boolean b = delegate.readBoolean(); + count++; + return b; + } + + @Override + public byte readByte() throws IOException { + byte b = delegate.readByte(); + count++; + return b; + } + + @Override + public int readUnsignedByte() throws IOException { + int i = delegate.readUnsignedByte(); + count++; + return i; + } + + @Override + public short readShort() throws IOException { + short s = delegate.readShort(); + count += 2; + return s; + } + + @Override + public int readUnsignedShort() throws IOException { + int s = delegate.readUnsignedShort(); + count += 2; + return s; + } + + @Override + public char readChar() throws IOException { + char c = delegate.readChar(); + count += 2; + return c; + } + + @Override + public int readInt() throws IOException { + int i = delegate.readInt(); + count += 4; + return i; + } + + @Override + public long readLong() throws IOException { + long l = delegate.readLong(); + count += 8; + return l; + } + + @Override + public float readFloat() throws IOException { + float f = delegate.readFloat(); + count += 4; + return f; + } + + @Override + public double readDouble() throws IOException { + double d = delegate.readDouble(); + count += 8; + return d; + } + + @Override + public String readLine() { + throw new RuntimeException("not implemented"); + } + + @Override + public String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + } + + /** + * Returns an InvalidModuleDescriptorException with the given detail + * message + */ + private static InvalidModuleDescriptorException invalidModuleDescriptor(String msg) { + return new InvalidModuleDescriptorException(msg); + } + + /** + * Returns an InvalidModuleDescriptorException with a detail message to + * indicate that the class file is truncated. + */ + private static InvalidModuleDescriptorException truncatedModuleDescriptor() { + return invalidModuleDescriptor("Truncated module-info.class"); + } + +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfoExtender.class b/tests/test_data/std/jdk/internal/module/ModuleInfoExtender.class new file mode 100644 index 00000000..c01cff7c Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleInfoExtender.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleInfoExtender.java b/tests/test_data/std/jdk/internal/module/ModuleInfoExtender.java new file mode 100644 index 00000000..c9d3fd0a --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleInfoExtender.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.constant.ClassDesc; +import java.lang.module.ModuleDescriptor.Version; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModuleHashInfo; +import java.lang.classfile.attribute.ModuleHashesAttribute; +import java.lang.classfile.attribute.ModuleMainClassAttribute; +import java.lang.classfile.attribute.ModulePackagesAttribute; +import java.lang.classfile.attribute.ModuleResolutionAttribute; +import java.lang.classfile.attribute.ModuleTargetAttribute; +import java.lang.constant.ModuleDesc; +import java.lang.constant.PackageDesc; + + +/** + * Utility class to extend a module-info.class with additional attributes. + */ + +public final class ModuleInfoExtender { + + // the input stream to read the original module-info.class + private final InputStream in; + + // the packages in the ModulePackages attribute + private Set packages; + + // the value for the module version in the Module attribute + private Version version; + + // the value of the ModuleMainClass attribute + private String mainClass; + + // the value for the ModuleTarget attribute + private String targetPlatform; + + // the hashes for the ModuleHashes attribute + private ModuleHashes hashes; + + // the value of the ModuleResolution attribute + private ModuleResolution moduleResolution; + + private ModuleInfoExtender(InputStream in) { + this.in = in; + } + + /** + * Sets the packages for the ModulePackages attribute + * + * @apiNote This method does not check that the package names are legal + * package names or that the set of packages is a super set of the + * packages in the module. + */ + public ModuleInfoExtender packages(Set packages) { + this.packages = Collections.unmodifiableSet(packages); + return this; + } + + /** + * Sets the value for the module version in the Module attribute + */ + public ModuleInfoExtender version(Version version) { + this.version = version; + return this; + } + + /** + * Sets the value of the ModuleMainClass attribute. + * + * @apiNote This method does not check that the main class is a legal + * class name in a named package. + */ + public ModuleInfoExtender mainClass(String mainClass) { + this.mainClass = mainClass; + return this; + } + + /** + * Sets the value for the ModuleTarget attribute. + */ + public ModuleInfoExtender targetPlatform(String targetPlatform) { + this.targetPlatform = targetPlatform; + return this; + } + + /** + * The ModuleHashes attribute will be emitted to the module-info with + * the hashes encapsulated in the given {@code ModuleHashes} + * object. + */ + public ModuleInfoExtender hashes(ModuleHashes hashes) { + this.hashes = hashes; + return this; + } + + /** + * Sets the value for the ModuleResolution attribute. + */ + public ModuleInfoExtender moduleResolution(ModuleResolution mres) { + this.moduleResolution = mres; + return this; + } + + /** + * Outputs the modified module-info.class to the given output stream. + * Once this method has been called then the Extender object should + * be discarded. + */ + public void write(OutputStream out) throws IOException { + // emit to the output stream + out.write(toByteArray()); + } + + /** + * Returns the bytes of the modified module-info.class. + * Once this method has been called then the Extender object should + * be discarded. + */ + public byte[] toByteArray() throws IOException { + var cc = ClassFile.of(); + var cm = cc.parse(in.readAllBytes()); + Version v = ModuleInfoExtender.this.version; + return cc.transform(cm, ClassTransform.endHandler(clb -> { + // ModuleMainClass attribute + if (mainClass != null) { + clb.with(ModuleMainClassAttribute.of(ClassDesc.of(mainClass))); + } + + // ModulePackages attribute + if (packages != null) { + List packageNames = packages.stream() + .sorted() + .map(PackageDesc::of) + .toList(); + clb.with(ModulePackagesAttribute.ofNames(packageNames)); + } + + // ModuleTarget, ModuleResolution and ModuleHashes attributes + if (targetPlatform != null) { + clb.with(ModuleTargetAttribute.of(targetPlatform)); + } + if (moduleResolution != null) { + clb.with(ModuleResolutionAttribute.of(moduleResolution.value())); + } + if (hashes != null) { + clb.with(ModuleHashesAttribute.of( + hashes.algorithm(), + hashes.hashes().entrySet().stream().map(he -> + ModuleHashInfo.of(ModuleDesc.of( + he.getKey()), + he.getValue())).toList())); + } + }).andThen((clb, cle) -> { + if (v != null && cle instanceof ModuleAttribute ma) { + clb.with(ModuleAttribute.of( + ma.moduleName(), + ma.moduleFlagsMask(), + clb.constantPool().utf8Entry(v.toString()), + ma.requires(), + ma.exports(), + ma.opens(), + ma.uses(), + ma.provides())); + } else { + clb.accept(cle); + } + })); + } + + /** + * Returns an {@code Extender} that may be used to add additional + * attributes to the module-info.class read from the given input + * stream. + */ + public static ModuleInfoExtender newExtender(InputStream in) { + return new ModuleInfoExtender(in); + } + +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleLoaderMap$Mapper.class b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap$Mapper.class new file mode 100644 index 00000000..bd5ca22a Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap$Mapper.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleLoaderMap$Modules.class b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap$Modules.class new file mode 100644 index 00000000..f1b4ef50 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap$Modules.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleLoaderMap.class b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap.class new file mode 100644 index 00000000..7bbd313a Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleLoaderMap.java b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap.java new file mode 100644 index 00000000..ad45399d --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleLoaderMap.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.module.Configuration; +import java.lang.module.ResolvedModule; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import jdk.internal.loader.ClassLoaders; + +/** + * Supports the mapping of modules to class loaders. The set of modules mapped + * to the boot and platform class loaders is generated at build time from + * this source file. + */ +public final class ModuleLoaderMap { + + /** + * Maps the system modules to the built-in class loaders. + */ + private static final class Mapper implements Function { + + private static final ClassLoader PLATFORM_CLASSLOADER = + ClassLoaders.platformClassLoader(); + private static final ClassLoader APP_CLASSLOADER = + ClassLoaders.appClassLoader(); + + private static final Integer PLATFORM_LOADER_INDEX = 1; + private static final Integer APP_LOADER_INDEX = 2; + + /** + * Map from module to a class loader index. The index is resolved to the + * actual class loader in {@code apply}. + */ + private final Map map; + + /** + * Creates a Mapper to map module names in the given Configuration to + * built-in classloaders. + * + * As a proxy for the actual classloader, we store an easily archiveable + * index value in the internal map. The index is stored as a boxed value + * so that we can cheaply do identity comparisons during bootstrap. + */ + Mapper(Configuration cf) { + var map = new HashMap(); + for (ResolvedModule resolvedModule : cf.modules()) { + String mn = resolvedModule.name(); + if (!Modules.bootModules.contains(mn)) { + if (Modules.platformModules.contains(mn)) { + map.put(mn, PLATFORM_LOADER_INDEX); + } else { + map.put(mn, APP_LOADER_INDEX); + } + } + } + this.map = map; + } + + @Override + public ClassLoader apply(String name) { + Integer loader = map.get(name); + if (loader == APP_LOADER_INDEX) { + return APP_CLASSLOADER; + } else if (loader == PLATFORM_LOADER_INDEX) { + return PLATFORM_CLASSLOADER; + } else { // BOOT_LOADER_INDEX + return null; + } + } + } + + /** + * Returns the names of the modules defined to the boot loader. + */ + public static Set bootModules() { + return Modules.bootModules; + } + + /** + * Returns the names of the modules defined to the platform loader. + */ + public static Set platformModules() { + return Modules.platformModules; + } + + /** + * Returns the names of the modules defined to the application loader which perform native access. + */ + public static Set nativeAccessModules() { + return Modules.nativeAccessModules; + } + + private static class Modules { + // list of boot modules is generated at build time. + private static final Set bootModules = + Set.of(new String[] { "java.base", + "java.datatransfer", + "java.desktop", + "java.instrument", + "java.logging", + "java.management", + "java.management.rmi", + "java.naming", + "java.prefs", + "java.rmi", + "java.security.sasl", + "java.xml", + "jdk.incubator.vector", + "jdk.internal.vm.ci", + "jdk.jfr", + "jdk.management", + "jdk.management.agent", + "jdk.management.jfr", + "jdk.naming.rmi", + "jdk.net", + "jdk.nio.mapmode", + "jdk.sctp", + "jdk.unsupported" }); + + // list of platform modules is generated at build time. + private static final Set platformModules = + Set.of(new String[] { "java.compiler", + "java.net.http", + "java.scripting", + "java.se", + "java.security.jgss", + "java.smartcardio", + "java.sql", + "java.sql.rowset", + "java.transaction.xa", + "java.xml.crypto", + "jdk.accessibility", + "jdk.charsets", + "jdk.crypto.cryptoki", + "jdk.dynalink", + "jdk.graal.compiler", + "jdk.graal.compiler.management", + "jdk.httpserver", + "jdk.jsobject", + "jdk.localedata", + "jdk.naming.dns", + "jdk.security.auth", + "jdk.security.jgss", + "jdk.xml.dom", + "jdk.zipfs" }); + + // list of jdk modules is generated at build time. + private static final Set nativeAccessModules = + Set.of(new String[] { "java.base", + "java.datatransfer", + "java.desktop", + "java.instrument", + "java.logging", + "java.management", + "java.management.rmi", + "java.naming", + "java.net.http", + "java.prefs", + "java.rmi", + "java.scripting", + "java.se", + "java.security.jgss", + "java.security.sasl", + "java.smartcardio", + "java.sql", + "java.sql.rowset", + "java.transaction.xa", + "java.xml", + "java.xml.crypto", + "jdk.accessibility", + "jdk.charsets", + "jdk.crypto.cryptoki", + "jdk.dynalink", + "jdk.httpserver", + "jdk.incubator.vector", + "jdk.internal.le", + "jdk.internal.vm.ci", + "jdk.jfr", + "jdk.jsobject", + "jdk.localedata", + "jdk.management", + "jdk.management.agent", + "jdk.management.jfr", + "jdk.naming.dns", + "jdk.naming.rmi", + "jdk.net", + "jdk.nio.mapmode", + "jdk.sctp", + "jdk.security.auth", + "jdk.security.jgss", + "jdk.unsupported", + "jdk.xml.dom", + "jdk.zipfs" }); + } + + /** + * Returns a function to map modules in the given configuration to the + * built-in class loaders. + */ + static Function mappingFunction(Configuration cf) { + return new Mapper(cf); + } + + /** + * When defining modules for a configuration, we only allow defining modules + * to the boot or platform classloader if the ClassLoader mapping function + * originate from here. + */ + public static boolean isBuiltinMapper(Function clf) { + return clf instanceof Mapper; + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$ExplodedResourceFinder$1.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$ExplodedResourceFinder$1.class new file mode 100644 index 00000000..31b5a072 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$ExplodedResourceFinder$1.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$ExplodedResourceFinder.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$ExplodedResourceFinder.class new file mode 100644 index 00000000..dd532e50 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$ExplodedResourceFinder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$JarResourceFinder$1.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$JarResourceFinder$1.class new file mode 100644 index 00000000..cba27cb5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$JarResourceFinder$1.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$JarResourceFinder.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$JarResourceFinder.class new file mode 100644 index 00000000..73014a42 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$JarResourceFinder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$PatchedModuleReader$1.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$PatchedModuleReader$1.class new file mode 100644 index 00000000..e797464b Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$PatchedModuleReader$1.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$PatchedModuleReader.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$PatchedModuleReader.class new file mode 100644 index 00000000..f17635c5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$PatchedModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher$ResourceFinder.class b/tests/test_data/std/jdk/internal/module/ModulePatcher$ResourceFinder.class new file mode 100644 index 00000000..d30ada87 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher$ResourceFinder.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher.class b/tests/test_data/std/jdk/internal/module/ModulePatcher.class new file mode 100644 index 00000000..b552d135 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePatcher.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePatcher.java b/tests/test_data/std/jdk/internal/module/ModulePatcher.java new file mode 100644 index 00000000..ce837027 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModulePatcher.java @@ -0,0 +1,604 @@ +/* + * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import java.io.Closeable; +import java.io.File; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleDescriptor.Builder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; + +import jdk.internal.loader.Resource; +import jdk.internal.access.JavaLangModuleAccess; +import jdk.internal.access.SharedSecrets; +import sun.net.www.ParseUtil; + + +/** + * Provides support for patching modules, mostly the boot layer. + */ + +public final class ModulePatcher { + + private static final JavaLangModuleAccess JLMA + = SharedSecrets.getJavaLangModuleAccess(); + + // module name -> sequence of patches (directories or JAR files) + private final Map> map; + + /** + * Initialize the module patcher with the given map. The map key is + * the module name, the value is a list of path strings. + */ + public ModulePatcher(Map> input) { + if (input.isEmpty()) { + this.map = Map.of(); + } else { + Map> map = new HashMap<>(); + for (Map.Entry> e : input.entrySet()) { + String mn = e.getKey(); + List paths = e.getValue().stream() + .map(Paths::get) + .toList(); + map.put(mn, paths); + } + this.map = map; + } + } + + /** + * Returns a module reference that interposes on the given module if + * needed. If there are no patches for the given module then the module + * reference is simply returned. Otherwise the patches for the module + * are scanned (to find any new packages) and a new module reference is + * returned. + * + * @throws UncheckedIOException if an I/O error is detected + */ + public ModuleReference patchIfNeeded(ModuleReference mref) { + // if there are no patches for the module then nothing to do + ModuleDescriptor descriptor = mref.descriptor(); + String mn = descriptor.name(); + List paths = map.get(mn); + if (paths == null) + return mref; + + // Scan the JAR file or directory tree to get the set of packages. + // For automatic modules then packages that do not contain class files + // must be ignored. + Set packages = new HashSet<>(); + boolean isAutomatic = descriptor.isAutomatic(); + try { + for (Path file : paths) { + if (Files.isRegularFile(file)) { + + // JAR file - do not open as a multi-release JAR as this + // is not supported by the boot class loader + try (JarFile jf = new JarFile(file.toString())) { + jf.stream() + .filter(e -> !e.isDirectory() + && (!isAutomatic || e.getName().endsWith(".class"))) + .map(e -> toPackageName(file, e)) + .filter(Checks::isPackageName) + .forEach(packages::add); + } + + } else if (Files.isDirectory(file)) { + + // exploded directory without following sym links + Path top = file; + try (Stream stream = Files.find(top, Integer.MAX_VALUE, + ((path, attrs) -> attrs.isRegularFile()))) { + stream.filter(path -> (!isAutomatic + || path.toString().endsWith(".class")) + && !isHidden(path)) + .map(path -> toPackageName(top, path)) + .filter(Checks::isPackageName) + .forEach(packages::add); + } + + } + } + + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + + // if there are new packages then we need a new ModuleDescriptor + packages.removeAll(descriptor.packages()); + if (!packages.isEmpty()) { + Builder builder = JLMA.newModuleBuilder(descriptor.name(), + /*strict*/ descriptor.isAutomatic(), + descriptor.modifiers()); + if (!descriptor.isAutomatic()) { + descriptor.requires().forEach(builder::requires); + descriptor.exports().forEach(builder::exports); + descriptor.opens().forEach(builder::opens); + descriptor.uses().forEach(builder::uses); + } + descriptor.provides().forEach(builder::provides); + + descriptor.version().ifPresent(builder::version); + descriptor.mainClass().ifPresent(builder::mainClass); + + // original + new packages + builder.packages(descriptor.packages()); + builder.packages(packages); + + descriptor = builder.build(); + } + + // return a module reference to the patched module + URI location = mref.location().orElse(null); + + ModuleTarget target = null; + ModuleHashes recordedHashes = null; + ModuleHashes.HashSupplier hasher = null; + ModuleResolution mres = null; + if (mref instanceof ModuleReferenceImpl) { + ModuleReferenceImpl impl = (ModuleReferenceImpl)mref; + target = impl.moduleTarget(); + recordedHashes = impl.recordedHashes(); + hasher = impl.hasher(); + mres = impl.moduleResolution(); + } + + return new ModuleReferenceImpl(descriptor, + location, + () -> new PatchedModuleReader(paths, mref), + this, + target, + recordedHashes, + hasher, + mres); + + } + + /** + * Returns true is this module patcher has patches. + */ + public boolean hasPatches() { + return !map.isEmpty(); + } + + /* + * Returns the names of the patched modules. + */ + Set patchedModules() { + return map.keySet(); + } + + /** + * A ModuleReader that reads resources from a patched module. + * + * This class is public so as to expose the findResource method to the + * built-in class loaders and avoid locating the resource twice during + * class loading (once to locate the resource, the second to gets the + * URL for the CodeSource). + */ + public static class PatchedModuleReader implements ModuleReader { + private final List finders; + private final ModuleReference mref; + private final URL delegateCodeSourceURL; + private volatile ModuleReader delegate; + + /** + * Creates the ModuleReader to reads resources in a patched module. + */ + PatchedModuleReader(List patches, ModuleReference mref) { + List finders = new ArrayList<>(); + boolean initialized = false; + try { + for (Path file : patches) { + if (Files.isRegularFile(file)) { + finders.add(new JarResourceFinder(file)); + } else { + finders.add(new ExplodedResourceFinder(file)); + } + } + initialized = true; + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } finally { + // close all ResourceFinder in the event of an error + if (!initialized) closeAll(finders); + } + + this.finders = finders; + this.mref = mref; + this.delegateCodeSourceURL = codeSourceURL(mref); + } + + /** + * Closes all resource finders. + */ + private static void closeAll(List finders) { + for (ResourceFinder finder : finders) { + try { finder.close(); } catch (IOException ioe) { } + } + } + + /** + * Returns the code source URL for the given module. + */ + private static URL codeSourceURL(ModuleReference mref) { + try { + Optional ouri = mref.location(); + if (ouri.isPresent()) + return ouri.get().toURL(); + } catch (MalformedURLException e) { } + return null; + } + + /** + * Returns the ModuleReader to delegate to when the resource is not + * found in a patch location. + */ + private ModuleReader delegate() throws IOException { + ModuleReader r = delegate; + if (r == null) { + synchronized (this) { + r = delegate; + if (r == null) { + delegate = r = mref.open(); + } + } + } + return r; + } + + /** + * Finds a resources in the patch locations. Returns null if not found + * or the name is "module-info.class" as that cannot be overridden. + */ + private Resource findResourceInPatch(String name) throws IOException { + if (!name.equals("module-info.class")) { + for (ResourceFinder finder : finders) { + Resource r = finder.find(name); + if (r != null) + return r; + } + } + return null; + } + + /** + * Finds a resource of the given name in the patched module. + */ + public Resource findResource(String name) throws IOException { + + // patch locations + Resource r = findResourceInPatch(name); + if (r != null) + return r; + + // original module + ByteBuffer bb = delegate().read(name).orElse(null); + if (bb == null) + return null; + + return new Resource() { + private T shouldNotGetHere(Class type) { + throw new InternalError("should not get here"); + } + @Override + public String getName() { + return shouldNotGetHere(String.class); + } + @Override + public URL getURL() { + return shouldNotGetHere(URL.class); + } + @Override + public URL getCodeSourceURL() { + return delegateCodeSourceURL; + } + @Override + public ByteBuffer getByteBuffer() throws IOException { + return bb; + } + @Override + public InputStream getInputStream() throws IOException { + return shouldNotGetHere(InputStream.class); + } + @Override + public int getContentLength() throws IOException { + return shouldNotGetHere(int.class); + } + }; + } + + @Override + public Optional find(String name) throws IOException { + Resource r = findResourceInPatch(name); + if (r != null) { + URI uri = URI.create(r.getURL().toString()); + return Optional.of(uri); + } else { + return delegate().find(name); + } + } + + @Override + public Optional open(String name) throws IOException { + Resource r = findResourceInPatch(name); + if (r != null) { + return Optional.of(r.getInputStream()); + } else { + return delegate().open(name); + } + } + + @Override + public Optional read(String name) throws IOException { + Resource r = findResourceInPatch(name); + if (r != null) { + ByteBuffer bb = r.getByteBuffer(); + assert !bb.isDirect(); + return Optional.of(bb); + } else { + return delegate().read(name); + } + } + + @Override + public void release(ByteBuffer bb) { + if (bb.isDirect()) { + try { + delegate().release(bb); + } catch (IOException ioe) { + throw new InternalError(ioe); + } + } + } + + @Override + public Stream list() throws IOException { + Stream s = delegate().list(); + for (ResourceFinder finder : finders) { + s = Stream.concat(s, finder.list()); + } + return s.distinct(); + } + + @Override + public void close() throws IOException { + closeAll(finders); + delegate().close(); + } + } + + + /** + * A resource finder that find resources in a patch location. + */ + private static interface ResourceFinder extends Closeable { + Resource find(String name) throws IOException; + Stream list() throws IOException; + } + + + /** + * A ResourceFinder that finds resources in a JAR file. + */ + private static class JarResourceFinder implements ResourceFinder { + private final JarFile jf; + private final URL csURL; + + JarResourceFinder(Path path) throws IOException { + this.jf = new JarFile(path.toString()); + this.csURL = path.toUri().toURL(); + } + + @Override + public void close() throws IOException { + jf.close(); + } + + @Override + public Resource find(String name) throws IOException { + JarEntry entry = jf.getJarEntry(name); + if (entry == null) + return null; + + return new Resource() { + @Override + public String getName() { + return name; + } + @Override + public URL getURL() { + String encodedPath = ParseUtil.encodePath(name, false); + try { + @SuppressWarnings("deprecation") + var result = new URL("jar:" + csURL + "!/" + encodedPath); + return result; + } catch (MalformedURLException e) { + return null; + } + } + @Override + public URL getCodeSourceURL() { + return csURL; + } + @Override + public ByteBuffer getByteBuffer() throws IOException { + byte[] bytes = getInputStream().readAllBytes(); + return ByteBuffer.wrap(bytes); + } + @Override + public InputStream getInputStream() throws IOException { + return jf.getInputStream(entry); + } + @Override + public int getContentLength() throws IOException { + long size = entry.getSize(); + return (size > Integer.MAX_VALUE) ? -1 : (int) size; + } + }; + } + + @Override + public Stream list() throws IOException { + return jf.stream().map(JarEntry::getName); + } + } + + + /** + * A ResourceFinder that finds resources on the file system. + */ + private static class ExplodedResourceFinder implements ResourceFinder { + private final Path dir; + + ExplodedResourceFinder(Path dir) { + this.dir = dir; + } + + @Override + public void close() { } + + @Override + public Resource find(String name) throws IOException { + Path file = Resources.toFilePath(dir, name); + if (file != null) { + return newResource(name, dir, file); + } else { + return null; + } + } + + private Resource newResource(String name, Path top, Path file) { + return new Resource() { + @Override + public String getName() { + return name; + } + @Override + public URL getURL() { + try { + return file.toUri().toURL(); + } catch (IOException | IOError e) { + return null; + } + } + @Override + public URL getCodeSourceURL() { + try { + return top.toUri().toURL(); + } catch (IOException | IOError e) { + return null; + } + } + @Override + public ByteBuffer getByteBuffer() throws IOException { + return ByteBuffer.wrap(Files.readAllBytes(file)); + } + @Override + public InputStream getInputStream() throws IOException { + return Files.newInputStream(file); + } + @Override + public int getContentLength() throws IOException { + long size = Files.size(file); + return (size > Integer.MAX_VALUE) ? -1 : (int)size; + } + }; + } + + @Override + public Stream list() throws IOException { + return Files.walk(dir, Integer.MAX_VALUE) + .map(f -> Resources.toResourceName(dir, f)) + .filter(s -> !s.isEmpty()); + } + } + + + /** + * Derives a package name from the file path of an entry in an exploded patch + */ + private static String toPackageName(Path top, Path file) { + Path entry = top.relativize(file); + Path parent = entry.getParent(); + if (parent == null) { + return warnIfModuleInfo(top, entry.toString()); + } else { + return parent.toString().replace(File.separatorChar, '.'); + } + } + + /** + * Returns true if the given file exists and is a hidden file + */ + private boolean isHidden(Path file) { + try { + return Files.isHidden(file); + } catch (IOException ioe) { + return false; + } + } + + /** + * Derives a package name from the name of an entry in a JAR file. + */ + private static String toPackageName(Path file, JarEntry entry) { + String name = entry.getName(); + int index = name.lastIndexOf("/"); + if (index == -1) { + return warnIfModuleInfo(file, name); + } else { + return name.substring(0, index).replace('/', '.'); + } + } + + private static String warnIfModuleInfo(Path file, String e) { + if (e.equals("module-info.class")) + System.err.println("WARNING: " + e + " ignored in patch: " + file); + return ""; + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModulePath$Patterns.class b/tests/test_data/std/jdk/internal/module/ModulePath$Patterns.class new file mode 100644 index 00000000..e53b7523 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePath$Patterns.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePath.class b/tests/test_data/std/jdk/internal/module/ModulePath.class new file mode 100644 index 00000000..43003df7 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePath.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePath.java b/tests/test_data/std/jdk/internal/module/ModulePath.java new file mode 100644 index 00000000..2804079c --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModulePath.java @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.lang.module.FindException; +import java.lang.module.InvalidModuleDescriptorException; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleDescriptor.Builder; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import sun.nio.cs.UTF_8; + +import jdk.internal.jmod.JmodFile; +import jdk.internal.jmod.JmodFile.Section; +import jdk.internal.perf.PerfCounter; + +/** + * A {@code ModuleFinder} that locates modules on the file system by searching + * a sequence of directories or packaged modules. The ModuleFinder can be + * created to work in either the run-time or link-time phases. In both cases it + * locates modular JAR and exploded modules. When created for link-time then it + * additionally locates modules in JMOD files. The ModuleFinder can also + * optionally patch any modules that it locates with a ModulePatcher. + */ + +public class ModulePath implements ModuleFinder { + private static final String MODULE_INFO = "module-info.class"; + + // the version to use for multi-release modular JARs + private final Runtime.Version releaseVersion; + + // true for the link phase (supports modules packaged in JMOD format) + private final boolean isLinkPhase; + + // for patching modules, can be null + private final ModulePatcher patcher; + + // the entries on this module path + private final Path[] entries; + private int next; + + // map of module name to module reference map for modules already located + private final Map cachedModules = new HashMap<>(); + + + private ModulePath(Runtime.Version version, + boolean isLinkPhase, + ModulePatcher patcher, + Path... entries) { + this.releaseVersion = version; + this.isLinkPhase = isLinkPhase; + this.patcher = patcher; + this.entries = entries.clone(); + for (Path entry : this.entries) { + Objects.requireNonNull(entry); + } + } + + /** + * Returns a ModuleFinder that locates modules on the file system by + * searching a sequence of directories and/or packaged modules. The modules + * may be patched by the given ModulePatcher. + */ + public static ModuleFinder of(ModulePatcher patcher, Path... entries) { + return new ModulePath(JarFile.runtimeVersion(), false, patcher, entries); + } + + /** + * Returns a ModuleFinder that locates modules on the file system by + * searching a sequence of directories and/or packaged modules. + */ + public static ModuleFinder of(Path... entries) { + return of((ModulePatcher)null, entries); + } + + /** + * Returns a ModuleFinder that locates modules on the file system by + * searching a sequence of directories and/or packaged modules. + * + * @param version The release version to use for multi-release JAR files + * @param isLinkPhase {@code true} if the link phase to locate JMOD files + */ + public static ModuleFinder of(Runtime.Version version, + boolean isLinkPhase, + Path... entries) { + return new ModulePath(version, isLinkPhase, null, entries); + } + + + @Override + public Optional find(String name) { + Objects.requireNonNull(name); + + // try cached modules + ModuleReference m = cachedModules.get(name); + if (m != null) + return Optional.of(m); + + // the module may not have been encountered yet + while (hasNextEntry()) { + scanNextEntry(); + m = cachedModules.get(name); + if (m != null) + return Optional.of(m); + } + return Optional.empty(); + } + + @Override + public Set findAll() { + // need to ensure that all entries have been scanned + while (hasNextEntry()) { + scanNextEntry(); + } + return cachedModules.values().stream().collect(Collectors.toSet()); + } + + /** + * Returns {@code true} if there are additional entries to scan + */ + private boolean hasNextEntry() { + return next < entries.length; + } + + /** + * Scans the next entry on the module path. A no-op if all entries have + * already been scanned. + * + * @throws FindException if an error occurs scanning the next entry + */ + private void scanNextEntry() { + if (hasNextEntry()) { + + long t0 = System.nanoTime(); + + Path entry = entries[next]; + Map modules = scan(entry); + next++; + + // update cache, ignoring duplicates + int initialSize = cachedModules.size(); + for (Map.Entry e : modules.entrySet()) { + cachedModules.putIfAbsent(e.getKey(), e.getValue()); + } + + // update counters + int added = cachedModules.size() - initialSize; + moduleCount.add(added); + + scanTime.addElapsedTimeFrom(t0); + } + } + + + /** + * Scan the given module path entry. If the entry is a directory then it is + * a directory of modules or an exploded module. If the entry is a regular + * file then it is assumed to be a packaged module. + * + * @throws FindException if an error occurs scanning the entry + */ + private Map scan(Path entry) { + + BasicFileAttributes attrs; + try { + attrs = Files.readAttributes(entry, BasicFileAttributes.class); + } catch (NoSuchFileException e) { + return Map.of(); + } catch (IOException ioe) { + throw new FindException(ioe); + } + + try { + + if (attrs.isDirectory()) { + Path mi = entry.resolve(MODULE_INFO); + if (!Files.exists(mi)) { + // assume a directory of modules + return scanDirectory(entry); + } + } + + // packaged or exploded module + ModuleReference mref = readModule(entry, attrs); + if (mref != null) { + String name = mref.descriptor().name(); + return Map.of(name, mref); + } + + // not recognized + String msg; + if (!isLinkPhase && entry.toString().endsWith(".jmod")) { + msg = "JMOD format not supported at execution time"; + } else { + msg = "Module format not recognized"; + } + throw new FindException(msg + ": " + entry); + + } catch (IOException ioe) { + throw new FindException(ioe); + } + } + + + /** + * Scans the given directory for packaged or exploded modules. + * + * @return a map of module name to ModuleReference for the modules found + * in the directory + * + * @throws IOException if an I/O error occurs + * @throws FindException if an error occurs scanning the entry or the + * directory contains two or more modules with the same name + */ + private Map scanDirectory(Path dir) + throws IOException + { + // The map of name -> mref of modules found in this directory. + Map nameToReference = new HashMap<>(); + + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + for (Path entry : stream) { + BasicFileAttributes attrs; + try { + attrs = Files.readAttributes(entry, BasicFileAttributes.class); + } catch (NoSuchFileException ignore) { + // file has been removed or moved, ignore for now + continue; + } + + ModuleReference mref = readModule(entry, attrs); + + // module found + if (mref != null) { + // can have at most one version of a module in the directory + String name = mref.descriptor().name(); + ModuleReference previous = nameToReference.put(name, mref); + if (previous != null) { + String fn1 = fileName(mref); + String fn2 = fileName(previous); + throw new FindException("Two versions of module " + + name + " found in " + dir + + " (" + fn1 + " and " + fn2 + ")"); + } + } + } + } + + return nameToReference; + } + + + /** + * Reads a packaged or exploded module, returning a {@code ModuleReference} + * to the module. Returns {@code null} if the entry is not recognized. + * + * @throws IOException if an I/O error occurs + * @throws FindException if an error occurs parsing its module descriptor + */ + private ModuleReference readModule(Path entry, BasicFileAttributes attrs) + throws IOException + { + try { + + // exploded module + if (attrs.isDirectory()) { + return readExplodedModule(entry); // may return null + } + + // JAR or JMOD file + if (attrs.isRegularFile()) { + String fn = entry.getFileName().toString(); + boolean isDefaultFileSystem = isDefaultFileSystem(entry); + + // JAR file + if (fn.endsWith(".jar")) { + if (isDefaultFileSystem) { + return readJar(entry); + } else { + // the JAR file is in a custom file system so + // need to copy it to the local file system + Path tmpdir = Files.createTempDirectory("mlib"); + Path target = Files.copy(entry, tmpdir.resolve(fn)); + return readJar(target); + } + } + + // JMOD file + if (isDefaultFileSystem && isLinkPhase && fn.endsWith(".jmod")) { + return readJMod(entry); + } + } + + return null; + + } catch (InvalidModuleDescriptorException e) { + throw new FindException("Error reading module: " + entry, e); + } + } + + /** + * Returns a string with the file name of the module if possible. + * If the module location is not a file URI then return the URI + * as a string. + */ + private String fileName(ModuleReference mref) { + URI uri = mref.location().orElse(null); + if (uri != null) { + if (uri.getScheme().equalsIgnoreCase("file")) { + Path file = Path.of(uri); + return file.getFileName().toString(); + } else { + return uri.toString(); + } + } else { + return ""; + } + } + + // -- JMOD files -- + + private Set jmodPackages(JmodFile jf) { + return jf.stream() + .filter(e -> e.section() == Section.CLASSES) + .map(JmodFile.Entry::name) + .map(this::toPackageName) + .flatMap(Optional::stream) + .collect(Collectors.toSet()); + } + + /** + * Returns a {@code ModuleReference} to a module in JMOD file on the + * file system. + * + * @throws IOException + * @throws InvalidModuleDescriptorException + */ + private ModuleReference readJMod(Path file) throws IOException { + try (JmodFile jf = new JmodFile(file)) { + ModuleInfo.Attributes attrs; + try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) { + attrs = ModuleInfo.read(in, () -> jmodPackages(jf)); + } + return ModuleReferences.newJModModule(attrs, file); + } + } + + + // -- JAR files -- + + private static final String SERVICES_PREFIX = "META-INF/services/"; + + private static final Attributes.Name AUTOMATIC_MODULE_NAME + = new Attributes.Name("Automatic-Module-Name"); + + /** + * Returns the service type corresponding to the name of a services + * configuration file if it is a legal type name. + * + * For example, if called with "META-INF/services/p.S" then this method + * returns a container with the value "p.S". + */ + private Optional toServiceName(String cf) { + assert cf.startsWith(SERVICES_PREFIX); + int index = cf.lastIndexOf("/") + 1; + if (index < cf.length()) { + String prefix = cf.substring(0, index); + if (prefix.equals(SERVICES_PREFIX)) { + String sn = cf.substring(index); + if (Checks.isClassName(sn)) + return Optional.of(sn); + } + } + return Optional.empty(); + } + + /** + * Reads the next line from the given reader and trims it of comments and + * leading/trailing white space. + * + * Returns null if the reader is at EOF. + */ + private String nextLine(BufferedReader reader) throws IOException { + String ln = reader.readLine(); + if (ln != null) { + int ci = ln.indexOf('#'); + if (ci >= 0) + ln = ln.substring(0, ci); + ln = ln.trim(); + } + return ln; + } + + /** + * Treat the given JAR file as a module as follows: + * + * 1. The value of the Automatic-Module-Name attribute is the module name + * 2. The version, and the module name when the Automatic-Module-Name + * attribute is not present, is derived from the file ame of the JAR file + * 3. All packages are derived from the .class files in the JAR file + * 4. The contents of any META-INF/services configuration files are mapped + * to "provides" declarations + * 5. The Main-Class attribute in the main attributes of the JAR manifest + * is mapped to the module descriptor mainClass if possible + */ + private ModuleDescriptor deriveModuleDescriptor(JarFile jf) + throws IOException + { + // Read Automatic-Module-Name attribute if present + Manifest man = jf.getManifest(); + Attributes attrs = null; + String moduleName = null; + if (man != null) { + attrs = man.getMainAttributes(); + if (attrs != null) { + moduleName = attrs.getValue(AUTOMATIC_MODULE_NAME); + } + } + + // Derive the version, and the module name if needed, from JAR file name + String fn = jf.getName(); + int i = fn.lastIndexOf(File.separator); + if (i != -1) + fn = fn.substring(i + 1); + + // drop ".jar" + String name = fn.substring(0, fn.length() - 4); + String vs = null; + + // find first occurrence of -${NUMBER}. or -${NUMBER}$ + Matcher matcher = Patterns.DASH_VERSION.matcher(name); + if (matcher.find()) { + int start = matcher.start(); + + // attempt to parse the tail as a version string + try { + String tail = name.substring(start + 1); + ModuleDescriptor.Version.parse(tail); + vs = tail; + } catch (IllegalArgumentException ignore) { } + + name = name.substring(0, start); + } + + // Create builder, using the name derived from file name when + // Automatic-Module-Name not present + Builder builder; + if (moduleName != null) { + try { + builder = ModuleDescriptor.newAutomaticModule(moduleName); + } catch (IllegalArgumentException e) { + throw new FindException(AUTOMATIC_MODULE_NAME + ": " + e.getMessage()); + } + } else { + builder = ModuleDescriptor.newAutomaticModule(cleanModuleName(name)); + } + + // module version if present + if (vs != null) + builder.version(vs); + + // scan the names of the entries in the JAR file + Map> map = jf.versionedStream() + .filter(e -> !e.isDirectory()) + .map(JarEntry::getName) + .filter(e -> (e.endsWith(".class") ^ e.startsWith(SERVICES_PREFIX))) + .collect(Collectors.partitioningBy(e -> e.startsWith(SERVICES_PREFIX), + Collectors.toSet())); + + Set classFiles = map.get(Boolean.FALSE); + Set configFiles = map.get(Boolean.TRUE); + + // the packages containing class files + Set packages = classFiles.stream() + .map(this::toPackageName) + .flatMap(Optional::stream) + .collect(Collectors.toSet()); + + // all packages are exported and open + builder.packages(packages); + + // map names of service configuration files to service names + Set serviceNames = configFiles.stream() + .map(this::toServiceName) + .flatMap(Optional::stream) + .collect(Collectors.toSet()); + + // parse each service configuration file + for (String sn : serviceNames) { + JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); + List providerClasses = new ArrayList<>(); + try (InputStream in = jf.getInputStream(entry)) { + BufferedReader reader + = new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE)); + String cn; + while ((cn = nextLine(reader)) != null) { + if (!cn.isEmpty()) { + String pn = packageName(cn); + if (!packages.contains(pn)) { + String msg = "Provider class " + cn + " not in JAR file " + fn; + throw new InvalidModuleDescriptorException(msg); + } + providerClasses.add(cn); + } + } + } + if (!providerClasses.isEmpty()) + builder.provides(sn, providerClasses); + } + + // Main-Class attribute if it exists + if (attrs != null) { + String mainClass = attrs.getValue(Attributes.Name.MAIN_CLASS); + if (mainClass != null) { + mainClass = mainClass.replace('/', '.'); + if (Checks.isClassName(mainClass)) { + String pn = packageName(mainClass); + if (packages.contains(pn)) { + builder.mainClass(mainClass); + } + } + } + } + + return builder.build(); + } + + /** + * Patterns used to derive the module name from a JAR file name. + */ + private static class Patterns { + static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))"); + static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]"); + static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+"); + static final Pattern LEADING_DOTS = Pattern.compile("^\\."); + static final Pattern TRAILING_DOTS = Pattern.compile("\\.$"); + } + + /** + * Clean up candidate module name derived from a JAR file name. + */ + private static String cleanModuleName(String mn) { + // replace non-alphanumeric + mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll("."); + + // collapse repeating dots + mn = Patterns.REPEATING_DOTS.matcher(mn).replaceAll("."); + + // drop leading dots + if (!mn.isEmpty() && mn.charAt(0) == '.') + mn = Patterns.LEADING_DOTS.matcher(mn).replaceAll(""); + + // drop trailing dots + int len = mn.length(); + if (len > 0 && mn.charAt(len-1) == '.') + mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll(""); + + return mn; + } + + private Set jarPackages(JarFile jf) { + return jf.versionedStream() + .filter(e -> !e.isDirectory()) + .map(JarEntry::getName) + .map(this::toPackageName) + .flatMap(Optional::stream) + .collect(Collectors.toSet()); + } + + /** + * Returns a {@code ModuleReference} to a module in modular JAR file on + * the file system. + * + * @throws IOException + * @throws FindException + * @throws InvalidModuleDescriptorException + */ + private ModuleReference readJar(Path file) throws IOException { + try (JarFile jf = new JarFile(file.toFile(), + true, // verify + ZipFile.OPEN_READ, + releaseVersion)) + { + ModuleInfo.Attributes attrs; + JarEntry entry = jf.getJarEntry(MODULE_INFO); + if (entry == null) { + + // no module-info.class so treat it as automatic module + try { + ModuleDescriptor md = deriveModuleDescriptor(jf); + attrs = new ModuleInfo.Attributes(md, null, null, null); + } catch (RuntimeException e) { + throw new FindException("Unable to derive module descriptor for " + + jf.getName(), e); + } + + } else { + attrs = ModuleInfo.read(jf.getInputStream(entry), + () -> jarPackages(jf)); + } + + return ModuleReferences.newJarModule(attrs, patcher, file); + } catch (ZipException e) { + throw new FindException("Error reading " + file, e); + } + } + + + // -- exploded directories -- + + private Set explodedPackages(Path dir) { + String separator = dir.getFileSystem().getSeparator(); + try (Stream stream = Files.find(dir, Integer.MAX_VALUE, + (path, attrs) -> attrs.isRegularFile() && !isHidden(path))) { + return stream.map(dir::relativize) + .map(path -> toPackageName(path, separator)) + .flatMap(Optional::stream) + .collect(Collectors.toSet()); + } catch (IOException x) { + throw new UncheckedIOException(x); + } + } + + /** + * Returns a {@code ModuleReference} to an exploded module on the file + * system or {@code null} if {@code module-info.class} not found. + * + * @throws IOException + * @throws InvalidModuleDescriptorException + */ + private ModuleReference readExplodedModule(Path dir) throws IOException { + Path mi = dir.resolve(MODULE_INFO); + ModuleInfo.Attributes attrs; + try (InputStream in = Files.newInputStream(mi)) { + attrs = ModuleInfo.read(new BufferedInputStream(in), + () -> explodedPackages(dir)); + } catch (NoSuchFileException e) { + // for now + return null; + } + return ModuleReferences.newExplodedModule(attrs, patcher, dir); + } + + /** + * Maps a type name to its package name. + */ + private static String packageName(String cn) { + int index = cn.lastIndexOf('.'); + return (index == -1) ? "" : cn.substring(0, index); + } + + /** + * Maps the name of an entry in a JAR or ZIP file to a package name. + * + * @throws InvalidModuleDescriptorException if the name is a class file in + * the top-level directory of the JAR/ZIP file (and it's not + * module-info.class) + */ + private Optional toPackageName(String name) { + assert !name.endsWith("/"); + int index = name.lastIndexOf("/"); + if (index == -1) { + if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { + String msg = name + " found in top-level directory" + + " (unnamed package not allowed in module)"; + throw new InvalidModuleDescriptorException(msg); + } + return Optional.empty(); + } + + String pn = name.substring(0, index).replace('/', '.'); + if (Checks.isPackageName(pn)) { + return Optional.of(pn); + } else { + // not a valid package name + return Optional.empty(); + } + } + + /** + * Maps the relative path of an entry in an exploded module to a package + * name. + * + * @throws InvalidModuleDescriptorException if the name is a class file in + * the top-level directory (and it's not module-info.class) + */ + private Optional toPackageName(Path file, String separator) { + assert file.getRoot() == null; + + Path parent = file.getParent(); + if (parent == null) { + String name = file.toString(); + if (name.endsWith(".class") && !name.equals(MODULE_INFO)) { + String msg = name + " found in top-level directory" + + " (unnamed package not allowed in module)"; + throw new InvalidModuleDescriptorException(msg); + } + return Optional.empty(); + } + + String pn = parent.toString().replace(separator, "."); + if (Checks.isPackageName(pn)) { + return Optional.of(pn); + } else { + // not a valid package name + return Optional.empty(); + } + } + + /** + * Returns true if the given file exists and is a hidden file + */ + private boolean isHidden(Path file) { + try { + return Files.isHidden(file); + } catch (IOException ioe) { + return false; + } + } + + + /** + * Return true if a path locates a path in the default file system + */ + private boolean isDefaultFileSystem(Path path) { + return path.getFileSystem().provider() + .getScheme().equalsIgnoreCase("file"); + } + + + private static final PerfCounter scanTime + = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.scanTime"); + private static final PerfCounter moduleCount + = PerfCounter.newPerfCounter("jdk.module.finder.modulepath.modules"); +} diff --git a/tests/test_data/std/jdk/internal/module/ModulePathValidator.class b/tests/test_data/std/jdk/internal/module/ModulePathValidator.class new file mode 100644 index 00000000..ef756a91 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModulePathValidator.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModulePathValidator.java b/tests/test_data/std/jdk/internal/module/ModulePathValidator.java new file mode 100644 index 00000000..cea54ce4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModulePathValidator.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.module.FindException; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * A validator to check for errors and conflicts between modules. + */ + +class ModulePathValidator { + private static final String MODULE_INFO = "module-info.class"; + private static final String INDENT = " "; + + private final Map nameToModule; + private final Map packageToModule; + private final PrintStream out; + + private int errorCount; + + private ModulePathValidator(PrintStream out) { + this.nameToModule = new HashMap<>(); + this.packageToModule = new HashMap<>(); + this.out = out; + } + + /** + * Scans and the validates all modules on the module path. The module path + * comprises the upgrade module path, system modules, and the application + * module path. + * + * @param out the print stream for output messages + * @return the number of errors found + */ + static int scanAllModules(PrintStream out) { + ModulePathValidator validator = new ModulePathValidator(out); + + // upgrade module path + String value = System.getProperty("jdk.module.upgrade.path"); + if (value != null) { + Stream.of(value.split(File.pathSeparator)) + .map(Path::of) + .forEach(validator::scan); + } + + // system modules + ModuleFinder.ofSystem().findAll().stream() + .sorted(Comparator.comparing(ModuleReference::descriptor)) + .forEach(validator::process); + + // application module path + value = System.getProperty("jdk.module.path"); + if (value != null) { + Stream.of(value.split(File.pathSeparator)) + .map(Path::of) + .forEach(validator::scan); + } + + return validator.errorCount; + } + + /** + * Prints the module location and name. + */ + private void printModule(ModuleReference mref) { + mref.location() + .filter(uri -> !isJrt(uri)) + .ifPresent(uri -> out.print(uri + " ")); + ModuleDescriptor descriptor = mref.descriptor(); + out.print(descriptor.name()); + if (descriptor.isAutomatic()) + out.print(" automatic"); + out.println(); + } + + /** + * Prints the module location and name, checks if the module is + * shadowed by a previously seen module, and finally checks for + * package conflicts with previously seen modules. + */ + private void process(ModuleReference mref) { + String name = mref.descriptor().name(); + ModuleReference previous = nameToModule.putIfAbsent(name, mref); + if (previous != null) { + printModule(mref); + out.print(INDENT + "shadowed by "); + printModule(previous); + } else { + boolean first = true; + + // check for package conflicts when not shadowed + for (String pkg : mref.descriptor().packages()) { + previous = packageToModule.putIfAbsent(pkg, mref); + if (previous != null) { + if (first) { + printModule(mref); + first = false; + errorCount++; + } + String mn = previous.descriptor().name(); + out.println(INDENT + "contains " + pkg + + " conflicts with module " + mn); + } + } + } + } + + /** + * Scan an element on a module path. The element is a directory + * of modules, an exploded module, or a JAR file. + */ + private void scan(Path entry) { + BasicFileAttributes attrs; + try { + attrs = Files.readAttributes(entry, BasicFileAttributes.class); + } catch (NoSuchFileException ignore) { + return; + } catch (IOException ioe) { + out.println(entry + " " + ioe); + errorCount++; + return; + } + + String fn = entry.getFileName().toString(); + if (attrs.isRegularFile() && fn.endsWith(".jar")) { + // JAR file, explicit or automatic module + scanModule(entry).ifPresent(this::process); + } else if (attrs.isDirectory()) { + Path mi = entry.resolve(MODULE_INFO); + if (Files.exists(mi)) { + // exploded module + scanModule(entry).ifPresent(this::process); + } else { + // directory of modules + scanDirectory(entry); + } + } + } + + /** + * Scan the JAR files and exploded modules in a directory. + */ + private void scanDirectory(Path dir) { + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + Map moduleToEntry = new HashMap<>(); + + for (Path entry : stream) { + BasicFileAttributes attrs; + try { + attrs = Files.readAttributes(entry, BasicFileAttributes.class); + } catch (IOException ioe) { + out.println(entry + " " + ioe); + errorCount++; + continue; + } + + ModuleReference mref = null; + + String fn = entry.getFileName().toString(); + if (attrs.isRegularFile() && fn.endsWith(".jar")) { + mref = scanModule(entry).orElse(null); + } else if (attrs.isDirectory()) { + Path mi = entry.resolve(MODULE_INFO); + if (Files.exists(mi)) { + mref = scanModule(entry).orElse(null); + } + } + + if (mref != null) { + String name = mref.descriptor().name(); + Path previous = moduleToEntry.putIfAbsent(name, entry); + if (previous != null) { + // same name as other module in the directory + printModule(mref); + out.println(INDENT + "contains same module as " + + previous.getFileName()); + errorCount++; + } else { + process(mref); + } + } + } + } catch (IOException ioe) { + out.println(dir + " " + ioe); + errorCount++; + } + } + + /** + * Scan a JAR file or exploded module. + */ + private Optional scanModule(Path entry) { + ModuleFinder finder = ModuleFinder.of(entry); + try { + return finder.findAll().stream().findFirst(); + } catch (FindException e) { + out.println(entry); + out.println(INDENT + e.getMessage()); + Throwable cause = e.getCause(); + if (cause != null) { + out.println(INDENT + cause); + } + errorCount++; + return Optional.empty(); + } + } + + /** + * Returns true if the given URI is a jrt URI + */ + private static boolean isJrt(URI uri) { + return (uri != null && uri.getScheme().equalsIgnoreCase("jrt")); + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl$CachedHash.class b/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl$CachedHash.class new file mode 100644 index 00000000..9ca31f4c Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl$CachedHash.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl.class b/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl.class new file mode 100644 index 00000000..5cbc754f Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl.java b/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl.java new file mode 100644 index 00000000..e460b0fc --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleReferenceImpl.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.util.Objects; +import java.util.function.Supplier; + +/** + * A ModuleReference implementation that supports referencing a module that + * is patched and/or can be tied to other modules by means of hashes. + */ + +public class ModuleReferenceImpl extends ModuleReference { + + // location of module + private final URI location; + + // the module reader + private final Supplier readerSupplier; + + // non-null if the module is patched + private final ModulePatcher patcher; + + // ModuleTarget if the module is OS/architecture specific + private final ModuleTarget target; + + // the hashes of other modules recorded in this module + private final ModuleHashes recordedHashes; + + // the function that computes the hash of this module + private final ModuleHashes.HashSupplier hasher; + + // ModuleResolution flags + private final ModuleResolution moduleResolution; + + // Single-slot cache of this module's hash to avoid needing to compute + // it many times. For correctness under concurrent updates, we need to + // wrap the fields updated at the same time with a record. + private record CachedHash(byte[] hash, String algorithm) {} + private CachedHash cachedHash; + + /** + * Constructs a new instance of this class. + */ + public ModuleReferenceImpl(ModuleDescriptor descriptor, + URI location, + Supplier readerSupplier, + ModulePatcher patcher, + ModuleTarget target, + ModuleHashes recordedHashes, + ModuleHashes.HashSupplier hasher, + ModuleResolution moduleResolution) + { + super(descriptor, Objects.requireNonNull(location)); + this.location = location; + this.readerSupplier = readerSupplier; + this.patcher = patcher; + this.target = target; + this.recordedHashes = recordedHashes; + this.hasher = hasher; + this.moduleResolution = moduleResolution; + } + + @Override + public ModuleReader open() throws IOException { + try { + return readerSupplier.get(); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + } + + /** + * Returns {@code true} if this module has been patched via --patch-module. + */ + public boolean isPatched() { + return (patcher != null); + } + + /** + * Returns the ModuleTarget or {@code null} if the no target platform. + */ + public ModuleTarget moduleTarget() { + return target; + } + + /** + * Returns the hashes recorded in this module or {@code null} if there + * are no hashes recorded. + */ + public ModuleHashes recordedHashes() { + return recordedHashes; + } + + /** + * Returns the supplier that computes the hash of this module. + */ + ModuleHashes.HashSupplier hasher() { + return hasher; + } + + /** + * Returns the ModuleResolution flags. + */ + public ModuleResolution moduleResolution() { + return moduleResolution; + } + + /** + * Computes the hash of this module. Returns {@code null} if the hash + * cannot be computed. + * + * @throws java.io.UncheckedIOException if an I/O error occurs + */ + public byte[] computeHash(String algorithm) { + CachedHash ch = cachedHash; + if (ch != null && ch.algorithm().equals(algorithm)) { + return ch.hash(); + } + + if (hasher == null) { + return null; + } + byte[] hash = hasher.generate(algorithm); + cachedHash = new CachedHash(hash, algorithm); + return hash; + } + + @Override + public int hashCode() { + int hc = hash; + if (hc == 0) { + hc = descriptor().hashCode(); + hc = 43 * hc + Objects.hashCode(location); + hc = 43 * hc + Objects.hashCode(patcher); + if (hc == 0) + hc = -1; + hash = hc; + } + return hc; + } + + private int hash; + + @Override + public boolean equals(Object ob) { + if (!(ob instanceof ModuleReferenceImpl)) + return false; + ModuleReferenceImpl that = (ModuleReferenceImpl)ob; + + // assume module content, recorded hashes, etc. are the same + // when the modules have equal module descriptors, are at the + // same location, and are patched by the same patcher. + return Objects.equals(this.descriptor(), that.descriptor()) + && Objects.equals(this.location, that.location) + && Objects.equals(this.patcher, that.patcher); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[module "); + sb.append(descriptor().name()); + sb.append(", location="); + sb.append(location); + if (isPatched()) sb.append(" (patched)"); + sb.append("]"); + return sb.toString(); + } + +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferences$ExplodedModuleReader.class b/tests/test_data/std/jdk/internal/module/ModuleReferences$ExplodedModuleReader.class new file mode 100644 index 00000000..f2a9e180 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferences$ExplodedModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferences$JModModuleReader.class b/tests/test_data/std/jdk/internal/module/ModuleReferences$JModModuleReader.class new file mode 100644 index 00000000..7ad82d66 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferences$JModModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferences$JarModuleReader.class b/tests/test_data/std/jdk/internal/module/ModuleReferences$JarModuleReader.class new file mode 100644 index 00000000..6d0e3e88 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferences$JarModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferences$SafeCloseModuleReader.class b/tests/test_data/std/jdk/internal/module/ModuleReferences$SafeCloseModuleReader.class new file mode 100644 index 00000000..57dec6c2 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferences$SafeCloseModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferences.class b/tests/test_data/std/jdk/internal/module/ModuleReferences.class new file mode 100644 index 00000000..f1f7990f Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleReferences.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleReferences.java b/tests/test_data/std/jdk/internal/module/ModuleReferences.java new file mode 100644 index 00000000..346838b4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleReferences.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.File; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; +import java.util.zip.ZipFile; + +import jdk.internal.jmod.JmodFile; +import jdk.internal.module.ModuleHashes.HashSupplier; +import sun.net.www.ParseUtil; + + +/** + * A factory for creating ModuleReference implementations where the modules are + * packaged as modular JAR file, JMOD files or where the modules are exploded + * on the file system. + */ + +class ModuleReferences { + private ModuleReferences() { } + + /** + * Creates a ModuleReference to a possibly-patched module + */ + private static ModuleReference newModule(ModuleInfo.Attributes attrs, + URI uri, + Supplier supplier, + ModulePatcher patcher, + HashSupplier hasher) { + ModuleReference mref = new ModuleReferenceImpl(attrs.descriptor(), + uri, + supplier, + null, + attrs.target(), + attrs.recordedHashes(), + hasher, + attrs.moduleResolution()); + if (patcher != null) + mref = patcher.patchIfNeeded(mref); + + return mref; + } + + /** + * Creates a ModuleReference to a possibly-patched module in a modular JAR. + */ + static ModuleReference newJarModule(ModuleInfo.Attributes attrs, + ModulePatcher patcher, + Path file) { + URI uri = file.toUri(); + Supplier supplier = () -> new JarModuleReader(file, uri); + HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a); + return newModule(attrs, uri, supplier, patcher, hasher); + } + + /** + * Creates a ModuleReference to a module in a JMOD file. + */ + static ModuleReference newJModModule(ModuleInfo.Attributes attrs, Path file) { + URI uri = file.toUri(); + Supplier supplier = () -> new JModModuleReader(file, uri); + HashSupplier hasher = (a) -> ModuleHashes.computeHash(supplier, a); + return newModule(attrs, uri, supplier, null, hasher); + } + + /** + * Creates a ModuleReference to a possibly-patched exploded module. + */ + static ModuleReference newExplodedModule(ModuleInfo.Attributes attrs, + ModulePatcher patcher, + Path dir) { + Supplier supplier = () -> new ExplodedModuleReader(dir); + return newModule(attrs, dir.toUri(), supplier, patcher, null); + } + + + /** + * A base module reader that encapsulates machinery required to close the + * module reader safely. + */ + abstract static class SafeCloseModuleReader implements ModuleReader { + + // RW lock to support safe close + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final Lock readLock = lock.readLock(); + private final Lock writeLock = lock.writeLock(); + private boolean closed; + + SafeCloseModuleReader() { } + + /** + * Returns a URL to resource. This method is invoked by the find + * method to do the actual work of finding the resource. + */ + abstract Optional implFind(String name) throws IOException; + + /** + * Returns an input stream for reading a resource. This method is + * invoked by the open method to do the actual work of opening + * an input stream to the resource. + */ + abstract Optional implOpen(String name) throws IOException; + + /** + * Returns a stream of the names of resources in the module. This + * method is invoked by the list method to do the actual work of + * creating the stream. + */ + abstract Stream implList() throws IOException; + + /** + * Closes the module reader. This method is invoked by close to do the + * actual work of closing the module reader. + */ + abstract void implClose() throws IOException; + + @Override + public final Optional find(String name) throws IOException { + readLock.lock(); + try { + if (!closed) { + return implFind(name); + } else { + throw new IOException("ModuleReader is closed"); + } + } finally { + readLock.unlock(); + } + } + + + @Override + public final Optional open(String name) throws IOException { + readLock.lock(); + try { + if (!closed) { + return implOpen(name); + } else { + throw new IOException("ModuleReader is closed"); + } + } finally { + readLock.unlock(); + } + } + + @Override + public final Stream list() throws IOException { + readLock.lock(); + try { + if (!closed) { + return implList(); + } else { + throw new IOException("ModuleReader is closed"); + } + } finally { + readLock.unlock(); + } + } + + @Override + public final void close() throws IOException { + writeLock.lock(); + try { + if (!closed) { + closed = true; + implClose(); + } + } finally { + writeLock.unlock(); + } + } + } + + + /** + * A ModuleReader for a modular JAR file. + */ + static class JarModuleReader extends SafeCloseModuleReader { + private final JarFile jf; + private final URI uri; + + static JarFile newJarFile(Path path) { + try { + return new JarFile(new File(path.toString()), + true, // verify + ZipFile.OPEN_READ, + JarFile.runtimeVersion()); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + JarModuleReader(Path path, URI uri) { + this.jf = newJarFile(path); + this.uri = uri; + } + + private JarEntry getEntry(String name) { + return jf.getJarEntry(Objects.requireNonNull(name)); + } + + @Override + Optional implFind(String name) throws IOException { + JarEntry je = getEntry(name); + if (je != null) { + if (jf.isMultiRelease()) + name = je.getRealName(); + if (je.isDirectory() && !name.endsWith("/")) + name += "/"; + String encodedPath = ParseUtil.encodePath(name, false); + String uris = "jar:" + uri + "!/" + encodedPath; + return Optional.of(URI.create(uris)); + } else { + return Optional.empty(); + } + } + + @Override + Optional implOpen(String name) throws IOException { + JarEntry je = getEntry(name); + if (je != null) { + return Optional.of(jf.getInputStream(je)); + } else { + return Optional.empty(); + } + } + + @Override + Stream implList() throws IOException { + // take snapshot to avoid async close + List names = jf.versionedStream() + .map(JarEntry::getName) + .toList(); + return names.stream(); + } + + @Override + void implClose() throws IOException { + jf.close(); + } + } + + + /** + * A ModuleReader for a JMOD file. + */ + static class JModModuleReader extends SafeCloseModuleReader { + private final JmodFile jf; + private final URI uri; + + static JmodFile newJmodFile(Path path) { + try { + return new JmodFile(path); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + JModModuleReader(Path path, URI uri) { + this.jf = newJmodFile(path); + this.uri = uri; + } + + private JmodFile.Entry getEntry(String name) { + Objects.requireNonNull(name); + return jf.getEntry(JmodFile.Section.CLASSES, name); + } + + @Override + Optional implFind(String name) { + JmodFile.Entry je = getEntry(name); + if (je != null) { + if (je.isDirectory() && !name.endsWith("/")) + name += "/"; + String encodedPath = ParseUtil.encodePath(name, false); + String uris = "jmod:" + uri + "!/" + encodedPath; + return Optional.of(URI.create(uris)); + } else { + return Optional.empty(); + } + } + + @Override + Optional implOpen(String name) throws IOException { + JmodFile.Entry je = getEntry(name); + if (je != null) { + return Optional.of(jf.getInputStream(je)); + } else { + return Optional.empty(); + } + } + + @Override + Stream implList() throws IOException { + // take snapshot to avoid async close + List names = jf.stream() + .filter(e -> e.section() == JmodFile.Section.CLASSES) + .map(JmodFile.Entry::name) + .toList(); + return names.stream(); + } + + @Override + void implClose() throws IOException { + jf.close(); + } + } + + + /** + * A ModuleReader for an exploded module. + */ + static class ExplodedModuleReader implements ModuleReader { + private final Path dir; + private volatile boolean closed; + + ExplodedModuleReader(Path dir) { + this.dir = dir; + + // when running with a security manager then check that the caller + // has access to the directory. + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + boolean unused = Files.isDirectory(dir); + } + } + + /** + * Throws IOException if the module reader is closed; + */ + private void ensureOpen() throws IOException { + if (closed) throw new IOException("ModuleReader is closed"); + } + + @Override + public Optional find(String name) throws IOException { + ensureOpen(); + Path path = Resources.toFilePath(dir, name); + if (path != null) { + try { + return Optional.of(path.toUri()); + } catch (IOError e) { + throw (IOException) e.getCause(); + } + } else { + return Optional.empty(); + } + } + + @Override + public Optional open(String name) throws IOException { + ensureOpen(); + Path path = Resources.toFilePath(dir, name); + if (path != null) { + return Optional.of(Files.newInputStream(path)); + } else { + return Optional.empty(); + } + } + + @Override + public Optional read(String name) throws IOException { + ensureOpen(); + Path path = Resources.toFilePath(dir, name); + if (path != null) { + return Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))); + } else { + return Optional.empty(); + } + } + + @Override + public Stream list() throws IOException { + ensureOpen(); + return Files.walk(dir, Integer.MAX_VALUE) + .map(f -> Resources.toResourceName(dir, f)) + .filter(s -> s.length() > 0); + } + + @Override + public void close() { + closed = true; + } + } + +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleResolution.class b/tests/test_data/std/jdk/internal/module/ModuleResolution.class new file mode 100644 index 00000000..e6cbb698 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleResolution.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleResolution.java b/tests/test_data/std/jdk/internal/module/ModuleResolution.java new file mode 100644 index 00000000..d8b9d960 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleResolution.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.module.ModuleReference; +import static jdk.internal.module.ClassFileConstants.*; + +/** + * Represents the Module Resolution flags. + */ +public final class ModuleResolution { + + final int value; + + ModuleResolution(int value) { + this.value = value; + } + + public int value() { + return value; + } + + public static ModuleResolution empty() { + return new ModuleResolution(0); + } + + public boolean doNotResolveByDefault() { + return (value & DO_NOT_RESOLVE_BY_DEFAULT) != 0; + } + + public boolean hasDeprecatedWarning() { + return (value & WARN_DEPRECATED) != 0; + } + + public boolean hasDeprecatedForRemovalWarning() { + return (value & WARN_DEPRECATED_FOR_REMOVAL) != 0; + } + + public boolean hasIncubatingWarning() { + return (value & WARN_INCUBATING) != 0; + } + + public ModuleResolution withDoNotResolveByDefault() { + return new ModuleResolution(value | DO_NOT_RESOLVE_BY_DEFAULT); + } + + public ModuleResolution withDeprecated() { + if ((value & (WARN_DEPRECATED_FOR_REMOVAL | WARN_INCUBATING)) != 0) + throw new InternalError("cannot add deprecated to " + value); + return new ModuleResolution(value | WARN_DEPRECATED); + } + + public ModuleResolution withDeprecatedForRemoval() { + if ((value & (WARN_DEPRECATED | WARN_INCUBATING)) != 0) + throw new InternalError("cannot add deprecated for removal to " + value); + return new ModuleResolution(value | WARN_DEPRECATED_FOR_REMOVAL); + } + + public ModuleResolution withIncubating() { + if ((value & (WARN_DEPRECATED | WARN_DEPRECATED_FOR_REMOVAL)) != 0) + throw new InternalError("cannot add incubating to " + value); + return new ModuleResolution(value | WARN_INCUBATING); + } + + public static boolean doNotResolveByDefault(ModuleReference mref) { + // get the DO_NOT_RESOLVE_BY_DEFAULT flag, if any + if (mref instanceof ModuleReferenceImpl) { + ModuleResolution mres = ((ModuleReferenceImpl) mref).moduleResolution(); + if (mres != null) + return mres.doNotResolveByDefault(); + } + + return false; + } + + public static boolean hasIncubatingWarning(ModuleReference mref) { + if (mref instanceof ModuleReferenceImpl) { + ModuleResolution mres = ((ModuleReferenceImpl) mref).moduleResolution(); + if (mres != null) + return mres.hasIncubatingWarning(); + } + + return false; + } + + @Override + public String toString() { + return super.toString() + "[value=" + value + "]"; + } +} diff --git a/tests/test_data/std/jdk/internal/module/ModuleTarget.class b/tests/test_data/std/jdk/internal/module/ModuleTarget.class new file mode 100644 index 00000000..41bf6272 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ModuleTarget.class differ diff --git a/tests/test_data/std/jdk/internal/module/ModuleTarget.java b/tests/test_data/std/jdk/internal/module/ModuleTarget.java new file mode 100644 index 00000000..ffd50704 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ModuleTarget.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +/** + * Represents the module target. + * + * For now, this is a single value for the target platform, e.g. "linux-x64". + */ +public final class ModuleTarget { + + private final String targetPlatform; + + public ModuleTarget(String targetPlatform) { + this.targetPlatform = targetPlatform; + } + + public String targetPlatform() { + return targetPlatform; + } + +} diff --git a/tests/test_data/std/jdk/internal/module/Modules.class b/tests/test_data/std/jdk/internal/module/Modules.class new file mode 100644 index 00000000..21dd5adc Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/Modules.class differ diff --git a/tests/test_data/std/jdk/internal/module/Modules.java b/tests/test_data/std/jdk/internal/module/Modules.java new file mode 100644 index 00000000..be3f8b63 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/Modules.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.PrintStream; +import java.lang.module.Configuration; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; +import java.net.URI; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import jdk.internal.access.JavaLangModuleAccess; +import jdk.internal.loader.BootLoader; +import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.loader.ClassLoaders; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; + +/** + * A helper class for creating and updating modules. This class is intended to + * support command-line options, tests, and the instrumentation API. It is also + * used by the VM to load modules or add read edges when agents are instrumenting + * code that need to link to supporting classes. + * + * The parameters that are package names in this API are the fully-qualified + * names of the packages as defined in section 6.5.3 of The Java + * Language Specification , for example, {@code "java.lang"}. + */ + +public class Modules { + private Modules() { } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess(); + + /** + * Creates a new Module. The module has the given ModuleDescriptor and + * is defined to the given class loader. + * + * The resulting Module is in a larval state in that it does not read + * any other module and does not have any exports. + * + * The URI is for information purposes only. + */ + public static Module defineModule(ClassLoader loader, + ModuleDescriptor descriptor, + URI uri) + { + return JLA.defineModule(loader, descriptor, uri); + } + + /** + * Updates m1 to read m2. + * Same as m1.addReads(m2) but without a caller check. + */ + public static void addReads(Module m1, Module m2) { + JLA.addReads(m1, m2); + } + + /** + * Update module m to read all unnamed modules. + */ + public static void addReadsAllUnnamed(Module m) { + JLA.addReadsAllUnnamed(m); + } + + /** + * Updates module m1 to export a package to module m2. + * Same as m1.addExports(pn, m2) but without a caller check + */ + public static void addExports(Module m1, String pn, Module m2) { + JLA.addExports(m1, pn, m2); + } + + /** + * Updates module m to export a package unconditionally. + */ + public static void addExports(Module m, String pn) { + JLA.addExports(m, pn); + } + + /** + * Updates module m to export a package to all unnamed modules. + */ + public static void addExportsToAllUnnamed(Module m, String pn) { + JLA.addExportsToAllUnnamed(m, pn); + } + + /** + * Updates module m1 to open a package to module m2. + * Same as m1.addOpens(pn, m2) but without a caller check. + */ + public static void addOpens(Module m1, String pn, Module m2) { + JLA.addOpens(m1, pn, m2); + } + + /** + * Updates module m to open a package to all unnamed modules. + */ + public static void addOpensToAllUnnamed(Module m, String pn) { + JLA.addOpensToAllUnnamed(m, pn); + } + + /** + * Adds native access to all unnamed modules. + */ + public static void addEnableNativeAccessToAllUnnamed() { + JLA.addEnableNativeAccessToAllUnnamed(); + } + + /** + * Updates module m to use a service. + * Same as m2.addUses(service) but without a caller check. + */ + public static void addUses(Module m, Class service) { + JLA.addUses(m, service); + } + + /** + * Updates module m to provide a service + */ + public static void addProvides(Module m, Class service, Class impl) { + ModuleLayer layer = m.getLayer(); + + PrivilegedAction pa = m::getClassLoader; + @SuppressWarnings("removal") + ClassLoader loader = AccessController.doPrivileged(pa); + + ClassLoader platformClassLoader = ClassLoaders.platformClassLoader(); + if (layer == null || loader == null || loader == platformClassLoader) { + // update ClassLoader catalog + ServicesCatalog catalog; + if (loader == null) { + catalog = BootLoader.getServicesCatalog(); + } else { + catalog = ServicesCatalog.getServicesCatalog(loader); + } + catalog.addProvider(m, service, impl); + } + + if (layer != null) { + // update Layer catalog + JLA.getServicesCatalog(layer).addProvider(m, service, impl); + } + } + + /** + * Resolves a collection of root modules, with service binding and the empty + * Configuration as the parent to create a Configuration for the boot layer. + * + * This method is intended to be used to create the Configuration for the + * boot layer during startup or at a link-time. + */ + public static Configuration newBootLayerConfiguration(ModuleFinder finder, + Collection roots, + PrintStream traceOutput) + { + return JLMA.resolveAndBind(finder, roots, traceOutput); + } + + /** + * Called by the VM when code in the given Module has been transformed by + * an agent and so may have been instrumented to call into supporting + * classes on the boot class path or application class path. + */ + public static void transformedByAgent(Module m) { + addReads(m, BootLoader.getUnnamedModule()); + addReads(m, ClassLoaders.appClassLoader().getUnnamedModule()); + } + + /** + * Called by the VM to load a system module, typically "java.instrument" or + * "jdk.management.agent". If the module is not loaded then it is resolved + * and loaded (along with any dependences that weren't previously loaded) + * into a child layer. + */ + public static synchronized Module loadModule(String name) { + ModuleLayer top = topLayer; + if (top == null) + top = ModuleLayer.boot(); + + Module module = top.findModule(name).orElse(null); + if (module != null) { + // module already loaded + return module; + } + + // resolve the module with the top-most layer as the parent + ModuleFinder empty = ModuleFinder.of(); + ModuleFinder finder = ModuleBootstrap.unlimitedFinder(); + Set roots = Set.of(name); + Configuration cf = top.configuration().resolveAndBind(empty, finder, roots); + + // create the child layer + Function clf = ModuleLoaderMap.mappingFunction(cf); + ModuleLayer newLayer = top.defineModules(cf, clf); + + // add qualified exports/opens to give access to modules in child layer + Map map = newLayer.modules().stream() + .collect(Collectors.toMap(Module::getName, + Function.identity())); + ModuleLayer layer = top; + while (layer != null) { + for (Module m : layer.modules()) { + // qualified exports + m.getDescriptor().exports().stream() + .filter(ModuleDescriptor.Exports::isQualified) + .forEach(e -> e.targets().forEach(target -> { + Module other = map.get(target); + if (other != null) { + addExports(m, e.source(), other); + }})); + + // qualified opens + m.getDescriptor().opens().stream() + .filter(ModuleDescriptor.Opens::isQualified) + .forEach(o -> o.targets().forEach(target -> { + Module other = map.get(target); + if (other != null) { + addOpens(m, o.source(), other); + }})); + } + + List parents = layer.parents(); + assert parents.size() <= 1; + layer = parents.isEmpty() ? null : parents.get(0); + } + + // update security manager before making types visible + JLA.addNonExportedPackages(newLayer); + + // update the built-in class loaders to make the types visible + for (ResolvedModule resolvedModule : cf.modules()) { + ModuleReference mref = resolvedModule.reference(); + String mn = mref.descriptor().name(); + ClassLoader cl = clf.apply(mn); + if (cl == null) { + BootLoader.loadModule(mref); + } else { + ((BuiltinClassLoader) cl).loadModule(mref); + } + } + + // new top layer + topLayer = newLayer; + + // return module + return newLayer.findModule(name) + .orElseThrow(() -> new InternalError("module not loaded")); + + } + + /** + * Finds the module with the given name in the boot layer or any child + * layers created to load the "java.instrument" or "jdk.management.agent" + * modules into a running VM. + */ + public static Optional findLoadedModule(String name) { + ModuleLayer top = topLayer; + if (top == null) + top = ModuleLayer.boot(); + return top.findModule(name); + } + + // the top-most layer + private static volatile ModuleLayer topLayer; + +} diff --git a/tests/test_data/std/jdk/internal/module/Resources.class b/tests/test_data/std/jdk/internal/module/Resources.class new file mode 100644 index 00000000..ba0b68e9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/Resources.class differ diff --git a/tests/test_data/std/jdk/internal/module/Resources.java b/tests/test_data/std/jdk/internal/module/Resources.java new file mode 100644 index 00000000..2bd26e47 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/Resources.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * A helper class to support working with resources in modules. Also provides + * support for translating resource names to file paths. + */ +public final class Resources { + private Resources() { } + + /** + * Return true if a resource can be encapsulated. Resource with names + * ending in ".class" or "/" cannot be encapsulated. Resource names + * that map to a legal package name can be encapsulated. + */ + public static boolean canEncapsulate(String name) { + int len = name.length(); + if (len > 6 && name.endsWith(".class")) { + return false; + } else { + return Checks.isPackageName(toPackageName(name)); + } + } + + /** + * Derive a package name for a resource. The package name + * returned by this method may not be a legal package name. This method + * returns null if the resource name ends with a "/" (a directory) + * or the resource name does not contain a "/". + */ + public static String toPackageName(String name) { + int index = name.lastIndexOf('/'); + if (index == -1 || index == name.length()-1) { + return ""; + } else { + return name.substring(0, index).replace('/', '.'); + } + } + + /** + * Returns a resource name corresponding to the relative file path + * between {@code dir} and {@code file}. If the file is a directory + * then the name will end with a "/", except the top-level directory + * where the empty string is returned. + */ + public static String toResourceName(Path dir, Path file) { + String s = dir.relativize(file) + .toString() + .replace(File.separatorChar, '/'); + if (!s.isEmpty() && Files.isDirectory(file)) + s += "/"; + return s; + } + + /** + * Returns a file path to a resource in a file tree. If the resource + * name has a trailing "/" then the file path will locate a directory. + * Returns {@code null} if the resource does not map to a file in the + * tree file. + */ + public static Path toFilePath(Path dir, String name) throws IOException { + boolean expectDirectory = name.endsWith("/"); + if (expectDirectory) { + name = name.substring(0, name.length() - 1); // drop trailing "/" + } + Path path = toSafeFilePath(dir.getFileSystem(), name); + if (path != null) { + Path file = dir.resolve(path); + try { + BasicFileAttributes attrs; + attrs = Files.readAttributes(file, BasicFileAttributes.class); + if (attrs.isDirectory() + || (!attrs.isDirectory() && !expectDirectory)) + return file; + } catch (NoSuchFileException ignore) { } + } + return null; + } + + /** + * Map a resource name to a "safe" file path. Returns {@code null} if + * the resource name cannot be converted into a "safe" file path. + * + * Resource names with empty elements, or elements that are "." or ".." + * are rejected, as are resource names that translates to a file path + * with a root component. + */ + private static Path toSafeFilePath(FileSystem fs, String name) { + // scan elements of resource name + int next; + int off = 0; + while ((next = name.indexOf('/', off)) != -1) { + int len = next - off; + if (!mayTranslate(name, off, len)) { + return null; + } + off = next + 1; + } + int rem = name.length() - off; + if (!mayTranslate(name, off, rem)) { + return null; + } + + // map resource name to a file path string + String pathString; + if (File.separatorChar == '/') { + pathString = name; + } else { + // not allowed to embed file separators + if (name.contains(File.separator)) + return null; + pathString = name.replace('/', File.separatorChar); + } + + // try to convert to a Path + Path path; + try { + path = fs.getPath(pathString); + } catch (InvalidPathException e) { + // not a valid file path + return null; + } + + // file path not allowed to have root component + return (path.getRoot() == null) ? path : null; + } + + /** + * Returns {@code true} if the element in a resource name is a candidate + * to translate to the element of a file path. + */ + private static boolean mayTranslate(String name, int off, int len) { + if (len <= 2) { + if (len == 0) + return false; + boolean starsWithDot = (name.charAt(off) == '.'); + if (len == 1 && starsWithDot) + return false; + if (len == 2 && starsWithDot && (name.charAt(off+1) == '.')) + return false; + } + return true; + } + +} diff --git a/tests/test_data/std/jdk/internal/module/ServicesCatalog$ServiceProvider.class b/tests/test_data/std/jdk/internal/module/ServicesCatalog$ServiceProvider.class new file mode 100644 index 00000000..2ed0867c Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ServicesCatalog$ServiceProvider.class differ diff --git a/tests/test_data/std/jdk/internal/module/ServicesCatalog.class b/tests/test_data/std/jdk/internal/module/ServicesCatalog.class new file mode 100644 index 00000000..a4802fba Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/ServicesCatalog.class differ diff --git a/tests/test_data/std/jdk/internal/module/ServicesCatalog.java b/tests/test_data/std/jdk/internal/module/ServicesCatalog.java new file mode 100644 index 00000000..730bbe63 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/ServicesCatalog.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleDescriptor.Provides; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import jdk.internal.loader.ClassLoaderValue; + +/** + * A services catalog. Each {@code ClassLoader} and {@code Layer} has + * an optional {@code ServicesCatalog} for modules that provide services. + * + * @apiNote This class will be replaced once the ServiceLoader is further + * specified + */ +public final class ServicesCatalog { + + /** + * Represents a service provider in the services catalog. + */ + public static final class ServiceProvider { + private final Module module; + private final String providerName; + + public ServiceProvider(Module module, String providerName) { + this.module = module; + this.providerName = providerName; + } + + public Module module() { + return module; + } + + public String providerName() { + return providerName; + } + + @Override + public int hashCode() { + return Objects.hash(module, providerName); + } + + @Override + public boolean equals(Object ob) { + if (!(ob instanceof ServiceProvider)) + return false; + ServiceProvider that = (ServiceProvider)ob; + return Objects.equals(this.module, that.module) + && Objects.equals(this.providerName, that.providerName); + } + } + + // service name -> list of providers + private final Map> map = new ConcurrentHashMap<>(32); + + private ServicesCatalog() { } + + /** + * Creates a ServicesCatalog that supports concurrent registration + * and lookup + */ + public static ServicesCatalog create() { + return new ServicesCatalog(); + } + + /** + * Adds service providers for the given service type. + */ + private void addProviders(String service, ServiceProvider ... providers) { + List list = map.get(service); + if (list == null) { + list = new CopyOnWriteArrayList<>(providers); + List prev = map.putIfAbsent(service, list); + if (prev != null) { + // someone else got there + prev.addAll(list); + } + } else { + if (providers.length == 1) { + list.add(providers[0]); + } else { + list.addAll(Arrays.asList(providers)); + } + } + } + + /** + * Registers the providers in the given module in this services catalog. + */ + public void register(Module module) { + ModuleDescriptor descriptor = module.getDescriptor(); + for (Provides provides : descriptor.provides()) { + String service = provides.service(); + List providerNames = provides.providers(); + int count = providerNames.size(); + ServiceProvider[] providers = new ServiceProvider[count]; + for (int i = 0; i < count; i++) { + providers[i] = new ServiceProvider(module, providerNames.get(i)); + } + addProviders(service, providers); + } + } + + /** + * Adds a provider in the given module to this services catalog. + * + * @apiNote This method is for use by java.lang.instrument + */ + public void addProvider(Module module, Class service, Class impl) { + addProviders(service.getName(), new ServiceProvider(module, impl.getName())); + } + + /** + * Returns the (possibly empty) list of service providers that implement + * the given service type. + */ + public List findServices(String service) { + return map.getOrDefault(service, List.of()); + } + + /** + * Returns the ServicesCatalog for the given class loader or {@code null} + * if there is none. + */ + public static ServicesCatalog getServicesCatalogOrNull(ClassLoader loader) { + return CLV.get(loader); + } + + /** + * Returns the ServicesCatalog for the given class loader, creating it if + * needed. + */ + public static ServicesCatalog getServicesCatalog(ClassLoader loader) { + // CLV.computeIfAbsent(loader, (cl, clv) -> create()); + ServicesCatalog catalog = CLV.get(loader); + if (catalog == null) { + catalog = create(); + ServicesCatalog previous = CLV.putIfAbsent(loader, catalog); + if (previous != null) catalog = previous; + } + return catalog; + } + + /** + * Associates the given ServicesCatalog with the given class loader. + */ + public static void putServicesCatalog(ClassLoader loader, ServicesCatalog catalog) { + ServicesCatalog previous = CLV.putIfAbsent(loader, catalog); + if (previous != null) { + throw new InternalError(); + } + } + + // the ServicesCatalog registered to a class loader + private static final ClassLoaderValue CLV = new ClassLoaderValue<>(); +} diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$1.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$1.class new file mode 100644 index 00000000..25d944f6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$1.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$2.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$2.class new file mode 100644 index 00000000..235f2dc9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$2.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$3.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$3.class new file mode 100644 index 00000000..3482d305 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$3.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$ModuleContentSpliterator.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$ModuleContentSpliterator.class new file mode 100644 index 00000000..8f295a14 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$ModuleContentSpliterator.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemImage.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemImage.class new file mode 100644 index 00000000..659af512 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemImage.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemModuleFinder.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemModuleFinder.class new file mode 100644 index 00000000..ebcb9764 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemModuleFinder.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemModuleReader.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemModuleReader.class new file mode 100644 index 00000000..51b38d5f Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders$SystemModuleReader.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders.class b/tests/test_data/std/jdk/internal/module/SystemModuleFinders.class new file mode 100644 index 00000000..8c19d4d0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModuleFinders.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModuleFinders.java b/tests/test_data/std/jdk/internal/module/SystemModuleFinders.java new file mode 100644 index 00000000..74af7570 --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/SystemModuleFinders.java @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.module; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.module.ModuleDescriptor; +import java.lang.module.ModuleFinder; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.lang.reflect.Constructor; +import java.net.URI; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import jdk.internal.jimage.ImageLocation; +import jdk.internal.jimage.ImageReader; +import jdk.internal.jimage.ImageReaderFactory; +import jdk.internal.access.JavaNetUriAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.util.StaticProperty; +import jdk.internal.module.ModuleHashes.HashSupplier; + +/** + * The factory for SystemModules objects and for creating ModuleFinder objects + * that find modules in the runtime image. + * + * This class supports initializing the module system when the runtime is an + * images build, an exploded build, or an images build with java.base patched + * by an exploded java.base. It also supports a testing mode that re-parses + * the module-info.class resources in the run-time image. + */ + +public final class SystemModuleFinders { + private static final JavaNetUriAccess JNUA = SharedSecrets.getJavaNetUriAccess(); + + private static final boolean USE_FAST_PATH; + static { + String value = System.getProperty("jdk.system.module.finder.disableFastPath"); + if (value == null) { + USE_FAST_PATH = true; + } else { + USE_FAST_PATH = !value.isEmpty() && !Boolean.parseBoolean(value); + } + } + + // cached ModuleFinder returned from ofSystem + private static volatile ModuleFinder cachedSystemModuleFinder; + + private SystemModuleFinders() { } + + /** + * Returns the SystemModules object to reconstitute all modules. Returns + * null if this is an exploded build or java.base is patched by an exploded + * build. + */ + static SystemModules allSystemModules() { + if (USE_FAST_PATH) { + return SystemModulesMap.allSystemModules(); + } else { + return null; + } + } + + /** + * Returns a SystemModules object to reconstitute the modules for the + * given initial module. If the initial module is null then return the + * SystemModules object to reconstitute the default modules. + * + * Return null if there is no SystemModules class for the initial module, + * this is an exploded build, or java.base is patched by an exploded build. + */ + static SystemModules systemModules(String initialModule) { + if (USE_FAST_PATH) { + if (initialModule == null) { + return SystemModulesMap.defaultSystemModules(); + } + + String[] initialModules = SystemModulesMap.moduleNames(); + for (int i = 0; i < initialModules.length; i++) { + String moduleName = initialModules[i]; + if (initialModule.equals(moduleName)) { + String cn = SystemModulesMap.classNames()[i]; + try { + // one-arg Class.forName as java.base may not be defined + Constructor ctor = Class.forName(cn).getConstructor(); + return (SystemModules) ctor.newInstance(); + } catch (Exception e) { + throw new InternalError(e); + } + } + } + } + return null; + } + + /** + * Returns a ModuleFinder that is backed by the given SystemModules object. + * + * @apiNote The returned ModuleFinder is thread safe. + */ + static ModuleFinder of(SystemModules systemModules) { + ModuleDescriptor[] descriptors = systemModules.moduleDescriptors(); + ModuleTarget[] targets = systemModules.moduleTargets(); + ModuleHashes[] recordedHashes = systemModules.moduleHashes(); + ModuleResolution[] moduleResolutions = systemModules.moduleResolutions(); + + int moduleCount = descriptors.length; + ModuleReference[] mrefs = new ModuleReference[moduleCount]; + @SuppressWarnings(value = {"rawtypes", "unchecked"}) + Map.Entry[] map + = (Map.Entry[])new Map.Entry[moduleCount]; + + Map nameToHash = generateNameToHash(recordedHashes); + + for (int i = 0; i < moduleCount; i++) { + String name = descriptors[i].name(); + HashSupplier hashSupplier = hashSupplier(nameToHash, name); + ModuleReference mref = toModuleReference(descriptors[i], + targets[i], + recordedHashes[i], + hashSupplier, + moduleResolutions[i]); + mrefs[i] = mref; + map[i] = Map.entry(name, mref); + } + + return new SystemModuleFinder(mrefs, map); + } + + /** + * Returns the ModuleFinder to find all system modules. Supports both + * images and exploded builds. + * + * @apiNote Used by ModuleFinder.ofSystem() + */ + public static ModuleFinder ofSystem() { + ModuleFinder finder = cachedSystemModuleFinder; + if (finder != null) { + return finder; + } + + // probe to see if this is an images build + String home = StaticProperty.javaHome(); + Path modules = Path.of(home, "lib", "modules"); + if (Files.isRegularFile(modules)) { + if (USE_FAST_PATH) { + SystemModules systemModules = allSystemModules(); + if (systemModules != null) { + finder = of(systemModules); + } + } + + // fall back to parsing the module-info.class files in image + if (finder == null) { + finder = ofModuleInfos(); + } + + cachedSystemModuleFinder = finder; + return finder; + + } + + // exploded build (do not cache module finder) + Path dir = Path.of(home, "modules"); + if (!Files.isDirectory(dir)) + throw new InternalError("Unable to detect the run-time image"); + ModuleFinder f = ModulePath.of(ModuleBootstrap.patcher(), dir); + return new ModuleFinder() { + @SuppressWarnings("removal") + @Override + public Optional find(String name) { + PrivilegedAction> pa = () -> f.find(name); + return AccessController.doPrivileged(pa); + } + @SuppressWarnings("removal") + @Override + public Set findAll() { + PrivilegedAction> pa = f::findAll; + return AccessController.doPrivileged(pa); + } + }; + } + + /** + * Parses the module-info.class of all module in the runtime image and + * returns a ModuleFinder to find the modules. + * + * @apiNote The returned ModuleFinder is thread safe. + */ + private static ModuleFinder ofModuleInfos() { + // parse the module-info.class in every module + Map nameToAttributes = new HashMap<>(); + Map nameToHash = new HashMap<>(); + ImageReader reader = SystemImage.reader(); + for (String mn : reader.getModuleNames()) { + ImageLocation loc = reader.findLocation(mn, "module-info.class"); + ModuleInfo.Attributes attrs + = ModuleInfo.read(reader.getResourceBuffer(loc), null); + + nameToAttributes.put(mn, attrs); + ModuleHashes hashes = attrs.recordedHashes(); + if (hashes != null) { + for (String name : hashes.names()) { + nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name)); + } + } + } + + // create a ModuleReference for each module + Set mrefs = new HashSet<>(); + Map nameToModule = new HashMap<>(); + for (Map.Entry e : nameToAttributes.entrySet()) { + String mn = e.getKey(); + ModuleInfo.Attributes attrs = e.getValue(); + HashSupplier hashSupplier = hashSupplier(nameToHash, mn); + ModuleReference mref = toModuleReference(attrs.descriptor(), + attrs.target(), + attrs.recordedHashes(), + hashSupplier, + attrs.moduleResolution()); + mrefs.add(mref); + nameToModule.put(mn, mref); + } + + return new SystemModuleFinder(mrefs, nameToModule); + } + + /** + * A ModuleFinder that finds module in an array or set of modules. + */ + private static class SystemModuleFinder implements ModuleFinder { + final Set mrefs; + final Map nameToModule; + + SystemModuleFinder(ModuleReference[] array, + Map.Entry[] map) { + this.mrefs = Set.of(array); + this.nameToModule = Map.ofEntries(map); + } + + SystemModuleFinder(Set mrefs, + Map nameToModule) { + this.mrefs = Set.copyOf(mrefs); + this.nameToModule = Map.copyOf(nameToModule); + } + + @Override + public Optional find(String name) { + Objects.requireNonNull(name); + return Optional.ofNullable(nameToModule.get(name)); + } + + @Override + public Set findAll() { + return mrefs; + } + } + + /** + * Creates a ModuleReference to the system module. + */ + static ModuleReference toModuleReference(ModuleDescriptor descriptor, + ModuleTarget target, + ModuleHashes recordedHashes, + HashSupplier hasher, + ModuleResolution mres) { + String mn = descriptor.name(); + URI uri = JNUA.create("jrt", "/".concat(mn)); + + Supplier readerSupplier = new Supplier<>() { + @Override + public ModuleReader get() { + return new SystemModuleReader(mn, uri); + } + }; + + ModuleReference mref = new ModuleReferenceImpl(descriptor, + uri, + readerSupplier, + null, + target, + recordedHashes, + hasher, + mres); + + // may need a reference to a patched module if --patch-module specified + mref = ModuleBootstrap.patcher().patchIfNeeded(mref); + + return mref; + } + + /** + * Generates a map of module name to hash value. + */ + static Map generateNameToHash(ModuleHashes[] recordedHashes) { + Map nameToHash = null; + + boolean secondSeen = false; + // record the hashes to build HashSupplier + for (ModuleHashes mh : recordedHashes) { + if (mh != null) { + // if only one module contain ModuleHashes, use it + if (nameToHash == null) { + nameToHash = mh.hashes(); + } else { + if (!secondSeen) { + nameToHash = new HashMap<>(nameToHash); + secondSeen = true; + } + nameToHash.putAll(mh.hashes()); + } + } + } + return (nameToHash != null) ? nameToHash : Map.of(); + } + + /** + * Returns a HashSupplier that returns the hash of the given module. + */ + static HashSupplier hashSupplier(Map nameToHash, String name) { + byte[] hash = nameToHash.get(name); + if (hash != null) { + // avoid lambda here + return new HashSupplier() { + @Override + public byte[] generate(String algorithm) { + return hash; + } + }; + } else { + return null; + } + } + + /** + * Holder class for the ImageReader + * + * @apiNote This class must be loaded before a security manager is set. + */ + private static class SystemImage { + static final ImageReader READER = ImageReaderFactory.getImageReader(); + static ImageReader reader() { + return READER; + } + } + + /** + * A ModuleReader for reading resources from a module linked into the + * run-time image. + */ + private static class SystemModuleReader implements ModuleReader { + private final String module; + private volatile boolean closed; + + /** + * If there is a security manager set then check permission to + * connect to the run-time image. + */ + private static void checkPermissionToConnect(URI uri) { + @SuppressWarnings("removal") + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + URLConnection uc = uri.toURL().openConnection(); + sm.checkPermission(uc.getPermission()); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + } + + SystemModuleReader(String module, URI uri) { + checkPermissionToConnect(uri); + this.module = module; + } + + /** + * Returns the ImageLocation for the given resource, {@code null} + * if not found. + */ + private ImageLocation findImageLocation(String name) throws IOException { + Objects.requireNonNull(name); + if (closed) + throw new IOException("ModuleReader is closed"); + ImageReader imageReader = SystemImage.reader(); + if (imageReader != null) { + return imageReader.findLocation(module, name); + } else { + // not an images build + return null; + } + } + + /** + * Returns {@code true} if the given resource exists, {@code false} + * if not found. + */ + private boolean containsImageLocation(String name) throws IOException { + Objects.requireNonNull(name); + if (closed) + throw new IOException("ModuleReader is closed"); + ImageReader imageReader = SystemImage.reader(); + if (imageReader != null) { + return imageReader.verifyLocation(module, name); + } else { + // not an images build + return false; + } + } + + @Override + public Optional find(String name) throws IOException { + if (containsImageLocation(name)) { + URI u = JNUA.create("jrt", "/" + module + "/" + name); + return Optional.of(u); + } else { + return Optional.empty(); + } + } + + @Override + public Optional open(String name) throws IOException { + return read(name).map(this::toInputStream); + } + + private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer? + try { + int rem = bb.remaining(); + byte[] bytes = new byte[rem]; + bb.get(bytes); + return new ByteArrayInputStream(bytes); + } finally { + release(bb); + } + } + + @Override + public Optional read(String name) throws IOException { + ImageLocation location = findImageLocation(name); + if (location != null) { + return Optional.of(SystemImage.reader().getResourceBuffer(location)); + } else { + return Optional.empty(); + } + } + + @Override + public void release(ByteBuffer bb) { + Objects.requireNonNull(bb); + ImageReader.releaseByteBuffer(bb); + } + + @Override + public Stream list() throws IOException { + if (closed) + throw new IOException("ModuleReader is closed"); + + Spliterator s = new ModuleContentSpliterator(module); + return StreamSupport.stream(s, false); + } + + @Override + public void close() { + // nothing else to do + closed = true; + } + } + + /** + * A Spliterator for traversing the resources of a module linked into the + * run-time image. + */ + private static class ModuleContentSpliterator implements Spliterator { + final String moduleRoot; + final Deque stack; + Iterator iterator; + + ModuleContentSpliterator(String module) throws IOException { + moduleRoot = "/modules/" + module; + stack = new ArrayDeque<>(); + + // push the root node to the stack to get started + ImageReader.Node dir = SystemImage.reader().findNode(moduleRoot); + if (dir == null || !dir.isDirectory()) + throw new IOException(moduleRoot + " not a directory"); + stack.push(dir); + iterator = Collections.emptyIterator(); + } + + /** + * Returns the name of the next non-directory node or {@code null} if + * there are no remaining nodes to visit. + */ + private String next() throws IOException { + for (;;) { + while (iterator.hasNext()) { + ImageReader.Node node = iterator.next(); + String name = node.getName(); + if (node.isDirectory()) { + // build node + ImageReader.Node dir = SystemImage.reader().findNode(name); + assert dir.isDirectory(); + stack.push(dir); + } else { + // strip /modules/$MODULE/ prefix + return name.substring(moduleRoot.length() + 1); + } + } + + if (stack.isEmpty()) { + return null; + } else { + ImageReader.Node dir = stack.poll(); + assert dir.isDirectory(); + iterator = dir.getChildren().iterator(); + } + } + } + + @Override + public boolean tryAdvance(Consumer action) { + String next; + try { + next = next(); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + if (next != null) { + action.accept(next); + return true; + } else { + return false; + } + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public int characteristics() { + return Spliterator.DISTINCT + Spliterator.NONNULL + Spliterator.IMMUTABLE; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + } +} diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$0.class b/tests/test_data/std/jdk/internal/module/SystemModules$0.class new file mode 100644 index 00000000..fa550fd6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$0.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$1.class b/tests/test_data/std/jdk/internal/module/SystemModules$1.class new file mode 100644 index 00000000..53e61be1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$1.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$2.class b/tests/test_data/std/jdk/internal/module/SystemModules$2.class new file mode 100644 index 00000000..e7e28478 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$2.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$3.class b/tests/test_data/std/jdk/internal/module/SystemModules$3.class new file mode 100644 index 00000000..376b52a6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$3.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$4.class b/tests/test_data/std/jdk/internal/module/SystemModules$4.class new file mode 100644 index 00000000..f3635177 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$4.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$5.class b/tests/test_data/std/jdk/internal/module/SystemModules$5.class new file mode 100644 index 00000000..6f75408d Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$5.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$all.class b/tests/test_data/std/jdk/internal/module/SystemModules$all.class new file mode 100644 index 00000000..71e765d6 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$all.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules$default.class b/tests/test_data/std/jdk/internal/module/SystemModules$default.class new file mode 100644 index 00000000..d8eefb91 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules$default.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules.class b/tests/test_data/std/jdk/internal/module/SystemModules.class new file mode 100644 index 00000000..921b3b57 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModules.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModules.java b/tests/test_data/std/jdk/internal/module/SystemModules.java new file mode 100644 index 00000000..4c74068a --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/SystemModules.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.lang.module.ModuleDescriptor; +import java.util.Map; +import java.util.Set; + +/** + * A SystemModules object reconstitutes module descriptors and other modules + * attributes in an efficient way to avoid parsing module-info.class files at + * startup. Implementations of this class are generated by the "system modules" + * jlink plugin. + * + * @see SystemModuleFinders + * @see jdk.tools.jlink.internal.plugins.SystemModulesPlugin + */ + +interface SystemModules { + + /** + * Returns false if the module reconstituted by this SystemModules object + * have no overlapping packages. Returns true if there are overlapping + * packages or unknown. + */ + boolean hasSplitPackages(); + + /** + * Return false if the modules reconstituted by this SystemModules object + * do not include any incubator modules. Returns true if there are + * incubating modules or unknown. + */ + boolean hasIncubatorModules(); + + /** + * Returns the non-empty array of ModuleDescriptor objects. + */ + ModuleDescriptor[] moduleDescriptors(); + + /** + * Returns the array of ModuleTarget objects. The array elements correspond + * to the array of ModuleDescriptor objects. + */ + ModuleTarget[] moduleTargets(); + + /** + * Returns the array of ModuleHashes objects. The array elements correspond + * to the array of ModuleDescriptor objects. + */ + ModuleHashes[] moduleHashes(); + + /** + * Returns the array of ModuleResolution objects. The array elements correspond + * to the array of ModuleDescriptor objects. + */ + ModuleResolution[] moduleResolutions(); + + /** + * Returns the map representing readability graph for the modules reconstituted + * by this SystemModules object. + */ + Map> moduleReads(); +} diff --git a/tests/test_data/std/jdk/internal/module/SystemModulesMap.class b/tests/test_data/std/jdk/internal/module/SystemModulesMap.class new file mode 100644 index 00000000..0a528fd1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/module/SystemModulesMap.class differ diff --git a/tests/test_data/std/jdk/internal/module/SystemModulesMap.java b/tests/test_data/std/jdk/internal/module/SystemModulesMap.java new file mode 100644 index 00000000..3753737c --- /dev/null +++ b/tests/test_data/std/jdk/internal/module/SystemModulesMap.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +/** + * This class is generated/overridden at link time to return the names of the + * SystemModules classes generated at link time. + * + * @see SystemModuleFinders + * @see jdk.tools.jlink.internal.plugins.SystemModulesPlugin + */ + +class SystemModulesMap { + + /** + * Returns the SystemModules object to reconstitute all modules or null + * if this is an exploded build. + */ + static SystemModules allSystemModules() { + return null; + } + + /** + * Returns the SystemModules object to reconstitute default modules or null + * if this is an exploded build. + */ + static SystemModules defaultSystemModules() { + return null; + } + + /** + * Returns the array of initial module names identified at link time. + */ + static String[] moduleNames() { + return new String[0]; + } + + /** + * Returns the array of SystemModules class names. The elements + * correspond to the elements in the array returned by moduleNames(). + */ + static String[] classNames() { + return new String[0]; + } +} \ No newline at end of file diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationVisitor.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationVisitor.class new file mode 100644 index 00000000..63496d45 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationVisitor.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationVisitor.java new file mode 100644 index 00000000..f62f7246 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationVisitor.java @@ -0,0 +1,196 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A visitor to visit a Java annotation. The methods of this class must be called in the following + * order: ( {@code visit} | {@code visitEnum} | {@code visitAnnotation} | {@code visitArray} )* + * {@code visitEnd}. + * + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public abstract class AnnotationVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. + */ + protected final int api; + + /** + * The annotation visitor to which this visitor must delegate method calls. May be {@literal + * null}. + */ + protected AnnotationVisitor av; + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + */ + protected AnnotationVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link AnnotationVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + * @param annotationVisitor the annotation visitor to which this visitor must delegate method + * calls. May be {@literal null}. + */ + protected AnnotationVisitor(final int api, final AnnotationVisitor annotationVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + this.av = annotationVisitor; + } + + /** + * The annotation visitor to which this visitor must delegate method calls. May be {@literal + * null}. + * + * @return the annotation visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public AnnotationVisitor getDelegate() { + return av; + } + + /** + * Visits a primitive value of the annotation. + * + * @param name the value name. + * @param value the actual value, whose type must be {@link Byte}, {@link Boolean}, {@link + * Character}, {@link Short}, {@link Integer} , {@link Long}, {@link Float}, {@link Double}, + * {@link String} or {@link Type} of {@link Type#OBJECT} or {@link Type#ARRAY} sort. This + * value can also be an array of byte, boolean, short, char, int, long, float or double values + * (this is equivalent to using {@link #visitArray} and visiting each array element in turn, + * but is more convenient). + */ + public void visit(final String name, final Object value) { + if (av != null) { + av.visit(name, value); + } + } + + /** + * Visits an enumeration value of the annotation. + * + * @param name the value name. + * @param descriptor the class descriptor of the enumeration class. + * @param value the actual enumeration value. + */ + public void visitEnum(final String name, final String descriptor, final String value) { + if (av != null) { + av.visitEnum(name, descriptor, value); + } + } + + /** + * Visits a nested annotation value of the annotation. + * + * @param name the value name. + * @param descriptor the class descriptor of the nested annotation class. + * @return a visitor to visit the actual nested annotation value, or {@literal null} if this + * visitor is not interested in visiting this nested annotation. The nested annotation + * value must be fully visited before calling other methods on this annotation visitor. + */ + public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { + if (av != null) { + return av.visitAnnotation(name, descriptor); + } + return null; + } + + /** + * Visits an array value of the annotation. Note that arrays of primitive values (such as byte, + * boolean, short, char, int, long, float or double) can be passed as value to {@link #visit + * visit}. This is what {@link ClassReader} does for non empty arrays of primitive values. + * + * @param name the value name. + * @return a visitor to visit the actual array value elements, or {@literal null} if this visitor + * is not interested in visiting these values. The 'name' parameters passed to the methods of + * this visitor are ignored. All the array values must be visited before calling other + * methods on this annotation visitor. + */ + public AnnotationVisitor visitArray(final String name) { + if (av != null) { + return av.visitArray(name); + } + return null; + } + + /** Visits the end of the annotation. */ + public void visitEnd() { + if (av != null) { + av.visitEnd(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationWriter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationWriter.class new file mode 100644 index 00000000..738b81e9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationWriter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationWriter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationWriter.java new file mode 100644 index 00000000..fb4ae268 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/AnnotationWriter.java @@ -0,0 +1,585 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation' + * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter + * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations + * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such + * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes. + * + * @see JVMS + * 4.7.16 + * @see JVMS + * 4.7.20 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class AnnotationWriter extends AnnotationVisitor { + + /** Where the constants used in this AnnotationWriter must be stored. */ + private final SymbolTable symbolTable; + + /** + * Whether values are named or not. AnnotationWriter instances used for annotation default and + * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each + * value, instead of an element_name_index followed by an element_value). + */ + private final boolean useNamedValues; + + /** + * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values + * visited so far. All the fields of these structures, except the last one - the + * element_value_pairs array, must be set before this ByteVector is passed to the constructor + * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link + * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit() + * methods. + * + *

Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a + * single element_value by definition), this ByteVector is initially empty when passed to the + * constructor, and {@link #numElementValuePairsOffset} is set to -1. + */ + private final ByteVector annotation; + + /** + * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for + * the case of AnnotationDefault attributes). + */ + private final int numElementValuePairsOffset; + + /** The number of element value pairs visited so far. */ + private int numElementValuePairs; + + /** + * The previous AnnotationWriter. This field is used to store the list of annotations of a + * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations + * (annotation values of annotation type), or for AnnotationDefault attributes. + */ + private final AnnotationWriter previousAnnotation; + + /** + * The next AnnotationWriter. This field is used to store the list of annotations of a + * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations + * (annotation values of annotation type), or for AnnotationDefault attributes. + */ + private AnnotationWriter nextAnnotation; + + // ----------------------------------------------------------------------------------------------- + // Constructors and factories + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link AnnotationWriter}. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays + * use unnamed values. + * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to + * the visited content must be stored. This ByteVector must already contain all the fields of + * the structure except the last one (the element_value_pairs array). + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + */ + AnnotationWriter( + final SymbolTable symbolTable, + final boolean useNamedValues, + final ByteVector annotation, + final AnnotationWriter previousAnnotation) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.useNamedValues = useNamedValues; + this.annotation = annotation; + // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'. + this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2; + this.previousAnnotation = previousAnnotation; + if (previousAnnotation != null) { + previousAnnotation.nextAnnotation = this; + } + } + + /** + * Creates a new {@link AnnotationWriter} using named values. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param descriptor the class descriptor of the annotation class. + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + * @return a new {@link AnnotationWriter} for the given annotation descriptor. + */ + static AnnotationWriter create( + final SymbolTable symbolTable, + final String descriptor, + final AnnotationWriter previousAnnotation) { + // Create a ByteVector to hold an 'annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16. + ByteVector annotation = new ByteVector(); + // Write type_index and reserve space for num_element_value_pairs. + annotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter( + symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation); + } + + /** + * Creates a new {@link AnnotationWriter} using named values. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param previousAnnotation the previously visited annotation of the + * Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or + * {@literal null} in other cases (e.g. nested or array annotations). + * @return a new {@link AnnotationWriter} for the given type annotation reference and descriptor. + */ + static AnnotationWriter create( + final SymbolTable symbolTable, + final int typeRef, + final TypePath typePath, + final String descriptor, + final AnnotationWriter previousAnnotation) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + TypeReference.putTarget(typeRef, typeAnnotation); + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter( + symbolTable, /* useNamedValues = */ true, typeAnnotation, previousAnnotation); + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the AnnotationVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public void visit(final String name, final Object value) { + // Case of an element_value with a const_value_index, class_info_index or array_index field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + if (value instanceof String) { + annotation.put12('s', symbolTable.addConstantUtf8((String) value)); + } else if (value instanceof Byte) { + annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index); + } else if (value instanceof Boolean) { + int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0; + annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index); + } else if (value instanceof Character) { + annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index); + } else if (value instanceof Short) { + annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index); + } else if (value instanceof Type) { + annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor())); + } else if (value instanceof byte[]) { + byte[] byteArray = (byte[]) value; + annotation.put12('[', byteArray.length); + for (byte byteValue : byteArray) { + annotation.put12('B', symbolTable.addConstantInteger(byteValue).index); + } + } else if (value instanceof boolean[]) { + boolean[] booleanArray = (boolean[]) value; + annotation.put12('[', booleanArray.length); + for (boolean booleanValue : booleanArray) { + annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index); + } + } else if (value instanceof short[]) { + short[] shortArray = (short[]) value; + annotation.put12('[', shortArray.length); + for (short shortValue : shortArray) { + annotation.put12('S', symbolTable.addConstantInteger(shortValue).index); + } + } else if (value instanceof char[]) { + char[] charArray = (char[]) value; + annotation.put12('[', charArray.length); + for (char charValue : charArray) { + annotation.put12('C', symbolTable.addConstantInteger(charValue).index); + } + } else if (value instanceof int[]) { + int[] intArray = (int[]) value; + annotation.put12('[', intArray.length); + for (int intValue : intArray) { + annotation.put12('I', symbolTable.addConstantInteger(intValue).index); + } + } else if (value instanceof long[]) { + long[] longArray = (long[]) value; + annotation.put12('[', longArray.length); + for (long longValue : longArray) { + annotation.put12('J', symbolTable.addConstantLong(longValue).index); + } + } else if (value instanceof float[]) { + float[] floatArray = (float[]) value; + annotation.put12('[', floatArray.length); + for (float floatValue : floatArray) { + annotation.put12('F', symbolTable.addConstantFloat(floatValue).index); + } + } else if (value instanceof double[]) { + double[] doubleArray = (double[]) value; + annotation.put12('[', doubleArray.length); + for (double doubleValue : doubleArray) { + annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index); + } + } else { + Symbol symbol = symbolTable.addConstant(value); + annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index); + } + } + + @Override + public void visitEnum(final String name, final String descriptor, final String value) { + // Case of an element_value with an enum_const_value field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + annotation + .put12('e', symbolTable.addConstantUtf8(descriptor)) + .putShort(symbolTable.addConstantUtf8(value)); + } + + @Override + public AnnotationVisitor visitAnnotation(final String name, final String descriptor) { + // Case of an element_value with an annotation_value field. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1. + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs. + annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ true, annotation, null); + } + + @Override + public AnnotationVisitor visitArray(final String name) { + // Case of an element_value with an array_value field. + // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1 + ++numElementValuePairs; + if (useNamedValues) { + annotation.putShort(symbolTable.addConstantUtf8(name)); + } + // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the + // end of an element_value of array type is similar to the end of an 'annotation' structure: an + // unsigned short num_values followed by num_values element_value, versus an unsigned short + // num_element_value_pairs, followed by num_element_value_pairs { element_name_index, + // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to + // visit the array elements. Its num_element_value_pairs will correspond to the number of array + // elements and will be stored in what is in fact num_values. + annotation.put12('[', 0); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null); + } + + @Override + public void visitEnd() { + if (numElementValuePairsOffset != -1) { + byte[] data = annotation.data; + data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8); + data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs; + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation + * and all its predecessors (see {@link #previousAnnotation}. Also adds the attribute name + * to the constant pool of the class (if not null). + * + * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or {@literal null}. + * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this + * annotation and all its predecessors. This includes the size of the attribute_name_index and + * attribute_length fields. + */ + int computeAnnotationsSize(final String attributeName) { + if (attributeName != null) { + symbolTable.addConstantUtf8(attributeName); + } + // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes. + int attributeSize = 8; + AnnotationWriter annotationWriter = this; + while (annotationWriter != null) { + attributeSize += annotationWriter.annotation.length; + annotationWriter = annotationWriter.previousAnnotation; + } + return attributeSize; + } + + /** + * Returns the size of the Runtime[In]Visible[Type]Annotations attributes containing the given + * annotations and all their predecessors (see {@link #previousAnnotation}. Also adds the + * attribute names to the constant pool of the class (if not null). + * + * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or + * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be + * {@literal null}. + * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field, + * method or class. The previous ones can be accessed with the {@link #previousAnnotation} + * field. May be {@literal null}. + * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a + * field, method or class. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a + * field, method or class field. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing the + * given annotations and all their predecessors. This includes the size of the + * attribute_name_index and attribute_length fields. + */ + static int computeAnnotationsSize( + final AnnotationWriter lastRuntimeVisibleAnnotation, + final AnnotationWriter lastRuntimeInvisibleAnnotation, + final AnnotationWriter lastRuntimeVisibleTypeAnnotation, + final AnnotationWriter lastRuntimeInvisibleTypeAnnotation) { + int size = 0; + if (lastRuntimeVisibleAnnotation != null) { + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + return size; + } + + /** + * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its + * predecessors (see {@link #previousAnnotation} in the given ByteVector. Annotations are + * put in the same order they have been visited. + * + * @param attributeNameIndex the constant pool index of the attribute name (one of + * "Runtime[In]Visible[Type]Annotations"). + * @param output where the attribute must be put. + */ + void putAnnotations(final int attributeNameIndex, final ByteVector output) { + int attributeLength = 2; // For num_annotations. + int numAnnotations = 0; + AnnotationWriter annotationWriter = this; + AnnotationWriter firstAnnotation = null; + while (annotationWriter != null) { + // In case the user forgot to call visitEnd(). + annotationWriter.visitEnd(); + attributeLength += annotationWriter.annotation.length; + numAnnotations++; + firstAnnotation = annotationWriter; + annotationWriter = annotationWriter.previousAnnotation; + } + output.putShort(attributeNameIndex); + output.putInt(attributeLength); + output.putShort(numAnnotations); + annotationWriter = firstAnnotation; + while (annotationWriter != null) { + output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length); + annotationWriter = annotationWriter.nextAnnotation; + } + } + + /** + * Puts the Runtime[In]Visible[Type]Annotations attributes containing the given annotations and + * all their predecessors (see {@link #previousAnnotation} in the given ByteVector. + * Annotations are put in the same order they have been visited. + * + * @param symbolTable where the constants used in the AnnotationWriter instances are stored. + * @param lastRuntimeVisibleAnnotation The last runtime visible annotation of a field, method or + * class. The previous ones can be accessed with the {@link #previousAnnotation} field. May be + * {@literal null}. + * @param lastRuntimeInvisibleAnnotation The last runtime invisible annotation of this a field, + * method or class. The previous ones can be accessed with the {@link #previousAnnotation} + * field. May be {@literal null}. + * @param lastRuntimeVisibleTypeAnnotation The last runtime visible type annotation of this a + * field, method or class. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param lastRuntimeInvisibleTypeAnnotation The last runtime invisible type annotation of a + * field, method or class field. The previous ones can be accessed with the {@link + * #previousAnnotation} field. May be {@literal null}. + * @param output where the attributes must be put. + */ + static void putAnnotations( + final SymbolTable symbolTable, + final AnnotationWriter lastRuntimeVisibleAnnotation, + final AnnotationWriter lastRuntimeInvisibleAnnotation, + final AnnotationWriter lastRuntimeVisibleTypeAnnotation, + final AnnotationWriter lastRuntimeInvisibleTypeAnnotation, + final ByteVector output) { + if (lastRuntimeVisibleAnnotation != null) { + lastRuntimeVisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleAnnotation != null) { + lastRuntimeInvisibleAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_ANNOTATIONS), output); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + lastRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + lastRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + } + + /** + * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the + * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the + * constant pool of the class. + * + * @param attributeName one of "Runtime[In]VisibleParameterAnnotations". + * @param annotationWriters an array of AnnotationWriter lists (designated by their last + * element). + * @param annotableParameterCount the number of elements in annotationWriters to take into account + * (elements [0..annotableParameterCount[ are taken into account). + * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding + * to the given sub-array of AnnotationWriter lists. This includes the size of the + * attribute_name_index and attribute_length fields. + */ + static int computeParameterAnnotationsSize( + final String attributeName, + final AnnotationWriter[] annotationWriters, + final int annotableParameterCount) { + // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize + // below. This assumes that there is at least one non-null element in the annotationWriters + // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter). + // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each + // element of the parameter_annotations array uses 2 bytes for its num_annotations field. + int attributeSize = 7 + 2 * annotableParameterCount; + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + attributeSize += + annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8; + } + return attributeSize; + } + + /** + * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists + * from the given AnnotationWriter sub-array in the given ByteVector. + * + * @param attributeNameIndex constant pool index of the attribute name (one of + * Runtime[In]VisibleParameterAnnotations). + * @param annotationWriters an array of AnnotationWriter lists (designated by their last + * element). + * @param annotableParameterCount the number of elements in annotationWriters to put (elements + * [0..annotableParameterCount[ are put). + * @param output where the attribute must be put. + */ + static void putParameterAnnotations( + final int attributeNameIndex, + final AnnotationWriter[] annotationWriters, + final int annotableParameterCount, + final ByteVector output) { + // The num_parameters field uses 1 byte, and each element of the parameter_annotations array + // uses 2 bytes for its num_annotations field. + int attributeLength = 1 + 2 * annotableParameterCount; + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + attributeLength += + annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8; + } + output.putShort(attributeNameIndex); + output.putInt(attributeLength); + output.putByte(annotableParameterCount); + for (int i = 0; i < annotableParameterCount; ++i) { + AnnotationWriter annotationWriter = annotationWriters[i]; + AnnotationWriter firstAnnotation = null; + int numAnnotations = 0; + while (annotationWriter != null) { + // In case user the forgot to call visitEnd(). + annotationWriter.visitEnd(); + numAnnotations++; + firstAnnotation = annotationWriter; + annotationWriter = annotationWriter.previousAnnotation; + } + output.putShort(numAnnotations); + annotationWriter = firstAnnotation; + while (annotationWriter != null) { + output.putByteArray( + annotationWriter.annotation.data, 0, annotationWriter.annotation.length); + annotationWriter = annotationWriter.nextAnnotation; + } + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute$Set.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute$Set.class new file mode 100644 index 00000000..29fe87c1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute$Set.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute.class new file mode 100644 index 00000000..04d569e5 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute.java new file mode 100644 index 00000000..9c5ce91e --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Attribute.java @@ -0,0 +1,424 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A non standard class, field, method or Code attribute, as defined in the Java Virtual Machine + * Specification (JVMS). + * + * @see JVMS + * 4.7 + * @see JVMS + * 4.7.3 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class Attribute { + + /** The type of this attribute, also called its name in the JVMS. */ + public final String type; + + /** + * The raw content of this attribute, only used for unknown attributes (see {@link #isUnknown()}). + * The 6 header bytes of the attribute (attribute_name_index and attribute_length) are not + * included. + */ + private byte[] content; + + /** + * The next attribute in this attribute list (Attribute instances can be linked via this field to + * store a list of class, field, method or Code attributes). May be {@literal null}. + */ + Attribute nextAttribute; + + /** + * Constructs a new empty attribute. + * + * @param type the type of the attribute. + */ + protected Attribute(final String type) { + this.type = type; + } + + /** + * Returns {@literal true} if this type of attribute is unknown. This means that the attribute + * content can't be parsed to extract constant pool references, labels, etc. Instead, the + * attribute content is read as an opaque byte array, and written back as is. This can lead to + * invalid attributes, if the content actually contains constant pool references, labels, or other + * symbolic references that need to be updated when there are changes to the constant pool, the + * method bytecode, etc. The default implementation of this method always returns {@literal true}. + * + * @return {@literal true} if this type of attribute is unknown. + */ + public boolean isUnknown() { + return true; + } + + /** + * Returns {@literal true} if this type of attribute is a Code attribute. + * + * @return {@literal true} if this type of attribute is a Code attribute. + */ + public boolean isCodeAttribute() { + return false; + } + + /** + * Returns the labels corresponding to this attribute. + * + * @return the labels corresponding to this attribute, or {@literal null} if this attribute is not + * a Code attribute that contains labels. + */ + protected Label[] getLabels() { + return new Label[0]; + } + + /** + * Reads a {@link #type} attribute. This method must return a new {@link Attribute} object, + * of type {@link #type}, corresponding to the 'length' bytes starting at 'offset', in the given + * ClassReader. + * + * @param classReader the class that contains the attribute to be read. + * @param offset index of the first byte of the attribute's content in {@link ClassReader}. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to call the ClassReader methods requiring a + * 'charBuffer' parameter. + * @param codeAttributeOffset index of the first byte of content of the enclosing Code attribute + * in {@link ClassReader}, or -1 if the attribute to be read is not a Code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * is not a Code attribute. + * @return a new {@link Attribute} object corresponding to the specified bytes. + */ + protected Attribute read( + final ClassReader classReader, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + Attribute attribute = new Attribute(type); + attribute.content = new byte[length]; + System.arraycopy(classReader.classFileBuffer, offset, attribute.content, 0, length); + return attribute; + } + + /** + * Returns the byte array form of the content of this attribute. The 6 header bytes + * (attribute_name_index and attribute_length) must not be added in the returned + * ByteVector. + * + * @param classWriter the class to which this attribute must be added. This parameter can be used + * to add the items that corresponds to this attribute to the constant pool of this class. + * @param code the bytecode of the method corresponding to this Code attribute, or {@literal null} + * if this attribute is not a Code attribute. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to this code + * attribute, or 0 if this attribute is not a Code attribute. Corresponds to the 'code_length' + * field of the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to this Code attribute, or + * -1 if this attribute is not a Code attribute. + * @param maxLocals the maximum number of local variables of the method corresponding to this code + * attribute, or -1 if this attribute is not a Code attribute. + * @return the byte array form of this attribute. + */ + protected ByteVector write( + final ClassWriter classWriter, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + return new ByteVector(content); + } + + /** + * Returns the number of attributes of the attribute list that begins with this attribute. + * + * @return the number of attributes of the attribute list that begins with this attribute. + */ + final int getAttributeCount() { + int count = 0; + Attribute attribute = this; + while (attribute != null) { + count += 1; + attribute = attribute.nextAttribute; + } + return count; + } + + /** + * Returns the total size in bytes of all the attributes in the attribute list that begins with + * this attribute. This size includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. Also adds the attribute type names to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @return the size of all the attributes in this attribute list. This size includes the size of + * the attribute headers. + */ + final int computeAttributesSize(final SymbolTable symbolTable) { + final byte[] code = null; + final int codeLength = 0; + final int maxStack = -1; + final int maxLocals = -1; + return computeAttributesSize(symbolTable, code, codeLength, maxStack, maxLocals); + } + + /** + * Returns the total size in bytes of all the attributes in the attribute list that begins with + * this attribute. This size includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. Also adds the attribute type names to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param code the bytecode of the method corresponding to these Code attributes, or {@literal + * null} if they are not Code attributes. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to these code + * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of + * the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or + * -1 if they are not Code attributes. + * @param maxLocals the maximum number of local variables of the method corresponding to these + * Code attributes, or -1 if they are not Code attribute. + * @return the size of all the attributes in this attribute list. This size includes the size of + * the attribute headers. + */ + final int computeAttributesSize( + final SymbolTable symbolTable, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals) { + final ClassWriter classWriter = symbolTable.classWriter; + int size = 0; + Attribute attribute = this; + while (attribute != null) { + symbolTable.addConstantUtf8(attribute.type); + size += 6 + attribute.write(classWriter, code, codeLength, maxStack, maxLocals).length; + attribute = attribute.nextAttribute; + } + return size; + } + + /** + * Returns the total size in bytes of all the attributes that correspond to the given field, + * method or class access flags and signature. This size includes the 6 header bytes + * (attribute_name_index and attribute_length) per attribute. Also adds the attribute type names + * to the constant pool. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param accessFlags some field, method or class access flags. + * @param signatureIndex the constant pool index of a field, method of class signature. + * @return the size of all the attributes in bytes. This size includes the size of the attribute + * headers. + */ + static int computeAttributesSize( + final SymbolTable symbolTable, final int accessFlags, final int signatureIndex) { + int size = 0; + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + // Synthetic attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + size += 6; + } + if (signatureIndex != 0) { + // Signature attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.SIGNATURE); + size += 8; + } + // ACC_DEPRECATED is ASM specific, the ClassFile format uses a Deprecated attribute instead. + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + // Deprecated attributes always use 6 bytes. + symbolTable.addConstantUtf8(Constants.DEPRECATED); + size += 6; + } + return size; + } + + /** + * Puts all the attributes of the attribute list that begins with this attribute, in the given + * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per + * attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param output where the attributes must be written. + */ + final void putAttributes(final SymbolTable symbolTable, final ByteVector output) { + final byte[] code = null; + final int codeLength = 0; + final int maxStack = -1; + final int maxLocals = -1; + putAttributes(symbolTable, code, codeLength, maxStack, maxLocals, output); + } + + /** + * Puts all the attributes of the attribute list that begins with this attribute, in the given + * byte vector. This includes the 6 header bytes (attribute_name_index and attribute_length) per + * attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param code the bytecode of the method corresponding to these Code attributes, or {@literal + * null} if they are not Code attributes. Corresponds to the 'code' field of the Code + * attribute. + * @param codeLength the length of the bytecode of the method corresponding to these code + * attributes, or 0 if they are not Code attributes. Corresponds to the 'code_length' field of + * the Code attribute. + * @param maxStack the maximum stack size of the method corresponding to these Code attributes, or + * -1 if they are not Code attributes. + * @param maxLocals the maximum number of local variables of the method corresponding to these + * Code attributes, or -1 if they are not Code attribute. + * @param output where the attributes must be written. + */ + final void putAttributes( + final SymbolTable symbolTable, + final byte[] code, + final int codeLength, + final int maxStack, + final int maxLocals, + final ByteVector output) { + final ClassWriter classWriter = symbolTable.classWriter; + Attribute attribute = this; + while (attribute != null) { + ByteVector attributeContent = + attribute.write(classWriter, code, codeLength, maxStack, maxLocals); + // Put attribute_name_index and attribute_length. + output.putShort(symbolTable.addConstantUtf8(attribute.type)).putInt(attributeContent.length); + output.putByteArray(attributeContent.data, 0, attributeContent.length); + attribute = attribute.nextAttribute; + } + } + + /** + * Puts all the attributes that correspond to the given field, method or class access flags and + * signature, in the given byte vector. This includes the 6 header bytes (attribute_name_index and + * attribute_length) per attribute. + * + * @param symbolTable where the constants used in the attributes must be stored. + * @param accessFlags some field, method or class access flags. + * @param signatureIndex the constant pool index of a field, method of class signature. + * @param output where the attributes must be written. + */ + static void putAttributes( + final SymbolTable symbolTable, + final int accessFlags, + final int signatureIndex, + final ByteVector output) { + // Before Java 1.5, synthetic fields are represented with a Synthetic attribute. + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 + && symbolTable.getMajorVersion() < Opcodes.V1_5) { + output.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + output.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + } + + /** A set of attribute prototypes (attributes with the same type are considered equal). */ + static final class Set { + + private static final int SIZE_INCREMENT = 6; + + private int size; + private Attribute[] data = new Attribute[SIZE_INCREMENT]; + + void addAttributes(final Attribute attributeList) { + Attribute attribute = attributeList; + while (attribute != null) { + if (!contains(attribute)) { + add(attribute); + } + attribute = attribute.nextAttribute; + } + } + + Attribute[] toArray() { + Attribute[] result = new Attribute[size]; + System.arraycopy(data, 0, result, 0, size); + return result; + } + + private boolean contains(final Attribute attribute) { + for (int i = 0; i < size; ++i) { + if (data[i].type.equals(attribute.type)) { + return true; + } + } + return false; + } + + private void add(final Attribute attribute) { + if (size >= data.length) { + Attribute[] newData = new Attribute[data.length + SIZE_INCREMENT]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + data[size++] = attribute; + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ByteVector.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ByteVector.class new file mode 100644 index 00000000..30f683a1 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ByteVector.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ByteVector.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ByteVector.java new file mode 100644 index 00000000..33a66b2c --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ByteVector.java @@ -0,0 +1,405 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A dynamically extensible vector of bytes. This class is roughly equivalent to a DataOutputStream + * on top of a ByteArrayOutputStream, but is more efficient. + * + * @author Eric Bruneton + */ +public class ByteVector { + + /** The content of this vector. Only the first {@link #length} bytes contain real data. */ + byte[] data; + + /** The actual number of bytes in this vector. */ + int length; + + /** Constructs a new {@link ByteVector} with a default initial capacity. */ + public ByteVector() { + data = new byte[64]; + } + + /** + * Constructs a new {@link ByteVector} with the given initial capacity. + * + * @param initialCapacity the initial capacity of the byte vector to be constructed. + */ + public ByteVector(final int initialCapacity) { + data = new byte[initialCapacity]; + } + + /** + * Constructs a new {@link ByteVector} from the given initial data. + * + * @param data the initial data of the new byte vector. + */ + ByteVector(final byte[] data) { + this.data = data; + this.length = data.length; + } + + /** + * Returns the actual number of bytes in this vector. + * + * @return the actual number of bytes in this vector. + */ + public int size() { + return length; + } + + /** + * Puts a byte into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param byteValue a byte. + * @return this byte vector. + */ + public ByteVector putByte(final int byteValue) { + int currentLength = length; + if (currentLength + 1 > data.length) { + enlarge(1); + } + data[currentLength++] = (byte) byteValue; + length = currentLength; + return this; + } + + /** + * Puts two bytes into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param byteValue1 a byte. + * @param byteValue2 another byte. + * @return this byte vector. + */ + final ByteVector put11(final int byteValue1, final int byteValue2) { + int currentLength = length; + if (currentLength + 2 > data.length) { + enlarge(2); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue1; + currentData[currentLength++] = (byte) byteValue2; + length = currentLength; + return this; + } + + /** + * Puts a short into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param shortValue a short. + * @return this byte vector. + */ + public ByteVector putShort(final int shortValue) { + int currentLength = length; + if (currentLength + 2 > data.length) { + enlarge(2); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } + + /** + * Puts a byte and a short into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteValue a byte. + * @param shortValue a short. + * @return this byte vector. + */ + final ByteVector put12(final int byteValue, final int shortValue) { + int currentLength = length; + if (currentLength + 3 > data.length) { + enlarge(3); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } + + /** + * Puts two bytes and a short into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteValue1 a byte. + * @param byteValue2 another byte. + * @param shortValue a short. + * @return this byte vector. + */ + final ByteVector put112(final int byteValue1, final int byteValue2, final int shortValue) { + int currentLength = length; + if (currentLength + 4 > data.length) { + enlarge(4); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue1; + currentData[currentLength++] = (byte) byteValue2; + currentData[currentLength++] = (byte) (shortValue >>> 8); + currentData[currentLength++] = (byte) shortValue; + length = currentLength; + return this; + } + + /** + * Puts an int into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param intValue an int. + * @return this byte vector. + */ + public ByteVector putInt(final int intValue) { + int currentLength = length; + if (currentLength + 4 > data.length) { + enlarge(4); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + length = currentLength; + return this; + } + + /** + * Puts one byte and two shorts into this byte vector. The byte vector is automatically enlarged + * if necessary. + * + * @param byteValue a byte. + * @param shortValue1 a short. + * @param shortValue2 another short. + * @return this byte vector. + */ + final ByteVector put122(final int byteValue, final int shortValue1, final int shortValue2) { + int currentLength = length; + if (currentLength + 5 > data.length) { + enlarge(5); + } + byte[] currentData = data; + currentData[currentLength++] = (byte) byteValue; + currentData[currentLength++] = (byte) (shortValue1 >>> 8); + currentData[currentLength++] = (byte) shortValue1; + currentData[currentLength++] = (byte) (shortValue2 >>> 8); + currentData[currentLength++] = (byte) shortValue2; + length = currentLength; + return this; + } + + /** + * Puts a long into this byte vector. The byte vector is automatically enlarged if necessary. + * + * @param longValue a long. + * @return this byte vector. + */ + public ByteVector putLong(final long longValue) { + int currentLength = length; + if (currentLength + 8 > data.length) { + enlarge(8); + } + byte[] currentData = data; + int intValue = (int) (longValue >>> 32); + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + intValue = (int) longValue; + currentData[currentLength++] = (byte) (intValue >>> 24); + currentData[currentLength++] = (byte) (intValue >>> 16); + currentData[currentLength++] = (byte) (intValue >>> 8); + currentData[currentLength++] = (byte) intValue; + length = currentLength; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param stringValue a String whose UTF8 encoded length must be less than 65536. + * @return this byte vector. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public ByteVector putUTF8(final String stringValue) { + int charLength = stringValue.length(); + if (charLength > 65535) { + throw new IllegalArgumentException("UTF8 string too large"); + } + int currentLength = length; + if (currentLength + 2 + charLength > data.length) { + enlarge(2 + charLength); + } + byte[] currentData = data; + // Optimistic algorithm: instead of computing the byte length and then serializing the string + // (which requires two loops), we assume the byte length is equal to char length (which is the + // most frequent case), and we start serializing the string right away. During the + // serialization, if we find that this assumption is wrong, we continue with the general method. + currentData[currentLength++] = (byte) (charLength >>> 8); + currentData[currentLength++] = (byte) charLength; + for (int i = 0; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= '\u0001' && charValue <= '\u007F') { + currentData[currentLength++] = (byte) charValue; + } else { + length = currentLength; + return encodeUtf8(stringValue, i, 65535); + } + } + length = currentLength; + return this; + } + + /** + * Puts an UTF8 string into this byte vector. The byte vector is automatically enlarged if + * necessary. The string length is encoded in two bytes before the encoded characters, if there is + * space for that (i.e. if this.length - offset - 2 >= 0). + * + * @param stringValue the String to encode. + * @param offset the index of the first character to encode. The previous characters are supposed + * to have already been encoded, using only one byte per character. + * @param maxByteLength the maximum byte length of the encoded string, including the already + * encoded characters. + * @return this byte vector. + */ + final ByteVector encodeUtf8(final String stringValue, final int offset, final int maxByteLength) { + int charLength = stringValue.length(); + int byteLength = offset; + for (int i = offset; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= 0x0001 && charValue <= 0x007F) { + byteLength++; + } else if (charValue <= 0x07FF) { + byteLength += 2; + } else { + byteLength += 3; + } + } + if (byteLength > maxByteLength) { + throw new IllegalArgumentException("UTF8 string too large"); + } + // Compute where 'byteLength' must be stored in 'data', and store it at this location. + int byteLengthOffset = length - offset - 2; + if (byteLengthOffset >= 0) { + data[byteLengthOffset] = (byte) (byteLength >>> 8); + data[byteLengthOffset + 1] = (byte) byteLength; + } + if (length + byteLength - offset > data.length) { + enlarge(byteLength - offset); + } + int currentLength = length; + for (int i = offset; i < charLength; ++i) { + char charValue = stringValue.charAt(i); + if (charValue >= 0x0001 && charValue <= 0x007F) { + data[currentLength++] = (byte) charValue; + } else if (charValue <= 0x07FF) { + data[currentLength++] = (byte) (0xC0 | charValue >> 6 & 0x1F); + data[currentLength++] = (byte) (0x80 | charValue & 0x3F); + } else { + data[currentLength++] = (byte) (0xE0 | charValue >> 12 & 0xF); + data[currentLength++] = (byte) (0x80 | charValue >> 6 & 0x3F); + data[currentLength++] = (byte) (0x80 | charValue & 0x3F); + } + } + length = currentLength; + return this; + } + + /** + * Puts an array of bytes into this byte vector. The byte vector is automatically enlarged if + * necessary. + * + * @param byteArrayValue an array of bytes. May be {@literal null} to put {@code byteLength} null + * bytes into this byte vector. + * @param byteOffset index of the first byte of byteArrayValue that must be copied. + * @param byteLength number of bytes of byteArrayValue that must be copied. + * @return this byte vector. + */ + public ByteVector putByteArray( + final byte[] byteArrayValue, final int byteOffset, final int byteLength) { + if (length + byteLength > data.length) { + enlarge(byteLength); + } + if (byteArrayValue != null) { + System.arraycopy(byteArrayValue, byteOffset, data, length, byteLength); + } + length += byteLength; + return this; + } + + /** + * Enlarges this byte vector so that it can receive 'size' more bytes. + * + * @param size number of additional bytes that this byte vector should be able to receive. + */ + private void enlarge(final int size) { + if (length > data.length) { + throw new AssertionError("Internal error"); + } + int doubleCapacity = 2 * data.length; + int minimalCapacity = length + size; + byte[] newData = new byte[doubleCapacity > minimalCapacity ? doubleCapacity : minimalCapacity]; + System.arraycopy(data, 0, newData, 0, length); + data = newData; + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassReader.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassReader.class new file mode 100644 index 00000000..01cea985 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassReader.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassReader.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassReader.java new file mode 100644 index 00000000..1e31e41e --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassReader.java @@ -0,0 +1,3900 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A parser to make a {@link ClassVisitor} visit a ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). This class parses the ClassFile content and calls the + * appropriate visit methods of a given {@link ClassVisitor} for each field, method and bytecode + * instruction encountered. + * + * @see JVMS 4 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +public class ClassReader { + + /** + * A flag to skip the Code attributes. If this flag is set the Code attributes are neither parsed + * nor visited. + */ + public static final int SKIP_CODE = 1; + + /** + * A flag to skip the SourceFile, SourceDebugExtension, LocalVariableTable, + * LocalVariableTypeTable, LineNumberTable and MethodParameters attributes. If this flag is set + * these attributes are neither parsed nor visited (i.e. {@link ClassVisitor#visitSource}, {@link + * MethodVisitor#visitLocalVariable}, {@link MethodVisitor#visitLineNumber} and {@link + * MethodVisitor#visitParameter} are not called). + */ + public static final int SKIP_DEBUG = 2; + + /** + * A flag to skip the StackMap and StackMapTable attributes. If this flag is set these attributes + * are neither parsed nor visited (i.e. {@link MethodVisitor#visitFrame} is not called). This flag + * is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is used: it avoids visiting frames + * that will be ignored and recomputed from scratch. + */ + public static final int SKIP_FRAMES = 4; + + /** + * A flag to expand the stack map frames. By default stack map frames are visited in their + * original format (i.e. "expanded" for classes whose version is less than V1_6, and "compressed" + * for the other classes). If this flag is set, stack map frames are always visited in expanded + * format (this option adds a decompression/compression step in ClassReader and ClassWriter which + * degrades performance quite a lot). + */ + public static final int EXPAND_FRAMES = 8; + + /** + * A flag to expand the ASM specific instructions into an equivalent sequence of standard bytecode + * instructions. When resolving a forward jump it may happen that the signed 2 bytes offset + * reserved for it is not sufficient to store the bytecode offset. In this case the jump + * instruction is replaced with a temporary ASM specific instruction using an unsigned 2 bytes + * offset (see {@link Label#resolve}). This internal flag is used to re-read classes containing + * such instructions, in order to replace them with standard instructions. In addition, when this + * flag is used, goto_w and jsr_w are not converted into goto and jsr, to make sure that + * infinite loops where a goto_w is replaced with a goto in ClassReader and converted back to a + * goto_w in ClassWriter cannot occur. + */ + static final int EXPAND_ASM_INSNS = 256; + + /** The maximum size of array to allocate. */ + private static final int MAX_BUFFER_SIZE = 1024 * 1024; + + /** The size of the temporary byte array used to read class input streams chunk by chunk. */ + private static final int INPUT_STREAM_DATA_CHUNK_SIZE = 4096; + + /** + * A byte array containing the JVMS ClassFile structure to be parsed. + * + * @deprecated Use {@link #readByte(int)} and the other read methods instead. This field will + * eventually be deleted. + */ + @Deprecated + // DontCheck(MemberName): can't be renamed (for backward binary compatibility). + public final byte[] b; + + /** The offset in bytes of the ClassFile's access_flags field. */ + public final int header; + + /** + * A byte array containing the JVMS ClassFile structure to be parsed. The content of this array + * must not be modified. This field is intended for {@link Attribute} sub classes, and is normally + * not needed by class visitors. + * + *

NOTE: the ClassFile structure can start at any offset within this array, i.e. it does not + * necessarily start at offset 0. Use {@link #getItem} and {@link #header} to get correct + * ClassFile element offsets within this byte array. + */ + final byte[] classFileBuffer; + + /** + * The offset in bytes, in {@link #classFileBuffer}, of each cp_info entry of the ClassFile's + * constant_pool array, plus one. In other words, the offset of constant pool entry i is + * given by cpInfoOffsets[i] - 1, i.e. its cp_info's tag field is given by b[cpInfoOffsets[i] - + * 1]. + */ + private final int[] cpInfoOffsets; + + /** + * The String objects corresponding to the CONSTANT_Utf8 constant pool items. This cache avoids + * multiple parsing of a given CONSTANT_Utf8 constant pool item. + */ + private final String[] constantUtf8Values; + + /** + * The ConstantDynamic objects corresponding to the CONSTANT_Dynamic constant pool items. This + * cache avoids multiple parsing of a given CONSTANT_Dynamic constant pool item. + */ + private final ConstantDynamic[] constantDynamicValues; + + /** + * The start offsets in {@link #classFileBuffer} of each element of the bootstrap_methods array + * (in the BootstrapMethods attribute). + * + * @see JVMS + * 4.7.23 + */ + private final int[] bootstrapMethodOffsets; + + /** + * A conservative estimate of the maximum length of the strings contained in the constant pool of + * the class. + */ + private final int maxStringLength; + + // ----------------------------------------------------------------------------------------------- + // Constructors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFile the JVMS ClassFile structure to be read. + */ + public ClassReader(final byte[] classFile) { + this(classFile, 0, classFile.length); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param classFileLength the length in bytes of the ClassFile to be read. + */ + @SuppressWarnings("this-escape") + public ClassReader( + final byte[] classFileBuffer, + final int classFileOffset, + final int classFileLength) { // NOPMD(UnusedFormalParameter) used for backward compatibility. + this(classFileBuffer, classFileOffset, /* checkClassVersion = */ true); + } + + /** + * Constructs a new {@link ClassReader} object. This internal constructor must not be exposed + * as a public API. + * + * @param classFileBuffer a byte array containing the JVMS ClassFile structure to be read. + * @param classFileOffset the offset in byteBuffer of the first byte of the ClassFile to be read. + * @param checkClassVersion whether to check the class version or not. + */ + ClassReader( + final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion) { + this.classFileBuffer = classFileBuffer; + this.b = classFileBuffer; + // Check the class' major_version. This field is after the magic and minor_version fields, which + // use 4 and 2 bytes respectively. + if (checkClassVersion && readShort(classFileOffset + 6) > Opcodes.V23) { + throw new IllegalArgumentException( + "Unsupported class file major version " + readShort(classFileOffset + 6)); + } + // Create the constant pool arrays. The constant_pool_count field is after the magic, + // minor_version and major_version fields, which use 4, 2 and 2 bytes respectively. + int constantPoolCount = readUnsignedShort(classFileOffset + 8); + cpInfoOffsets = new int[constantPoolCount]; + constantUtf8Values = new String[constantPoolCount]; + // Compute the offset of each constant pool entry, as well as a conservative estimate of the + // maximum length of the constant pool strings. The first constant pool entry is after the + // magic, minor_version, major_version and constant_pool_count fields, which use 4, 2, 2 and 2 + // bytes respectively. + int currentCpInfoIndex = 1; + int currentCpInfoOffset = classFileOffset + 10; + int currentMaxStringLength = 0; + boolean hasBootstrapMethods = false; + boolean hasConstantDynamic = false; + // The offset of the other entries depend on the total size of all the previous entries. + while (currentCpInfoIndex < constantPoolCount) { + cpInfoOffsets[currentCpInfoIndex++] = currentCpInfoOffset + 1; + int cpInfoSize; + switch (classFileBuffer[currentCpInfoOffset]) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + cpInfoSize = 5; + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; + hasConstantDynamic = true; + break; + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + cpInfoSize = 5; + hasBootstrapMethods = true; + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + cpInfoSize = 9; + currentCpInfoIndex++; + break; + case Symbol.CONSTANT_UTF8_TAG: + cpInfoSize = 3 + readUnsignedShort(currentCpInfoOffset + 1); + if (cpInfoSize > currentMaxStringLength) { + // The size in bytes of this CONSTANT_Utf8 structure provides a conservative estimate + // of the length in characters of the corresponding string, and is much cheaper to + // compute than this exact length. + currentMaxStringLength = cpInfoSize; + } + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + cpInfoSize = 4; + break; + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + cpInfoSize = 3; + break; + default: + throw new IllegalArgumentException(); + } + currentCpInfoOffset += cpInfoSize; + } + maxStringLength = currentMaxStringLength; + // The Classfile's access_flags field is just after the last constant pool entry. + header = currentCpInfoOffset; + + // Allocate the cache of ConstantDynamic values, if there is at least one. + constantDynamicValues = hasConstantDynamic ? new ConstantDynamic[constantPoolCount] : null; + + // Read the BootstrapMethods attribute, if any (only get the offset of each method). + bootstrapMethodOffsets = + hasBootstrapMethods ? readBootstrapMethodsAttribute(currentMaxStringLength) : null; + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param inputStream an input stream of the JVMS ClassFile structure to be read. This input + * stream must contain nothing more than the ClassFile structure itself. It is read from its + * current position to its end. + * @throws IOException if a problem occurs during reading. + */ + public ClassReader(final InputStream inputStream) throws IOException { + this(readStream(inputStream, false)); + } + + /** + * Constructs a new {@link ClassReader} object. + * + * @param className the fully qualified name of the class to be read. The ClassFile structure is + * retrieved with the current class loader's {@link ClassLoader#getSystemResourceAsStream}. + * @throws IOException if an exception occurs during reading. + */ + public ClassReader(final String className) throws IOException { + this( + readStream( + ClassLoader.getSystemResourceAsStream(className.replace('.', '/') + ".class"), true)); + } + + /** + * Reads the given input stream and returns its content as a byte array. + * + * @param inputStream an input stream. + * @param close true to close the input stream after reading. + * @return the content of the given input stream. + * @throws IOException if a problem occurs during reading. + */ + @SuppressWarnings("PMD.UseTryWithResources") + private static byte[] readStream(final InputStream inputStream, final boolean close) + throws IOException { + if (inputStream == null) { + throw new IOException("Class not found"); + } + int bufferSize = computeBufferSize(inputStream); + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] data = new byte[bufferSize]; + int bytesRead; + int readCount = 0; + while ((bytesRead = inputStream.read(data, 0, bufferSize)) != -1) { + outputStream.write(data, 0, bytesRead); + readCount++; + } + outputStream.flush(); + if (readCount == 1) { + return data; + } + return outputStream.toByteArray(); + } finally { + if (close) { + inputStream.close(); + } + } + } + + private static int computeBufferSize(final InputStream inputStream) throws IOException { + int expectedLength = inputStream.available(); + /* + * Some implementations can return 0 while holding available data (e.g. new + * FileInputStream("/proc/a_file")). Also in some pathological cases a very small number might + * be returned, and in this case we use a default size. + */ + if (expectedLength < 256) { + return INPUT_STREAM_DATA_CHUNK_SIZE; + } + return Math.min(expectedLength, MAX_BUFFER_SIZE); + } + + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the class's access flags (see {@link Opcodes}). This value may not reflect Deprecated + * and Synthetic flags when bytecode is before 1.5 and those flags are represented by attributes. + * + * @return the class access flags. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public int getAccess() { + return readUnsignedShort(header); + } + + /** + * Returns the internal name of the class (see {@link Type#getInternalName()}). + * + * @return the internal class name. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getClassName() { + // this_class is just after the access_flags field (using 2 bytes). + return readClass(header + 2, new char[maxStringLength]); + } + + /** + * Returns the internal name of the super class (see {@link Type#getInternalName()}). For + * interfaces, the super class is {@link Object}. + * + * @return the internal name of the super class, or {@literal null} for {@link Object} class. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String getSuperName() { + // super_class is after the access_flags and this_class fields (2 bytes each). + return readClass(header + 4, new char[maxStringLength]); + } + + /** + * Returns the internal names of the implemented interfaces (see {@link Type#getInternalName()}). + * + * @return the internal names of the directly implemented interfaces. Inherited implemented + * interfaces are not returned. + * @see ClassVisitor#visit(int, int, String, String, String, String[]) + */ + public String[] getInterfaces() { + // interfaces_count is after the access_flags, this_class and super_class fields (2 bytes each). + int currentOffset = header + 6; + int interfacesCount = readUnsignedShort(currentOffset); + String[] interfaces = new String[interfacesCount]; + if (interfacesCount > 0) { + char[] charBuffer = new char[maxStringLength]; + for (int i = 0; i < interfacesCount; ++i) { + currentOffset += 2; + interfaces[i] = readClass(currentOffset, charBuffer); + } + } + return interfaces; + } + + // ----------------------------------------------------------------------------------------------- + // Public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept(final ClassVisitor classVisitor, final int parsingOptions) { + accept(classVisitor, new Attribute[0], parsingOptions); + } + + /** + * Makes the given visitor visit the JVMS ClassFile structure passed to the constructor of this + * {@link ClassReader}. + * + * @param classVisitor the visitor that must visit this class. + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. This may + * corrupt it if this value contains references to the constant pool, or has syntactic or + * semantic links with a class element that has been transformed by a class adapter between + * the reader and the writer. + * @param parsingOptions the options to use to parse this class. One or more of {@link + * #SKIP_CODE}, {@link #SKIP_DEBUG}, {@link #SKIP_FRAMES} or {@link #EXPAND_FRAMES}. + */ + public void accept( + final ClassVisitor classVisitor, + final Attribute[] attributePrototypes, + final int parsingOptions) { + Context context = new Context(); + context.attributePrototypes = attributePrototypes; + context.parsingOptions = parsingOptions; + context.charBuffer = new char[maxStringLength]; + + // Read the access_flags, this_class, super_class, interface_count and interfaces fields. + char[] charBuffer = context.charBuffer; + int currentOffset = header; + int accessFlags = readUnsignedShort(currentOffset); + String thisClass = readClass(currentOffset + 2, charBuffer); + String superClass = readClass(currentOffset + 4, charBuffer); + String[] interfaces = new String[readUnsignedShort(currentOffset + 6)]; + currentOffset += 8; + for (int i = 0; i < interfaces.length; ++i) { + interfaces[i] = readClass(currentOffset, charBuffer); + currentOffset += 2; + } + + // Read the class attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the InnerClasses attribute, or 0. + int innerClassesOffset = 0; + // - The offset of the EnclosingMethod attribute, or 0. + int enclosingMethodOffset = 0; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The string corresponding to the SourceFile attribute, or null. + String sourceFile = null; + // - The string corresponding to the SourceDebugExtension attribute, or null. + String sourceDebugExtension = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the Module attribute, or 0. + int moduleOffset = 0; + // - The offset of the ModulePackages attribute, or 0. + int modulePackagesOffset = 0; + // - The string corresponding to the ModuleMainClass attribute, or null. + String moduleMainClass = null; + // - The string corresponding to the NestHost attribute, or null. + String nestHostClass = null; + // - The offset of the NestMembers attribute, or 0. + int nestMembersOffset = 0; + // - The offset of the PermittedSubclasses attribute, or 0 + int permittedSubclassesOffset = 0; + // - The offset of the Record attribute, or 0. + int recordOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int currentAttributeOffset = getFirstAttributeOffset(); + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SOURCE_FILE.equals(attributeName)) { + sourceFile = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.INNER_CLASSES.equals(attributeName)) { + innerClassesOffset = currentAttributeOffset; + } else if (Constants.ENCLOSING_METHOD.equals(attributeName)) { + enclosingMethodOffset = currentAttributeOffset; + } else if (Constants.NEST_HOST.equals(attributeName)) { + nestHostClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.NEST_MEMBERS.equals(attributeName)) { + nestMembersOffset = currentAttributeOffset; + } else if (Constants.PERMITTED_SUBCLASSES.equals(attributeName)) { + permittedSubclassesOffset = currentAttributeOffset; + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentAttributeOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.SOURCE_DEBUG_EXTENSION.equals(attributeName)) { + if (attributeLength > classFileBuffer.length - currentAttributeOffset) { + throw new IllegalArgumentException(); + } + sourceDebugExtension = + readUtf(currentAttributeOffset, attributeLength, new char[attributeLength]); + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentAttributeOffset; + } else if (Constants.RECORD.equals(attributeName)) { + recordOffset = currentAttributeOffset; + accessFlags |= Opcodes.ACC_RECORD; + } else if (Constants.MODULE.equals(attributeName)) { + moduleOffset = currentAttributeOffset; + } else if (Constants.MODULE_MAIN_CLASS.equals(attributeName)) { + moduleMainClass = readClass(currentAttributeOffset, charBuffer); + } else if (Constants.MODULE_PACKAGES.equals(attributeName)) { + modulePackagesOffset = currentAttributeOffset; + } else if (!Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // The BootstrapMethods attribute is read in the constructor. + Attribute attribute = + readAttribute( + attributePrototypes, + attributeName, + currentAttributeOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentAttributeOffset += attributeLength; + } + + // Visit the class declaration. The minor_version and major_version fields start 6 bytes before + // the first constant pool entry, which itself starts at cpInfoOffsets[1] - 1 (by definition). + classVisitor.visit( + readInt(cpInfoOffsets[1] - 7), accessFlags, thisClass, signature, superClass, interfaces); + + // Visit the SourceFile and SourceDebugExtenstion attributes. + if ((parsingOptions & SKIP_DEBUG) == 0 + && (sourceFile != null || sourceDebugExtension != null)) { + classVisitor.visitSource(sourceFile, sourceDebugExtension); + } + + // Visit the Module, ModulePackages and ModuleMainClass attributes. + if (moduleOffset != 0) { + readModuleAttributes( + classVisitor, context, moduleOffset, modulePackagesOffset, moduleMainClass); + } + + // Visit the NestHost attribute. + if (nestHostClass != null) { + classVisitor.visitNestHost(nestHostClass); + } + + // Visit the EnclosingMethod attribute. + if (enclosingMethodOffset != 0) { + String className = readClass(enclosingMethodOffset, charBuffer); + int methodIndex = readUnsignedShort(enclosingMethodOffset + 2); + String name = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex], charBuffer); + String type = methodIndex == 0 ? null : readUTF8(cpInfoOffsets[methodIndex] + 2, charBuffer); + classVisitor.visitOuterClass(className, name, type); + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + classVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in ClassWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + classVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the NestedMembers attribute. + if (nestMembersOffset != 0) { + int numberOfNestMembers = readUnsignedShort(nestMembersOffset); + int currentNestMemberOffset = nestMembersOffset + 2; + while (numberOfNestMembers-- > 0) { + classVisitor.visitNestMember(readClass(currentNestMemberOffset, charBuffer)); + currentNestMemberOffset += 2; + } + } + + // Visit the PermittedSubclasses attribute. + if (permittedSubclassesOffset != 0) { + int numberOfPermittedSubclasses = readUnsignedShort(permittedSubclassesOffset); + int currentPermittedSubclassesOffset = permittedSubclassesOffset + 2; + while (numberOfPermittedSubclasses-- > 0) { + classVisitor.visitPermittedSubclass( + readClass(currentPermittedSubclassesOffset, charBuffer)); + currentPermittedSubclassesOffset += 2; + } + } + + // Visit the InnerClasses attribute. + if (innerClassesOffset != 0) { + int numberOfClasses = readUnsignedShort(innerClassesOffset); + int currentClassesOffset = innerClassesOffset + 2; + while (numberOfClasses-- > 0) { + classVisitor.visitInnerClass( + readClass(currentClassesOffset, charBuffer), + readClass(currentClassesOffset + 2, charBuffer), + readUTF8(currentClassesOffset + 4, charBuffer), + readUnsignedShort(currentClassesOffset + 6)); + currentClassesOffset += 8; + } + } + + // Visit Record components. + if (recordOffset != 0) { + int recordComponentsCount = readUnsignedShort(recordOffset); + recordOffset += 2; + while (recordComponentsCount-- > 0) { + recordOffset = readRecordComponent(classVisitor, context, recordOffset); + } + } + + // Visit the fields and methods. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (fieldsCount-- > 0) { + currentOffset = readField(classVisitor, context, currentOffset); + } + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + currentOffset = readMethod(classVisitor, context, currentOffset); + } + + // Visit the end of the class. + classVisitor.visitEnd(); + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse modules, fields and methods + // ---------------------------------------------------------------------------------------------- + + /** + * Reads the Module, ModulePackages and ModuleMainClass attributes and visit them. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param moduleOffset the offset of the Module attribute (excluding the attribute_info's + * attribute_name_index and attribute_length fields). + * @param modulePackagesOffset the offset of the ModulePackages attribute (excluding the + * attribute_info's attribute_name_index and attribute_length fields), or 0. + * @param moduleMainClass the string corresponding to the ModuleMainClass attribute, or {@literal + * null}. + */ + private void readModuleAttributes( + final ClassVisitor classVisitor, + final Context context, + final int moduleOffset, + final int modulePackagesOffset, + final String moduleMainClass) { + char[] buffer = context.charBuffer; + + // Read the module_name_index, module_flags and module_version_index fields and visit them. + int currentOffset = moduleOffset; + String moduleName = readModule(currentOffset, buffer); + int moduleFlags = readUnsignedShort(currentOffset + 2); + String moduleVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + ModuleVisitor moduleVisitor = classVisitor.visitModule(moduleName, moduleFlags, moduleVersion); + if (moduleVisitor == null) { + return; + } + + // Visit the ModuleMainClass attribute. + if (moduleMainClass != null) { + moduleVisitor.visitMainClass(moduleMainClass); + } + + // Visit the ModulePackages attribute. + if (modulePackagesOffset != 0) { + int packageCount = readUnsignedShort(modulePackagesOffset); + int currentPackageOffset = modulePackagesOffset + 2; + while (packageCount-- > 0) { + moduleVisitor.visitPackage(readPackage(currentPackageOffset, buffer)); + currentPackageOffset += 2; + } + } + + // Read the 'requires_count' and 'requires' fields. + int requiresCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (requiresCount-- > 0) { + // Read the requires_index, requires_flags and requires_version fields and visit them. + String requires = readModule(currentOffset, buffer); + int requiresFlags = readUnsignedShort(currentOffset + 2); + String requiresVersion = readUTF8(currentOffset + 4, buffer); + currentOffset += 6; + moduleVisitor.visitRequire(requires, requiresFlags, requiresVersion); + } + + // Read the 'exports_count' and 'exports' fields. + int exportsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exportsCount-- > 0) { + // Read the exports_index, exports_flags, exports_to_count and exports_to_index fields + // and visit them. + String exports = readPackage(currentOffset, buffer); + int exportsFlags = readUnsignedShort(currentOffset + 2); + int exportsToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] exportsTo = null; + if (exportsToCount != 0) { + exportsTo = new String[exportsToCount]; + for (int i = 0; i < exportsToCount; ++i) { + exportsTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; + } + } + moduleVisitor.visitExport(exports, exportsFlags, exportsTo); + } + + // Reads the 'opens_count' and 'opens' fields. + int opensCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (opensCount-- > 0) { + // Read the opens_index, opens_flags, opens_to_count and opens_to_index fields and visit them. + String opens = readPackage(currentOffset, buffer); + int opensFlags = readUnsignedShort(currentOffset + 2); + int opensToCount = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + String[] opensTo = null; + if (opensToCount != 0) { + opensTo = new String[opensToCount]; + for (int i = 0; i < opensToCount; ++i) { + opensTo[i] = readModule(currentOffset, buffer); + currentOffset += 2; + } + } + moduleVisitor.visitOpen(opens, opensFlags, opensTo); + } + + // Read the 'uses_count' and 'uses' fields. + int usesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (usesCount-- > 0) { + moduleVisitor.visitUse(readClass(currentOffset, buffer)); + currentOffset += 2; + } + + // Read the 'provides_count' and 'provides' fields. + int providesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (providesCount-- > 0) { + // Read the provides_index, provides_with_count and provides_with_index fields and visit them. + String provides = readClass(currentOffset, buffer); + int providesWithCount = readUnsignedShort(currentOffset + 2); + currentOffset += 4; + String[] providesWith = new String[providesWithCount]; + for (int i = 0; i < providesWithCount; ++i) { + providesWith[i] = readClass(currentOffset, buffer); + currentOffset += 2; + } + moduleVisitor.visitProvide(provides, providesWith); + } + + // Visit the end of the module attributes. + moduleVisitor.visitEnd(); + } + + /** + * Reads a record component and visit it. + * + * @param classVisitor the current class visitor + * @param context information about the class being parsed. + * @param recordComponentOffset the offset of the current record component. + * @return the offset of the first byte following the record component. + */ + private int readRecordComponent( + final ClassVisitor classVisitor, final Context context, final int recordComponentOffset) { + char[] charBuffer = context.charBuffer; + + int currentOffset = recordComponentOffset; + String name = readUTF8(currentOffset, charBuffer); + String descriptor = readUTF8(currentOffset + 2, charBuffer); + currentOffset += 4; + + // Read the record component attributes (the variables are ordered as in Section 4.7 of the + // JVMS). + + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + RecordComponentVisitor recordComponentVisitor = + classVisitor.visitRecordComponent(name, descriptor, signature); + if (recordComponentVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + recordComponentVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + recordComponentVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + recordComponentVisitor.visitEnd(); + return currentOffset; + } + + /** + * Reads a JVMS field_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the field. + * @param context information about the class being parsed. + * @param fieldInfoOffset the start offset of the field_info structure. + * @return the offset of the first byte following the field_info structure. + */ + private int readField( + final ClassVisitor classVisitor, final Context context, final int fieldInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = fieldInfoOffset; + int accessFlags = readUnsignedShort(currentOffset); + String name = readUTF8(currentOffset + 2, charBuffer); + String descriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the field attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The value corresponding to the ConstantValue attribute, or null. + Object constantValue = null; + // - The string corresponding to the Signature attribute, or null. + String signature = null; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CONSTANT_VALUE.equals(attributeName)) { + int constantvalueIndex = readUnsignedShort(currentOffset); + constantValue = constantvalueIndex == 0 ? null : readConst(constantvalueIndex, charBuffer); + } else if (Constants.SIGNATURE.equals(attributeName)) { + signature = readUTF8(currentOffset, charBuffer); + } else if (Constants.DEPRECATED.equals(attributeName)) { + accessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + accessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the field declaration. + FieldVisitor fieldVisitor = + classVisitor.visitField(accessFlags, name, descriptor, signature, constantValue); + if (fieldVisitor == null) { + return currentOffset; + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + fieldVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in FieldWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + fieldVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the end of the field. + fieldVisitor.visitEnd(); + return currentOffset; + } + + /** + * Reads a JVMS method_info structure and makes the given visitor visit it. + * + * @param classVisitor the visitor that must visit the method. + * @param context information about the class being parsed. + * @param methodInfoOffset the start offset of the method_info structure. + * @return the offset of the first byte following the method_info structure. + */ + private int readMethod( + final ClassVisitor classVisitor, final Context context, final int methodInfoOffset) { + char[] charBuffer = context.charBuffer; + + // Read the access_flags, name_index and descriptor_index fields. + int currentOffset = methodInfoOffset; + context.currentMethodAccessFlags = readUnsignedShort(currentOffset); + context.currentMethodName = readUTF8(currentOffset + 2, charBuffer); + context.currentMethodDescriptor = readUTF8(currentOffset + 4, charBuffer); + currentOffset += 6; + + // Read the method attributes (the variables are ordered as in Section 4.7 of the JVMS). + // Attribute offsets exclude the attribute_name_index and attribute_length fields. + // - The offset of the Code attribute, or 0. + int codeOffset = 0; + // - The offset of the Exceptions attribute, or 0. + int exceptionsOffset = 0; + // - The strings corresponding to the Exceptions attribute, or null. + String[] exceptions = null; + // - Whether the method has a Synthetic attribute. + boolean synthetic = false; + // - The constant pool index contained in the Signature attribute, or 0. + int signatureIndex = 0; + // - The offset of the RuntimeVisibleAnnotations attribute, or 0. + int runtimeVisibleAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleAnnotations attribute, or 0. + int runtimeInvisibleAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleParameterAnnotations attribute, or 0. + int runtimeVisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleParameterAnnotations attribute, or 0. + int runtimeInvisibleParameterAnnotationsOffset = 0; + // - The offset of the RuntimeVisibleTypeAnnotations attribute, or 0. + int runtimeVisibleTypeAnnotationsOffset = 0; + // - The offset of the RuntimeInvisibleTypeAnnotations attribute, or 0. + int runtimeInvisibleTypeAnnotationsOffset = 0; + // - The offset of the AnnotationDefault attribute, or 0. + int annotationDefaultOffset = 0; + // - The offset of the MethodParameters attribute, or 0. + int methodParametersOffset = 0; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + // The tests are sorted in decreasing frequency order (based on frequencies observed on + // typical classes). + if (Constants.CODE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_CODE) == 0) { + codeOffset = currentOffset; + } + } else if (Constants.EXCEPTIONS.equals(attributeName)) { + exceptionsOffset = currentOffset; + exceptions = new String[readUnsignedShort(exceptionsOffset)]; + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < exceptions.length; ++i) { + exceptions[i] = readClass(currentExceptionOffset, charBuffer); + currentExceptionOffset += 2; + } + } else if (Constants.SIGNATURE.equals(attributeName)) { + signatureIndex = readUnsignedShort(currentOffset); + } else if (Constants.DEPRECATED.equals(attributeName)) { + context.currentMethodAccessFlags |= Opcodes.ACC_DEPRECATED; + } else if (Constants.RUNTIME_VISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.ANNOTATION_DEFAULT.equals(attributeName)) { + annotationDefaultOffset = currentOffset; + } else if (Constants.SYNTHETIC.equals(attributeName)) { + synthetic = true; + context.currentMethodAccessFlags |= Opcodes.ACC_SYNTHETIC; + } else if (Constants.RUNTIME_INVISIBLE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleTypeAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeVisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS.equals(attributeName)) { + runtimeInvisibleParameterAnnotationsOffset = currentOffset; + } else if (Constants.METHOD_PARAMETERS.equals(attributeName)) { + methodParametersOffset = currentOffset; + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + -1, + null); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Visit the method declaration. + MethodVisitor methodVisitor = + classVisitor.visitMethod( + context.currentMethodAccessFlags, + context.currentMethodName, + context.currentMethodDescriptor, + signatureIndex == 0 ? null : readUtf(signatureIndex, charBuffer), + exceptions); + if (methodVisitor == null) { + return currentOffset; + } + + // If the returned MethodVisitor is in fact a MethodWriter, it means there is no method + // adapter between the reader and the writer. In this case, it might be possible to copy + // the method attributes directly into the writer. If so, return early without visiting + // the content of these attributes. + if (methodVisitor instanceof MethodWriter) { + MethodWriter methodWriter = (MethodWriter) methodVisitor; + if (methodWriter.canCopyMethodAttributes( + this, + synthetic, + (context.currentMethodAccessFlags & Opcodes.ACC_DEPRECATED) != 0, + readUnsignedShort(methodInfoOffset + 4), + signatureIndex, + exceptionsOffset)) { + methodWriter.setMethodAttributesSource(methodInfoOffset, currentOffset - methodInfoOffset); + return currentOffset; + } + } + + // Visit the MethodParameters attribute. + if (methodParametersOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { + int parametersCount = readByte(methodParametersOffset); + int currentParameterOffset = methodParametersOffset + 1; + while (parametersCount-- > 0) { + // Read the name_index and access_flags fields and visit them. + methodVisitor.visitParameter( + readUTF8(currentParameterOffset, charBuffer), + readUnsignedShort(currentParameterOffset + 2)); + currentParameterOffset += 4; + } + } + + // Visit the AnnotationDefault attribute. + if (annotationDefaultOffset != 0) { + AnnotationVisitor annotationVisitor = methodVisitor.visitAnnotationDefault(); + readElementValue(annotationVisitor, annotationDefaultOffset, null, charBuffer); + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + } + + // Visit the RuntimeVisibleAnnotations attribute. + if (runtimeVisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleAnnotations attribute. + if (runtimeInvisibleAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitAnnotation(annotationDescriptor, /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleTypeAnnotations attribute. + if (runtimeVisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeVisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeVisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeInvisibleTypeAnnotations attribute. + if (runtimeInvisibleTypeAnnotationsOffset != 0) { + int numAnnotations = readUnsignedShort(runtimeInvisibleTypeAnnotationsOffset); + int currentAnnotationOffset = runtimeInvisibleTypeAnnotationsOffset + 2; + while (numAnnotations-- > 0) { + // Parse the target_type, target_info and target_path fields. + currentAnnotationOffset = readTypeAnnotationTarget(context, currentAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentAnnotationOffset = + readElementValues( + methodVisitor.visitTypeAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + } + + // Visit the RuntimeVisibleParameterAnnotations attribute. + if (runtimeVisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, context, runtimeVisibleParameterAnnotationsOffset, /* visible = */ true); + } + + // Visit the RuntimeInvisibleParameterAnnotations attribute. + if (runtimeInvisibleParameterAnnotationsOffset != 0) { + readParameterAnnotations( + methodVisitor, + context, + runtimeInvisibleParameterAnnotationsOffset, + /* visible = */ false); + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the Code attribute. + if (codeOffset != 0) { + methodVisitor.visitCode(); + readCode(methodVisitor, context, codeOffset); + } + + // Visit the end of the method. + methodVisitor.visitEnd(); + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse a Code attribute + // ---------------------------------------------------------------------------------------------- + + /** + * Reads a JVMS 'Code' attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the Code attribute. + * @param context information about the class being parsed. + * @param codeOffset the start offset in {@link #classFileBuffer} of the Code attribute, excluding + * its attribute_name_index and attribute_length fields. + */ + private void readCode( + final MethodVisitor methodVisitor, final Context context, final int codeOffset) { + int currentOffset = codeOffset; + + // Read the max_stack, max_locals and code_length fields. + final byte[] classBuffer = classFileBuffer; + final char[] charBuffer = context.charBuffer; + final int maxStack = readUnsignedShort(currentOffset); + final int maxLocals = readUnsignedShort(currentOffset + 2); + final int codeLength = readInt(currentOffset + 4); + currentOffset += 8; + if (codeLength > classFileBuffer.length - currentOffset) { + throw new IllegalArgumentException(); + } + + // Read the bytecode 'code' array to create a label for each referenced instruction. + final int bytecodeStartOffset = currentOffset; + final int bytecodeEndOffset = currentOffset + codeLength; + final Label[] labels = context.currentMethodLabels = new Label[codeLength + 1]; + while (currentOffset < bytecodeEndOffset) { + final int bytecodeOffset = currentOffset - bytecodeStartOffset; + final int opcode = classBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + currentOffset += 1; + break; + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + createLabel(bytecodeOffset + readShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + createLabel(bytecodeOffset + readUnsignedShort(currentOffset + 1), labels); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + case Constants.ASM_GOTO_W: + createLabel(bytecodeOffset + readInt(currentOffset + 1), labels); + currentOffset += 5; + break; + case Constants.WIDE: + switch (classBuffer[currentOffset + 1] & 0xFF) { + case Opcodes.ILOAD: + case Opcodes.FLOAD: + case Opcodes.ALOAD: + case Opcodes.LLOAD: + case Opcodes.DLOAD: + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + case Opcodes.LSTORE: + case Opcodes.DSTORE: + case Opcodes.RET: + currentOffset += 4; + break; + case Opcodes.IINC: + currentOffset += 6; + break; + default: + throw new IllegalArgumentException(); + } + break; + case Opcodes.TABLESWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of table entries. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numTableEntries = readInt(currentOffset + 8) - readInt(currentOffset + 4) + 1; + currentOffset += 12; + // Read the table labels. + while (numTableEntries-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset), labels); + currentOffset += 4; + } + break; + case Opcodes.LOOKUPSWITCH: + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (bytecodeOffset & 3); + // Read the default label and the number of switch cases. + createLabel(bytecodeOffset + readInt(currentOffset), labels); + int numSwitchCases = readInt(currentOffset + 4); + currentOffset += 8; + // Read the switch labels. + while (numSwitchCases-- > 0) { + createLabel(bytecodeOffset + readInt(currentOffset + 4), labels); + currentOffset += 8; + } + break; + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + case Opcodes.LDC: + currentOffset += 2; + break; + case Opcodes.SIPUSH: + case Constants.LDC_W: + case Constants.LDC2_W: + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + case Opcodes.IINC: + currentOffset += 3; + break; + case Opcodes.INVOKEINTERFACE: + case Opcodes.INVOKEDYNAMIC: + currentOffset += 5; + break; + case Opcodes.MULTIANEWARRAY: + currentOffset += 4; + break; + default: + throw new IllegalArgumentException(); + } + } + + // Read the 'exception_table_length' and 'exception_table' field to create a label for each + // referenced instruction, and to make methodVisitor visit the corresponding try catch blocks. + int exceptionTableLength = readUnsignedShort(currentOffset); + currentOffset += 2; + while (exceptionTableLength-- > 0) { + Label start = createLabel(readUnsignedShort(currentOffset), labels); + Label end = createLabel(readUnsignedShort(currentOffset + 2), labels); + Label handler = createLabel(readUnsignedShort(currentOffset + 4), labels); + String catchType = readUTF8(cpInfoOffsets[readUnsignedShort(currentOffset + 6)], charBuffer); + currentOffset += 8; + methodVisitor.visitTryCatchBlock(start, end, handler, catchType); + } + + // Read the Code attributes to create a label for each referenced instruction (the variables + // are ordered as in Section 4.7 of the JVMS). Attribute offsets exclude the + // attribute_name_index and attribute_length fields. + // - The offset of the current 'stack_map_frame' in the StackMap[Table] attribute, or 0. + // Initially, this is the offset of the first 'stack_map_frame' entry. Then this offset is + // updated after each stack_map_frame is read. + int stackMapFrameOffset = 0; + // - The end offset of the StackMap[Table] attribute, or 0. + int stackMapTableEndOffset = 0; + // - Whether the stack map frames are compressed (i.e. in a StackMapTable) or not. + boolean compressedFrames = true; + // - The offset of the LocalVariableTable attribute, or 0. + int localVariableTableOffset = 0; + // - The offset of the LocalVariableTypeTable attribute, or 0. + int localVariableTypeTableOffset = 0; + // - The offset of each 'type_annotation' entry in the RuntimeVisibleTypeAnnotations + // attribute, or null. + int[] visibleTypeAnnotationOffsets = null; + // - The offset of each 'type_annotation' entry in the RuntimeInvisibleTypeAnnotations + // attribute, or null. + int[] invisibleTypeAnnotationOffsets = null; + // - The non standard attributes (linked with their {@link Attribute#nextAttribute} field). + // This list in the reverse order or their order in the ClassFile structure. + Attribute attributes = null; + + int attributesCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (attributesCount-- > 0) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentOffset, charBuffer); + int attributeLength = readInt(currentOffset + 2); + currentOffset += 6; + if (Constants.LOCAL_VARIABLE_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + localVariableTableOffset = currentOffset; + // Parse the attribute to find the corresponding (debug only) labels. + int currentLocalVariableTableOffset = currentOffset; + int localVariableTableLength = readUnsignedShort(currentLocalVariableTableOffset); + currentLocalVariableTableOffset += 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentLocalVariableTableOffset); + createDebugLabel(startPc, labels); + int length = readUnsignedShort(currentLocalVariableTableOffset + 2); + createDebugLabel(startPc + length, labels); + // Skip the name_index, descriptor_index and index fields (2 bytes each). + currentLocalVariableTableOffset += 10; + } + } + } else if (Constants.LOCAL_VARIABLE_TYPE_TABLE.equals(attributeName)) { + localVariableTypeTableOffset = currentOffset; + // Here we do not extract the labels corresponding to the attribute content. We assume they + // are the same or a subset of those of the LocalVariableTable attribute. + } else if (Constants.LINE_NUMBER_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_DEBUG) == 0) { + // Parse the attribute to find the corresponding (debug only) labels. + int currentLineNumberTableOffset = currentOffset; + int lineNumberTableLength = readUnsignedShort(currentLineNumberTableOffset); + currentLineNumberTableOffset += 2; + while (lineNumberTableLength-- > 0) { + int startPc = readUnsignedShort(currentLineNumberTableOffset); + int lineNumber = readUnsignedShort(currentLineNumberTableOffset + 2); + currentLineNumberTableOffset += 4; + createDebugLabel(startPc, labels); + labels[startPc].addLineNumber(lineNumber); + } + } + } else if (Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + visibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ true); + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // type annotation at a time (i.e. after a type annotation has been visited, the next type + // annotation is read), and the labels it contains are also extracted one annotation at a + // time. This assumes that type annotations are ordered by increasing bytecode offset. + } else if (Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS.equals(attributeName)) { + invisibleTypeAnnotationOffsets = + readTypeAnnotations(methodVisitor, context, currentOffset, /* visible = */ false); + // Same comment as above for the RuntimeVisibleTypeAnnotations attribute. + } else if (Constants.STACK_MAP_TABLE.equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + } + // Here we do not extract the labels corresponding to the attribute content. This would + // require a full parsing of the attribute, which would need to be repeated when parsing + // the bytecode instructions (see below). Instead, the content of the attribute is read one + // frame at a time (i.e. after a frame has been visited, the next frame is read), and the + // labels it contains are also extracted one frame at a time. Thanks to the ordering of + // frames, having only a "one frame lookahead" is not a problem, i.e. it is not possible to + // see an offset smaller than the offset of the current instruction and for which no Label + // exist. Except for UNINITIALIZED type offsets. We solve this by parsing the stack map + // table without a full decoding (see below). + } else if ("StackMap".equals(attributeName)) { + if ((context.parsingOptions & SKIP_FRAMES) == 0) { + stackMapFrameOffset = currentOffset + 2; + stackMapTableEndOffset = currentOffset + attributeLength; + compressedFrames = false; + } + // IMPORTANT! Here we assume that the frames are ordered, as in the StackMapTable attribute, + // although this is not guaranteed by the attribute format. This allows an incremental + // extraction of the labels corresponding to this attribute (see the comment above for the + // StackMapTable attribute). + } else { + Attribute attribute = + readAttribute( + context.attributePrototypes, + attributeName, + currentOffset, + attributeLength, + charBuffer, + codeOffset, + labels); + attribute.nextAttribute = attributes; + attributes = attribute; + } + currentOffset += attributeLength; + } + + // Initialize the context fields related to stack map frames, and generate the first + // (implicit) stack map frame, if needed. + final boolean expandFrames = (context.parsingOptions & EXPAND_FRAMES) != 0; + if (stackMapFrameOffset != 0) { + // The bytecode offset of the first explicit frame is not offset_delta + 1 but only + // offset_delta. Setting the implicit frame offset to -1 allows us to use of the + // "offset_delta + 1" rule in all cases. + context.currentFrameOffset = -1; + context.currentFrameType = 0; + context.currentFrameLocalCount = 0; + context.currentFrameLocalCountDelta = 0; + context.currentFrameLocalTypes = new Object[maxLocals]; + context.currentFrameStackCount = 0; + context.currentFrameStackTypes = new Object[maxStack]; + if (expandFrames) { + computeImplicitFrame(context); + } + // Find the labels for UNINITIALIZED frame types. Instead of decoding each element of the + // stack map table, we look for 3 consecutive bytes that "look like" an UNINITIALIZED type + // (tag ITEM_Uninitialized, offset within bytecode bounds, NEW instruction at this offset). + // We may find false positives (i.e. not real UNINITIALIZED types), but this should be rare, + // and the only consequence will be the creation of an unneeded label. This is better than + // creating a label for each NEW instruction, and faster than fully decoding the whole stack + // map table. + for (int offset = stackMapFrameOffset; offset < stackMapTableEndOffset - 2; ++offset) { + if (classBuffer[offset] == Frame.ITEM_UNINITIALIZED) { + int potentialBytecodeOffset = readUnsignedShort(offset + 1); + if (potentialBytecodeOffset >= 0 + && potentialBytecodeOffset < codeLength + && (classBuffer[bytecodeStartOffset + potentialBytecodeOffset] & 0xFF) + == Opcodes.NEW) { + createLabel(potentialBytecodeOffset, labels); + } + } + } + } + if (expandFrames && (context.parsingOptions & EXPAND_ASM_INSNS) != 0) { + // Expanding the ASM specific instructions can introduce F_INSERT frames, even if the method + // does not currently have any frame. These inserted frames must be computed by simulating the + // effect of the bytecode instructions, one by one, starting from the implicit first frame. + // For this, MethodWriter needs to know maxLocals before the first instruction is visited. To + // ensure this, we visit the implicit first frame here (passing only maxLocals - the rest is + // computed in MethodWriter). + methodVisitor.visitFrame(Opcodes.F_NEW, maxLocals, null, 0, null); + } + + // Visit the bytecode instructions. First, introduce state variables for the incremental parsing + // of the type annotations. + + // Index of the next runtime visible type annotation to read (in the + // visibleTypeAnnotationOffsets array). + int currentVisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime visible type annotation to read, or -1. + int currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(visibleTypeAnnotationOffsets, 0); + // Index of the next runtime invisible type annotation to read (in the + // invisibleTypeAnnotationOffsets array). + int currentInvisibleTypeAnnotationIndex = 0; + // The bytecode offset of the next runtime invisible type annotation to read, or -1. + int currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset(invisibleTypeAnnotationOffsets, 0); + + // Whether a F_INSERT stack map frame must be inserted before the current instruction. + boolean insertFrame = false; + + // The delta to subtract from a goto_w or jsr_w opcode to get the corresponding goto or jsr + // opcode, or 0 if goto_w and jsr_w must be left unchanged (i.e. when expanding ASM specific + // instructions). + final int wideJumpOpcodeDelta = + (context.parsingOptions & EXPAND_ASM_INSNS) == 0 ? Constants.WIDE_JUMP_OPCODE_DELTA : 0; + + currentOffset = bytecodeStartOffset; + while (currentOffset < bytecodeEndOffset) { + final int currentBytecodeOffset = currentOffset - bytecodeStartOffset; + readBytecodeInstructionOffset(currentBytecodeOffset); + + // Visit the label and the line number(s) for this bytecode offset, if any. + Label currentLabel = labels[currentBytecodeOffset]; + if (currentLabel != null) { + currentLabel.accept(methodVisitor, (context.parsingOptions & SKIP_DEBUG) == 0); + } + + // Visit the stack map frame for this bytecode offset, if any. + while (stackMapFrameOffset != 0 + && (context.currentFrameOffset == currentBytecodeOffset + || context.currentFrameOffset == -1)) { + // If there is a stack map frame for this offset, make methodVisitor visit it, and read the + // next stack map frame if there is one. + if (context.currentFrameOffset != -1) { + if (!compressedFrames || expandFrames) { + methodVisitor.visitFrame( + Opcodes.F_NEW, + context.currentFrameLocalCount, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } else { + methodVisitor.visitFrame( + context.currentFrameType, + context.currentFrameLocalCountDelta, + context.currentFrameLocalTypes, + context.currentFrameStackCount, + context.currentFrameStackTypes); + } + // Since there is already a stack map frame for this bytecode offset, there is no need to + // insert a new one. + insertFrame = false; + } + if (stackMapFrameOffset < stackMapTableEndOffset) { + stackMapFrameOffset = + readStackMapFrame(stackMapFrameOffset, compressedFrames, expandFrames, context); + } else { + stackMapFrameOffset = 0; + } + } + + // Insert a stack map frame for this bytecode offset, if requested by setting insertFrame to + // true during the previous iteration. The actual frame content is computed in MethodWriter. + if (insertFrame) { + if ((context.parsingOptions & EXPAND_FRAMES) != 0) { + methodVisitor.visitFrame(Constants.F_INSERT, 0, null, 0, null); + } + insertFrame = false; + } + + // Visit the instruction at this bytecode offset. + int opcode = classBuffer[currentOffset] & 0xFF; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.ACONST_NULL: + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.IALOAD: + case Opcodes.LALOAD: + case Opcodes.FALOAD: + case Opcodes.DALOAD: + case Opcodes.AALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.POP: + case Opcodes.POP2: + case Opcodes.DUP: + case Opcodes.DUP_X1: + case Opcodes.DUP_X2: + case Opcodes.DUP2: + case Opcodes.DUP2_X1: + case Opcodes.DUP2_X2: + case Opcodes.SWAP: + case Opcodes.IADD: + case Opcodes.LADD: + case Opcodes.FADD: + case Opcodes.DADD: + case Opcodes.ISUB: + case Opcodes.LSUB: + case Opcodes.FSUB: + case Opcodes.DSUB: + case Opcodes.IMUL: + case Opcodes.LMUL: + case Opcodes.FMUL: + case Opcodes.DMUL: + case Opcodes.IDIV: + case Opcodes.LDIV: + case Opcodes.FDIV: + case Opcodes.DDIV: + case Opcodes.IREM: + case Opcodes.LREM: + case Opcodes.FREM: + case Opcodes.DREM: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.ISHL: + case Opcodes.LSHL: + case Opcodes.ISHR: + case Opcodes.LSHR: + case Opcodes.IUSHR: + case Opcodes.LUSHR: + case Opcodes.IAND: + case Opcodes.LAND: + case Opcodes.IOR: + case Opcodes.LOR: + case Opcodes.IXOR: + case Opcodes.LXOR: + case Opcodes.I2L: + case Opcodes.I2F: + case Opcodes.I2D: + case Opcodes.L2I: + case Opcodes.L2F: + case Opcodes.L2D: + case Opcodes.F2I: + case Opcodes.F2L: + case Opcodes.F2D: + case Opcodes.D2I: + case Opcodes.D2L: + case Opcodes.D2F: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.LCMP: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + case Opcodes.IRETURN: + case Opcodes.LRETURN: + case Opcodes.FRETURN: + case Opcodes.DRETURN: + case Opcodes.ARETURN: + case Opcodes.RETURN: + case Opcodes.ARRAYLENGTH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + methodVisitor.visitInsn(opcode); + currentOffset += 1; + break; + case Constants.ILOAD_0: + case Constants.ILOAD_1: + case Constants.ILOAD_2: + case Constants.ILOAD_3: + case Constants.LLOAD_0: + case Constants.LLOAD_1: + case Constants.LLOAD_2: + case Constants.LLOAD_3: + case Constants.FLOAD_0: + case Constants.FLOAD_1: + case Constants.FLOAD_2: + case Constants.FLOAD_3: + case Constants.DLOAD_0: + case Constants.DLOAD_1: + case Constants.DLOAD_2: + case Constants.DLOAD_3: + case Constants.ALOAD_0: + case Constants.ALOAD_1: + case Constants.ALOAD_2: + case Constants.ALOAD_3: + opcode -= Constants.ILOAD_0; + methodVisitor.visitVarInsn(Opcodes.ILOAD + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Constants.ISTORE_0: + case Constants.ISTORE_1: + case Constants.ISTORE_2: + case Constants.ISTORE_3: + case Constants.LSTORE_0: + case Constants.LSTORE_1: + case Constants.LSTORE_2: + case Constants.LSTORE_3: + case Constants.FSTORE_0: + case Constants.FSTORE_1: + case Constants.FSTORE_2: + case Constants.FSTORE_3: + case Constants.DSTORE_0: + case Constants.DSTORE_1: + case Constants.DSTORE_2: + case Constants.DSTORE_3: + case Constants.ASTORE_0: + case Constants.ASTORE_1: + case Constants.ASTORE_2: + case Constants.ASTORE_3: + opcode -= Constants.ISTORE_0; + methodVisitor.visitVarInsn(Opcodes.ISTORE + (opcode >> 2), opcode & 0x3); + currentOffset += 1; + break; + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.GOTO: + case Opcodes.JSR: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + methodVisitor.visitJumpInsn( + opcode, labels[currentBytecodeOffset + readShort(currentOffset + 1)]); + currentOffset += 3; + break; + case Constants.GOTO_W: + case Constants.JSR_W: + methodVisitor.visitJumpInsn( + opcode - wideJumpOpcodeDelta, + labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + currentOffset += 5; + break; + case Constants.ASM_IFEQ: + case Constants.ASM_IFNE: + case Constants.ASM_IFLT: + case Constants.ASM_IFGE: + case Constants.ASM_IFGT: + case Constants.ASM_IFLE: + case Constants.ASM_IF_ICMPEQ: + case Constants.ASM_IF_ICMPNE: + case Constants.ASM_IF_ICMPLT: + case Constants.ASM_IF_ICMPGE: + case Constants.ASM_IF_ICMPGT: + case Constants.ASM_IF_ICMPLE: + case Constants.ASM_IF_ACMPEQ: + case Constants.ASM_IF_ACMPNE: + case Constants.ASM_GOTO: + case Constants.ASM_JSR: + case Constants.ASM_IFNULL: + case Constants.ASM_IFNONNULL: + { + // A forward jump with an offset > 32767. In this case we automatically replace ASM_GOTO + // with GOTO_W, ASM_JSR with JSR_W and ASM_IFxxx with IFNOTxxx GOTO_W L:..., + // where IFNOTxxx is the "opposite" opcode of ASMS_IFxxx (e.g. IFNE for ASM_IFEQ) and + // where designates the instruction just after the GOTO_W. + // First, change the ASM specific opcodes ASM_IFEQ ... ASM_JSR, ASM_IFNULL and + // ASM_IFNONNULL to IFEQ ... JSR, IFNULL and IFNONNULL. + opcode = + opcode < Constants.ASM_IFNULL + ? opcode - Constants.ASM_OPCODE_DELTA + : opcode - Constants.ASM_IFNULL_OPCODE_DELTA; + Label target = labels[currentBytecodeOffset + readUnsignedShort(currentOffset + 1)]; + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + // Replace GOTO with GOTO_W and JSR with JSR_W. + methodVisitor.visitJumpInsn(opcode + Constants.WIDE_JUMP_OPCODE_DELTA, target); + } else { + // Compute the "opposite" of opcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ + // (with a pre and post offset by 1). + opcode = opcode < Opcodes.GOTO ? ((opcode + 1) ^ 1) - 1 : opcode ^ 1; + Label endif = createLabel(currentBytecodeOffset + 3, labels); + methodVisitor.visitJumpInsn(opcode, endif); + methodVisitor.visitJumpInsn(Constants.GOTO_W, target); + // endif designates the instruction just after GOTO_W, and is visited as part of the + // next instruction. Since it is a jump target, we need to insert a frame here. + insertFrame = true; + } + currentOffset += 3; + break; + } + case Constants.ASM_GOTO_W: + // Replace ASM_GOTO_W with GOTO_W. + methodVisitor.visitJumpInsn( + Constants.GOTO_W, labels[currentBytecodeOffset + readInt(currentOffset + 1)]); + // The instruction just after is a jump target (because ASM_GOTO_W is used in patterns + // IFNOTxxx ASM_GOTO_W L:..., see MethodWriter), so we need to insert a frame + // here. + insertFrame = true; + currentOffset += 5; + break; + case Constants.WIDE: + opcode = classBuffer[currentOffset + 1] & 0xFF; + if (opcode == Opcodes.IINC) { + methodVisitor.visitIincInsn( + readUnsignedShort(currentOffset + 2), readShort(currentOffset + 4)); + currentOffset += 6; + } else { + methodVisitor.visitVarInsn(opcode, readUnsignedShort(currentOffset + 2)); + currentOffset += 4; + } + break; + case Opcodes.TABLESWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int low = readInt(currentOffset + 4); + int high = readInt(currentOffset + 8); + currentOffset += 12; + Label[] table = new Label[high - low + 1]; + for (int i = 0; i < table.length; ++i) { + table[i] = labels[currentBytecodeOffset + readInt(currentOffset)]; + currentOffset += 4; + } + methodVisitor.visitTableSwitchInsn(low, high, defaultLabel, table); + break; + } + case Opcodes.LOOKUPSWITCH: + { + // Skip 0 to 3 padding bytes. + currentOffset += 4 - (currentBytecodeOffset & 3); + // Read the instruction. + Label defaultLabel = labels[currentBytecodeOffset + readInt(currentOffset)]; + int numPairs = readInt(currentOffset + 4); + currentOffset += 8; + int[] keys = new int[numPairs]; + Label[] values = new Label[numPairs]; + for (int i = 0; i < numPairs; ++i) { + keys[i] = readInt(currentOffset); + values[i] = labels[currentBytecodeOffset + readInt(currentOffset + 4)]; + currentOffset += 8; + } + methodVisitor.visitLookupSwitchInsn(defaultLabel, keys, values); + break; + } + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: + case Opcodes.ALOAD: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + case Opcodes.RET: + methodVisitor.visitVarInsn(opcode, classBuffer[currentOffset + 1] & 0xFF); + currentOffset += 2; + break; + case Opcodes.BIPUSH: + case Opcodes.NEWARRAY: + methodVisitor.visitIntInsn(opcode, classBuffer[currentOffset + 1]); + currentOffset += 2; + break; + case Opcodes.SIPUSH: + methodVisitor.visitIntInsn(opcode, readShort(currentOffset + 1)); + currentOffset += 3; + break; + case Opcodes.LDC: + methodVisitor.visitLdcInsn(readConst(classBuffer[currentOffset + 1] & 0xFF, charBuffer)); + currentOffset += 2; + break; + case Constants.LDC_W: + case Constants.LDC2_W: + methodVisitor.visitLdcInsn(readConst(readUnsignedShort(currentOffset + 1), charBuffer)); + currentOffset += 3; + break; + case Opcodes.GETSTATIC: + case Opcodes.PUTSTATIC: + case Opcodes.GETFIELD: + case Opcodes.PUTFIELD: + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String owner = readClass(cpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + if (opcode < Opcodes.INVOKEVIRTUAL) { + methodVisitor.visitFieldInsn(opcode, owner, name, descriptor); + } else { + boolean isInterface = + classBuffer[cpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + if (opcode == Opcodes.INVOKEINTERFACE) { + currentOffset += 5; + } else { + currentOffset += 3; + } + break; + } + case Opcodes.INVOKEDYNAMIC: + { + int cpInfoOffset = cpInfoOffsets[readUnsignedShort(currentOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = + (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = + new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = + readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + methodVisitor.visitInvokeDynamicInsn( + name, descriptor, handle, bootstrapMethodArguments); + currentOffset += 5; + break; + } + case Opcodes.NEW: + case Opcodes.ANEWARRAY: + case Opcodes.CHECKCAST: + case Opcodes.INSTANCEOF: + methodVisitor.visitTypeInsn(opcode, readClass(currentOffset + 1, charBuffer)); + currentOffset += 3; + break; + case Opcodes.IINC: + methodVisitor.visitIincInsn( + classBuffer[currentOffset + 1] & 0xFF, classBuffer[currentOffset + 2]); + currentOffset += 3; + break; + case Opcodes.MULTIANEWARRAY: + methodVisitor.visitMultiANewArrayInsn( + readClass(currentOffset + 1, charBuffer), classBuffer[currentOffset + 3] & 0xFF); + currentOffset += 4; + break; + default: + throw new AssertionError(); + } + + // Visit the runtime visible instruction annotations, if any. + while (visibleTypeAnnotationOffsets != null + && currentVisibleTypeAnnotationIndex < visibleTypeAnnotationOffsets.length + && currentVisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentVisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, visibleTypeAnnotationOffsets[currentVisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ true), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + currentVisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + visibleTypeAnnotationOffsets, ++currentVisibleTypeAnnotationIndex); + } + + // Visit the runtime invisible instruction annotations, if any. + while (invisibleTypeAnnotationOffsets != null + && currentInvisibleTypeAnnotationIndex < invisibleTypeAnnotationOffsets.length + && currentInvisibleTypeAnnotationBytecodeOffset <= currentBytecodeOffset) { + if (currentInvisibleTypeAnnotationBytecodeOffset == currentBytecodeOffset) { + // Parse the target_type, target_info and target_path fields. + int currentAnnotationOffset = + readTypeAnnotationTarget( + context, invisibleTypeAnnotationOffsets[currentInvisibleTypeAnnotationIndex]); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentAnnotationOffset, charBuffer); + currentAnnotationOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitInsnAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + annotationDescriptor, + /* visible = */ false), + currentAnnotationOffset, + /* named = */ true, + charBuffer); + } + currentInvisibleTypeAnnotationBytecodeOffset = + getTypeAnnotationBytecodeOffset( + invisibleTypeAnnotationOffsets, ++currentInvisibleTypeAnnotationIndex); + } + } + if (labels[codeLength] != null) { + methodVisitor.visitLabel(labels[codeLength]); + } + + // Visit LocalVariableTable and LocalVariableTypeTable attributes. + if (localVariableTableOffset != 0 && (context.parsingOptions & SKIP_DEBUG) == 0) { + // The (start_pc, index, signature_index) fields of each entry of the LocalVariableTypeTable. + int[] typeTable = null; + if (localVariableTypeTableOffset != 0) { + typeTable = new int[readUnsignedShort(localVariableTypeTableOffset) * 3]; + currentOffset = localVariableTypeTableOffset + 2; + int typeTableIndex = typeTable.length; + while (typeTableIndex > 0) { + // Store the offset of 'signature_index', and the value of 'index' and 'start_pc'. + typeTable[--typeTableIndex] = currentOffset + 6; + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset + 8); + typeTable[--typeTableIndex] = readUnsignedShort(currentOffset); + currentOffset += 10; + } + } + int localVariableTableLength = readUnsignedShort(localVariableTableOffset); + currentOffset = localVariableTableOffset + 2; + while (localVariableTableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + String name = readUTF8(currentOffset + 4, charBuffer); + String descriptor = readUTF8(currentOffset + 6, charBuffer); + int index = readUnsignedShort(currentOffset + 8); + currentOffset += 10; + String signature = null; + if (typeTable != null) { + for (int i = 0; i < typeTable.length; i += 3) { + if (typeTable[i] == startPc && typeTable[i + 1] == index) { + signature = readUTF8(typeTable[i + 2], charBuffer); + break; + } + } + } + methodVisitor.visitLocalVariable( + name, descriptor, signature, labels[startPc], labels[startPc + length], index); + } + } + + // Visit the local variable type annotations of the RuntimeVisibleTypeAnnotations attribute. + if (visibleTypeAnnotationOffsets != null) { + for (int typeAnnotationOffset : visibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ true), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + // Visit the local variable type annotations of the RuntimeInvisibleTypeAnnotations attribute. + if (invisibleTypeAnnotationOffsets != null) { + for (int typeAnnotationOffset : invisibleTypeAnnotationOffsets) { + int targetType = readByte(typeAnnotationOffset); + if (targetType == TypeReference.LOCAL_VARIABLE + || targetType == TypeReference.RESOURCE_VARIABLE) { + // Parse the target_type, target_info and target_path fields. + currentOffset = readTypeAnnotationTarget(context, typeAnnotationOffset); + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + readElementValues( + methodVisitor.visitLocalVariableAnnotation( + context.currentTypeAnnotationTarget, + context.currentTypeAnnotationTargetPath, + context.currentLocalVariableAnnotationRangeStarts, + context.currentLocalVariableAnnotationRangeEnds, + context.currentLocalVariableAnnotationRangeIndices, + annotationDescriptor, + /* visible = */ false), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + // Visit the non standard attributes. + while (attributes != null) { + // Copy and reset the nextAttribute field so that it can also be used in MethodWriter. + Attribute nextAttribute = attributes.nextAttribute; + attributes.nextAttribute = null; + methodVisitor.visitAttribute(attributes); + attributes = nextAttribute; + } + + // Visit the max stack and max locals values. + methodVisitor.visitMaxs(maxStack, maxLocals); + } + + /** + * Handles the bytecode offset of the next instruction to be visited in {@link + * #accept(ClassVisitor,int)}. This method is called just before the instruction and before its + * associated label and stack map frame, if any. The default implementation of this method does + * nothing. Subclasses can override this method to store the argument in a mutable field, for + * instance, so that {@link MethodVisitor} instances can get the bytecode offset of each visited + * instruction (if so, the usual concurrency issues related to mutable data should be addressed). + * + * @param bytecodeOffset the bytecode offset of the next instruction to be visited. + */ + protected void readBytecodeInstructionOffset(final int bytecodeOffset) { + // Do nothing by default. + } + + /** + * Returns the label corresponding to the given bytecode offset. The default implementation of + * this method creates a label for the given offset if it has not been already created. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. If a label already exists + * for bytecodeOffset this method must not create a new one. Otherwise it must store the new + * label in this array. + * @return a non null Label, which must be equal to labels[bytecodeOffset]. + */ + protected Label readLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + labels[bytecodeOffset] = new Label(); + } + return labels[bytecodeOffset]; + } + + /** + * Creates a label without the {@link Label#FLAG_DEBUG_ONLY} flag set, for the given bytecode + * offset. The label is created with a call to {@link #readLabel} and its {@link + * Label#FLAG_DEBUG_ONLY} flag is cleared. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + * @return a Label without the {@link Label#FLAG_DEBUG_ONLY} flag set. + */ + private Label createLabel(final int bytecodeOffset, final Label[] labels) { + Label label = readLabel(bytecodeOffset, labels); + label.flags &= ~Label.FLAG_DEBUG_ONLY; + return label; + } + + /** + * Creates a label with the {@link Label#FLAG_DEBUG_ONLY} flag set, if there is no already + * existing label for the given bytecode offset (otherwise does nothing). The label is created + * with a call to {@link #readLabel}. + * + * @param bytecodeOffset a bytecode offset in a method. + * @param labels the already created labels, indexed by their offset. + */ + private void createDebugLabel(final int bytecodeOffset, final Label[] labels) { + if (labels[bytecodeOffset] == null) { + readLabel(bytecodeOffset, labels).flags |= Label.FLAG_DEBUG_ONLY; + } + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse annotations, type annotations and parameter annotations + // ---------------------------------------------------------------------------------------------- + + /** + * Parses a Runtime[In]VisibleTypeAnnotations attribute to find the offset of each type_annotation + * entry it contains, to find the corresponding labels, and to visit the try catch block + * annotations. + * + * @param methodVisitor the method visitor to be used to visit the try catch block annotations. + * @param context information about the class being parsed. + * @param runtimeTypeAnnotationsOffset the start offset of a Runtime[In]VisibleTypeAnnotations + * attribute, excluding the attribute_info's attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleTypeAnnotations attribute, + * false it is a RuntimeInvisibleTypeAnnotations attribute. + * @return the start offset of each entry of the Runtime[In]VisibleTypeAnnotations_attribute's + * 'annotations' array field. + */ + private int[] readTypeAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeTypeAnnotationsOffset, + final boolean visible) { + char[] charBuffer = context.charBuffer; + int currentOffset = runtimeTypeAnnotationsOffset; + // Read the num_annotations field and create an array to store the type_annotation offsets. + int[] typeAnnotationsOffsets = new int[readUnsignedShort(currentOffset)]; + currentOffset += 2; + // Parse the 'annotations' array field. + for (int i = 0; i < typeAnnotationsOffsets.length; ++i) { + typeAnnotationsOffsets[i] = currentOffset; + // Parse the type_annotation's target_type and the target_info fields. The size of the + // target_info field depends on the value of target_type. + int targetType = readInt(currentOffset); + switch (targetType >>> 24) { + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + // A localvar_target has a variable size, which depends on the value of their table_length + // field. It also references bytecode offsets, for which we need labels. + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + while (tableLength-- > 0) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + // Skip the index field (2 bytes). + currentOffset += 6; + createLabel(startPc, context.currentMethodLabels); + createLabel(startPc + length, context.currentMethodLabels); + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + currentOffset += 3; + break; + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + default: + // TypeReference type which can't be used in Code attribute, or which is unknown. + throw new IllegalArgumentException(); + } + // Parse the rest of the type_annotation structure, starting with the target_path structure + // (whose size depends on its path_length field). + int pathLength = readByte(currentOffset); + if ((targetType >>> 24) == TypeReference.EXCEPTION_PARAMETER) { + // Parse the target_path structure and create a corresponding TypePath. + TypePath path = pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); + currentOffset += 1 + 2 * pathLength; + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitTryCatchAnnotation( + targetType & 0xFFFFFF00, path, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } else { + // We don't want to visit the other target_type annotations, so we just skip them (which + // requires some parsing because the element_value_pairs array has a variable size). First, + // skip the target_path structure: + currentOffset += 3 + 2 * pathLength; + // Then skip the num_element_value_pairs and element_value_pairs fields (by reading them + // with a null AnnotationVisitor). + currentOffset = + readElementValues( + /* annotationVisitor = */ null, currentOffset, /* named = */ true, charBuffer); + } + } + return typeAnnotationsOffsets; + } + + /** + * Returns the bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or + * -1 if there is no such type_annotation of if it does not have a bytecode offset. + * + * @param typeAnnotationOffsets the offset of each 'type_annotation' entry in a + * Runtime[In]VisibleTypeAnnotations attribute, or {@literal null}. + * @param typeAnnotationIndex the index a 'type_annotation' entry in typeAnnotationOffsets. + * @return bytecode offset corresponding to the specified JVMS 'type_annotation' structure, or -1 + * if there is no such type_annotation of if it does not have a bytecode offset. + */ + private int getTypeAnnotationBytecodeOffset( + final int[] typeAnnotationOffsets, final int typeAnnotationIndex) { + if (typeAnnotationOffsets == null + || typeAnnotationIndex >= typeAnnotationOffsets.length + || readByte(typeAnnotationOffsets[typeAnnotationIndex]) < TypeReference.INSTANCEOF) { + return -1; + } + return readUnsignedShort(typeAnnotationOffsets[typeAnnotationIndex] + 1); + } + + /** + * Parses the header of a JVMS type_annotation structure to extract its target_type, target_info + * and target_path (the result is stored in the given context), and returns the start offset of + * the rest of the type_annotation structure. + * + * @param context information about the class being parsed. This is where the extracted + * target_type and target_path must be stored. + * @param typeAnnotationOffset the start offset of a type_annotation structure. + * @return the start offset of the rest of the type_annotation structure. + */ + private int readTypeAnnotationTarget(final Context context, final int typeAnnotationOffset) { + int currentOffset = typeAnnotationOffset; + // Parse and store the target_type structure. + int targetType = readInt(typeAnnotationOffset); + switch (targetType >>> 24) { + case TypeReference.CLASS_TYPE_PARAMETER: + case TypeReference.METHOD_TYPE_PARAMETER: + case TypeReference.METHOD_FORMAL_PARAMETER: + targetType &= 0xFFFF0000; + currentOffset += 2; + break; + case TypeReference.FIELD: + case TypeReference.METHOD_RETURN: + case TypeReference.METHOD_RECEIVER: + targetType &= 0xFF000000; + currentOffset += 1; + break; + case TypeReference.LOCAL_VARIABLE: + case TypeReference.RESOURCE_VARIABLE: + targetType &= 0xFF000000; + int tableLength = readUnsignedShort(currentOffset + 1); + currentOffset += 3; + context.currentLocalVariableAnnotationRangeStarts = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeEnds = new Label[tableLength]; + context.currentLocalVariableAnnotationRangeIndices = new int[tableLength]; + for (int i = 0; i < tableLength; ++i) { + int startPc = readUnsignedShort(currentOffset); + int length = readUnsignedShort(currentOffset + 2); + int index = readUnsignedShort(currentOffset + 4); + currentOffset += 6; + context.currentLocalVariableAnnotationRangeStarts[i] = + createLabel(startPc, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeEnds[i] = + createLabel(startPc + length, context.currentMethodLabels); + context.currentLocalVariableAnnotationRangeIndices[i] = index; + } + break; + case TypeReference.CAST: + case TypeReference.CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case TypeReference.METHOD_INVOCATION_TYPE_ARGUMENT: + case TypeReference.CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case TypeReference.METHOD_REFERENCE_TYPE_ARGUMENT: + targetType &= 0xFF0000FF; + currentOffset += 4; + break; + case TypeReference.CLASS_EXTENDS: + case TypeReference.CLASS_TYPE_PARAMETER_BOUND: + case TypeReference.METHOD_TYPE_PARAMETER_BOUND: + case TypeReference.THROWS: + case TypeReference.EXCEPTION_PARAMETER: + targetType &= 0xFFFFFF00; + currentOffset += 3; + break; + case TypeReference.INSTANCEOF: + case TypeReference.NEW: + case TypeReference.CONSTRUCTOR_REFERENCE: + case TypeReference.METHOD_REFERENCE: + targetType &= 0xFF000000; + currentOffset += 3; + break; + default: + throw new IllegalArgumentException(); + } + context.currentTypeAnnotationTarget = targetType; + // Parse and store the target_path structure. + int pathLength = readByte(currentOffset); + context.currentTypeAnnotationTargetPath = + pathLength == 0 ? null : new TypePath(classFileBuffer, currentOffset); + // Return the start offset of the rest of the type_annotation structure. + return currentOffset + 1 + 2 * pathLength; + } + + /** + * Reads a Runtime[In]VisibleParameterAnnotations attribute and makes the given visitor visit it. + * + * @param methodVisitor the visitor that must visit the parameter annotations. + * @param context information about the class being parsed. + * @param runtimeParameterAnnotationsOffset the start offset of a + * Runtime[In]VisibleParameterAnnotations attribute, excluding the attribute_info's + * attribute_name_index and attribute_length fields. + * @param visible true if the attribute to parse is a RuntimeVisibleParameterAnnotations + * attribute, false it is a RuntimeInvisibleParameterAnnotations attribute. + */ + private void readParameterAnnotations( + final MethodVisitor methodVisitor, + final Context context, + final int runtimeParameterAnnotationsOffset, + final boolean visible) { + int currentOffset = runtimeParameterAnnotationsOffset; + int numParameters = classFileBuffer[currentOffset++] & 0xFF; + methodVisitor.visitAnnotableParameterCount(numParameters, visible); + char[] charBuffer = context.charBuffer; + for (int i = 0; i < numParameters; ++i) { + int numAnnotations = readUnsignedShort(currentOffset); + currentOffset += 2; + while (numAnnotations-- > 0) { + // Parse the type_index field. + String annotationDescriptor = readUTF8(currentOffset, charBuffer); + currentOffset += 2; + // Parse num_element_value_pairs and element_value_pairs and visit these values. + currentOffset = + readElementValues( + methodVisitor.visitParameterAnnotation(i, annotationDescriptor, visible), + currentOffset, + /* named = */ true, + charBuffer); + } + } + } + + /** + * Reads the element values of a JVMS 'annotation' structure and makes the given visitor visit + * them. This method can also be used to read the values of the JVMS 'array_value' field of an + * annotation's 'element_value'. + * + * @param annotationVisitor the visitor that must visit the values. + * @param annotationOffset the start offset of an 'annotation' structure (excluding its type_index + * field) or of an 'array_value' structure. + * @param named if the annotation values are named or not. This should be true to parse the values + * of a JVMS 'annotation' structure, and false to parse the JVMS 'array_value' of an + * annotation's element_value. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'annotation' or 'array_value' structure. + */ + private int readElementValues( + final AnnotationVisitor annotationVisitor, + final int annotationOffset, + final boolean named, + final char[] charBuffer) { + int currentOffset = annotationOffset; + // Read the num_element_value_pairs field (or num_values field for an array_value). + int numElementValuePairs = readUnsignedShort(currentOffset); + currentOffset += 2; + if (named) { + // Parse the element_value_pairs array. + while (numElementValuePairs-- > 0) { + String elementName = readUTF8(currentOffset, charBuffer); + currentOffset = + readElementValue(annotationVisitor, currentOffset + 2, elementName, charBuffer); + } + } else { + // Parse the array_value array. + while (numElementValuePairs-- > 0) { + currentOffset = + readElementValue(annotationVisitor, currentOffset, /* elementName= */ null, charBuffer); + } + } + if (annotationVisitor != null) { + annotationVisitor.visitEnd(); + } + return currentOffset; + } + + /** + * Reads a JVMS 'element_value' structure and makes the given visitor visit it. + * + * @param annotationVisitor the visitor that must visit the element_value structure. + * @param elementValueOffset the start offset in {@link #classFileBuffer} of the element_value + * structure to be read. + * @param elementName the name of the element_value structure to be read, or {@literal null}. + * @param charBuffer the buffer used to read strings in the constant pool. + * @return the end offset of the JVMS 'element_value' structure. + */ + private int readElementValue( + final AnnotationVisitor annotationVisitor, + final int elementValueOffset, + final String elementName, + final char[] charBuffer) { + int currentOffset = elementValueOffset; + if (annotationVisitor == null) { + switch (classFileBuffer[currentOffset] & 0xFF) { + case 'e': // enum_const_value + return currentOffset + 5; + case '@': // annotation_value + return readElementValues(null, currentOffset + 3, /* named = */ true, charBuffer); + case '[': // array_value + return readElementValues(null, currentOffset + 1, /* named = */ false, charBuffer); + default: + return currentOffset + 3; + } + } + switch (classFileBuffer[currentOffset++] & 0xFF) { + case 'B': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'C': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + case 'D': // const_value_index, CONSTANT_Double + case 'F': // const_value_index, CONSTANT_Float + case 'I': // const_value_index, CONSTANT_Integer + case 'J': // const_value_index, CONSTANT_Long + annotationVisitor.visit( + elementName, readConst(readUnsignedShort(currentOffset), charBuffer)); + currentOffset += 2; + break; + case 'S': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset)])); + currentOffset += 2; + break; + + case 'Z': // const_value_index, CONSTANT_Integer + annotationVisitor.visit( + elementName, + readInt(cpInfoOffsets[readUnsignedShort(currentOffset)]) == 0 + ? Boolean.FALSE + : Boolean.TRUE); + currentOffset += 2; + break; + case 's': // const_value_index, CONSTANT_Utf8 + annotationVisitor.visit(elementName, readUTF8(currentOffset, charBuffer)); + currentOffset += 2; + break; + case 'e': // enum_const_value + annotationVisitor.visitEnum( + elementName, + readUTF8(currentOffset, charBuffer), + readUTF8(currentOffset + 2, charBuffer)); + currentOffset += 4; + break; + case 'c': // class_info + annotationVisitor.visit(elementName, Type.getType(readUTF8(currentOffset, charBuffer))); + currentOffset += 2; + break; + case '@': // annotation_value + currentOffset = + readElementValues( + annotationVisitor.visitAnnotation(elementName, readUTF8(currentOffset, charBuffer)), + currentOffset + 2, + true, + charBuffer); + break; + case '[': // array_value + int numValues = readUnsignedShort(currentOffset); + currentOffset += 2; + if (numValues == 0) { + return readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); + } + switch (classFileBuffer[currentOffset] & 0xFF) { + case 'B': + byte[] byteValues = new byte[numValues]; + for (int i = 0; i < numValues; i++) { + byteValues[i] = (byte) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, byteValues); + break; + case 'Z': + boolean[] booleanValues = new boolean[numValues]; + for (int i = 0; i < numValues; i++) { + booleanValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]) != 0; + currentOffset += 3; + } + annotationVisitor.visit(elementName, booleanValues); + break; + case 'S': + short[] shortValues = new short[numValues]; + for (int i = 0; i < numValues; i++) { + shortValues[i] = (short) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, shortValues); + break; + case 'C': + char[] charValues = new char[numValues]; + for (int i = 0; i < numValues; i++) { + charValues[i] = (char) readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, charValues); + break; + case 'I': + int[] intValues = new int[numValues]; + for (int i = 0; i < numValues; i++) { + intValues[i] = readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, intValues); + break; + case 'J': + long[] longValues = new long[numValues]; + for (int i = 0; i < numValues; i++) { + longValues[i] = readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)]); + currentOffset += 3; + } + annotationVisitor.visit(elementName, longValues); + break; + case 'F': + float[] floatValues = new float[numValues]; + for (int i = 0; i < numValues; i++) { + floatValues[i] = + Float.intBitsToFloat( + readInt(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, floatValues); + break; + case 'D': + double[] doubleValues = new double[numValues]; + for (int i = 0; i < numValues; i++) { + doubleValues[i] = + Double.longBitsToDouble( + readLong(cpInfoOffsets[readUnsignedShort(currentOffset + 1)])); + currentOffset += 3; + } + annotationVisitor.visit(elementName, doubleValues); + break; + default: + currentOffset = + readElementValues( + annotationVisitor.visitArray(elementName), + currentOffset - 2, + /* named = */ false, + charBuffer); + break; + } + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse stack map frames + // ---------------------------------------------------------------------------------------------- + + /** + * Computes the implicit frame of the method currently being parsed (as defined in the given + * {@link Context}) and stores it in the given context. + * + * @param context information about the class being parsed. + */ + private void computeImplicitFrame(final Context context) { + String methodDescriptor = context.currentMethodDescriptor; + Object[] locals = context.currentFrameLocalTypes; + int numLocal = 0; + if ((context.currentMethodAccessFlags & Opcodes.ACC_STATIC) == 0) { + if ("".equals(context.currentMethodName)) { + locals[numLocal++] = Opcodes.UNINITIALIZED_THIS; + } else { + locals[numLocal++] = readClass(header + 2, context.charBuffer); + } + } + // Parse the method descriptor, one argument type descriptor at each iteration. Start by + // skipping the first method descriptor character, which is always '('. + int currentMethodDescritorOffset = 1; + while (true) { + int currentArgumentDescriptorStartOffset = currentMethodDescritorOffset; + switch (methodDescriptor.charAt(currentMethodDescritorOffset++)) { + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + locals[numLocal++] = Opcodes.INTEGER; + break; + case 'F': + locals[numLocal++] = Opcodes.FLOAT; + break; + case 'J': + locals[numLocal++] = Opcodes.LONG; + break; + case 'D': + locals[numLocal++] = Opcodes.DOUBLE; + break; + case '[': + while (methodDescriptor.charAt(currentMethodDescritorOffset) == '[') { + ++currentMethodDescritorOffset; + } + if (methodDescriptor.charAt(currentMethodDescritorOffset) == 'L') { + ++currentMethodDescritorOffset; + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + } + locals[numLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset, ++currentMethodDescritorOffset); + break; + case 'L': + while (methodDescriptor.charAt(currentMethodDescritorOffset) != ';') { + ++currentMethodDescritorOffset; + } + locals[numLocal++] = + methodDescriptor.substring( + currentArgumentDescriptorStartOffset + 1, currentMethodDescritorOffset++); + break; + default: + context.currentFrameLocalCount = numLocal; + return; + } + } + } + + /** + * Reads a JVMS 'stack_map_frame' structure and stores the result in the given {@link Context} + * object. This method can also be used to read a full_frame structure, excluding its frame_type + * field (this is used to parse the legacy StackMap attributes). + * + * @param stackMapFrameOffset the start offset in {@link #classFileBuffer} of the + * stack_map_frame_value structure to be read, or the start offset of a full_frame structure + * (excluding its frame_type field). + * @param compressed true to read a 'stack_map_frame' structure, false to read a 'full_frame' + * structure without its frame_type field. + * @param expand if the stack map frame must be expanded. See {@link #EXPAND_FRAMES}. + * @param context where the parsed stack map frame must be stored. + * @return the end offset of the JVMS 'stack_map_frame' or 'full_frame' structure. + */ + private int readStackMapFrame( + final int stackMapFrameOffset, + final boolean compressed, + final boolean expand, + final Context context) { + int currentOffset = stackMapFrameOffset; + final char[] charBuffer = context.charBuffer; + final Label[] labels = context.currentMethodLabels; + int frameType; + if (compressed) { + // Read the frame_type field. + frameType = classFileBuffer[currentOffset++] & 0xFF; + } else { + frameType = Frame.FULL_FRAME; + context.currentFrameOffset = -1; + } + int offsetDelta; + context.currentFrameLocalCountDelta = 0; + if (frameType < Frame.SAME_LOCALS_1_STACK_ITEM_FRAME) { + offsetDelta = frameType; + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.RESERVED) { + offsetDelta = frameType - Frame.SAME_LOCALS_1_STACK_ITEM_FRAME; + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + offsetDelta = readUnsignedShort(currentOffset); + currentOffset += 2; + if (frameType == Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, 0, charBuffer, labels); + context.currentFrameType = Opcodes.F_SAME1; + context.currentFrameStackCount = 1; + } else if (frameType >= Frame.CHOP_FRAME && frameType < Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_CHOP; + context.currentFrameLocalCountDelta = Frame.SAME_FRAME_EXTENDED - frameType; + context.currentFrameLocalCount -= context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else if (frameType == Frame.SAME_FRAME_EXTENDED) { + context.currentFrameType = Opcodes.F_SAME; + context.currentFrameStackCount = 0; + } else if (frameType < Frame.FULL_FRAME) { + int local = expand ? context.currentFrameLocalCount : 0; + for (int k = frameType - Frame.SAME_FRAME_EXTENDED; k > 0; k--) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local++, charBuffer, labels); + } + context.currentFrameType = Opcodes.F_APPEND; + context.currentFrameLocalCountDelta = frameType - Frame.SAME_FRAME_EXTENDED; + context.currentFrameLocalCount += context.currentFrameLocalCountDelta; + context.currentFrameStackCount = 0; + } else { + final int numberOfLocals = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameType = Opcodes.F_FULL; + context.currentFrameLocalCountDelta = numberOfLocals; + context.currentFrameLocalCount = numberOfLocals; + for (int local = 0; local < numberOfLocals; ++local) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameLocalTypes, local, charBuffer, labels); + } + final int numberOfStackItems = readUnsignedShort(currentOffset); + currentOffset += 2; + context.currentFrameStackCount = numberOfStackItems; + for (int stack = 0; stack < numberOfStackItems; ++stack) { + currentOffset = + readVerificationTypeInfo( + currentOffset, context.currentFrameStackTypes, stack, charBuffer, labels); + } + } + } else { + throw new IllegalArgumentException(); + } + context.currentFrameOffset += offsetDelta + 1; + createLabel(context.currentFrameOffset, labels); + return currentOffset; + } + + /** + * Reads a JVMS 'verification_type_info' structure and stores it at the given index in the given + * array. + * + * @param verificationTypeInfoOffset the start offset of the 'verification_type_info' structure to + * read. + * @param frame the array where the parsed type must be stored. + * @param index the index in 'frame' where the parsed type must be stored. + * @param charBuffer the buffer used to read strings in the constant pool. + * @param labels the labels of the method currently being parsed, indexed by their offset. If the + * parsed type is an ITEM_Uninitialized, a new label for the corresponding NEW instruction is + * stored in this array if it does not already exist. + * @return the end offset of the JVMS 'verification_type_info' structure. + */ + private int readVerificationTypeInfo( + final int verificationTypeInfoOffset, + final Object[] frame, + final int index, + final char[] charBuffer, + final Label[] labels) { + int currentOffset = verificationTypeInfoOffset; + int tag = classFileBuffer[currentOffset++] & 0xFF; + switch (tag) { + case Frame.ITEM_TOP: + frame[index] = Opcodes.TOP; + break; + case Frame.ITEM_INTEGER: + frame[index] = Opcodes.INTEGER; + break; + case Frame.ITEM_FLOAT: + frame[index] = Opcodes.FLOAT; + break; + case Frame.ITEM_DOUBLE: + frame[index] = Opcodes.DOUBLE; + break; + case Frame.ITEM_LONG: + frame[index] = Opcodes.LONG; + break; + case Frame.ITEM_NULL: + frame[index] = Opcodes.NULL; + break; + case Frame.ITEM_UNINITIALIZED_THIS: + frame[index] = Opcodes.UNINITIALIZED_THIS; + break; + case Frame.ITEM_OBJECT: + frame[index] = readClass(currentOffset, charBuffer); + currentOffset += 2; + break; + case Frame.ITEM_UNINITIALIZED: + frame[index] = createLabel(readUnsignedShort(currentOffset), labels); + currentOffset += 2; + break; + default: + throw new IllegalArgumentException(); + } + return currentOffset; + } + + // ---------------------------------------------------------------------------------------------- + // Methods to parse attributes + // ---------------------------------------------------------------------------------------------- + + /** + * Returns the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. + * + * @return the offset in {@link #classFileBuffer} of the first ClassFile's 'attributes' array + * field entry. + */ + final int getFirstAttributeOffset() { + // Skip the access_flags, this_class, super_class, and interfaces_count fields (using 2 bytes + // each), as well as the interfaces array field (2 bytes per interface). + int currentOffset = header + 8 + readUnsignedShort(header + 6) * 2; + + // Read the fields_count field. + int fieldsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + // Skip the 'fields' array field. + while (fieldsCount-- > 0) { + // Invariant: currentOffset is the offset of a field_info structure. + // Skip the access_flags, name_index and descriptor_index fields (2 bytes each), and read the + // attributes_count field. + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + // Skip the 'attributes' array field. + while (attributesCount-- > 0) { + // Invariant: currentOffset is the offset of an attribute_info structure. + // Read the attribute_length field (2 bytes after the start of the attribute_info) and skip + // this many bytes, plus 6 for the attribute_name_index and attribute_length fields + // (yielding the total size of the attribute_info structure). + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the methods_count and 'methods' fields, using the same method as above. + int methodsCount = readUnsignedShort(currentOffset); + currentOffset += 2; + while (methodsCount-- > 0) { + int attributesCount = readUnsignedShort(currentOffset + 6); + currentOffset += 8; + while (attributesCount-- > 0) { + currentOffset += 6 + readInt(currentOffset + 2); + } + } + + // Skip the ClassFile's attributes_count field. + return currentOffset + 2; + } + + /** + * Reads the BootstrapMethods attribute to compute the offset of each bootstrap method. + * + * @param maxStringLength a conservative estimate of the maximum length of the strings contained + * in the constant pool of the class. + * @return the offsets of the bootstrap methods. + */ + private int[] readBootstrapMethodsAttribute(final int maxStringLength) { + char[] charBuffer = new char[maxStringLength]; + int currentAttributeOffset = getFirstAttributeOffset(); + for (int i = readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + // Read the attribute_info's attribute_name and attribute_length fields. + String attributeName = readUTF8(currentAttributeOffset, charBuffer); + int attributeLength = readInt(currentAttributeOffset + 2); + currentAttributeOffset += 6; + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + // Read the num_bootstrap_methods field and create an array of this size. + int[] result = new int[readUnsignedShort(currentAttributeOffset)]; + // Compute and store the offset of each 'bootstrap_methods' array field entry. + int currentBootstrapMethodOffset = currentAttributeOffset + 2; + for (int j = 0; j < result.length; ++j) { + result[j] = currentBootstrapMethodOffset; + // Skip the bootstrap_method_ref and num_bootstrap_arguments fields (2 bytes each), + // as well as the bootstrap_arguments array field (of size num_bootstrap_arguments * 2). + currentBootstrapMethodOffset += + 4 + readUnsignedShort(currentBootstrapMethodOffset + 2) * 2; + } + return result; + } + currentAttributeOffset += attributeLength; + } + throw new IllegalArgumentException(); + } + + /** + * Reads a non standard JVMS 'attribute' structure in {@link #classFileBuffer}. + * + * @param attributePrototypes prototypes of the attributes that must be parsed during the visit of + * the class. Any attribute whose type is not equal to the type of one the prototypes will not + * be parsed: its byte array value will be passed unchanged to the ClassWriter. + * @param type the type of the attribute. + * @param offset the start offset of the JVMS 'attribute' structure in {@link #classFileBuffer}. + * The 6 attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param length the length of the attribute's content (excluding the 6 attribute header bytes). + * @param charBuffer the buffer to be used to read strings in the constant pool. + * @param codeAttributeOffset the start offset of the enclosing Code attribute in {@link + * #classFileBuffer}, or -1 if the attribute to be read is not a code attribute. The 6 + * attribute header bytes (attribute_name_index and attribute_length) are not taken into + * account here. + * @param labels the labels of the method's code, or {@literal null} if the attribute to be read + * is not a code attribute. + * @return the attribute that has been read. + */ + private Attribute readAttribute( + final Attribute[] attributePrototypes, + final String type, + final int offset, + final int length, + final char[] charBuffer, + final int codeAttributeOffset, + final Label[] labels) { + for (Attribute attributePrototype : attributePrototypes) { + if (attributePrototype.type.equals(type)) { + return attributePrototype.read( + this, offset, length, charBuffer, codeAttributeOffset, labels); + } + } + return new Attribute(type).read(this, offset, length, null, -1, null); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: low level parsing + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the number of entries in the class's constant pool table. + * + * @return the number of entries in the class's constant pool table. + */ + public int getItemCount() { + return cpInfoOffsets.length; + } + + /** + * Returns the start offset in this {@link ClassReader} of a JVMS 'cp_info' structure (i.e. a + * constant pool entry), plus one. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param constantPoolEntryIndex the index a constant pool entry in the class's constant pool + * table. + * @return the start offset in this {@link ClassReader} of the corresponding JVMS 'cp_info' + * structure, plus one. + */ + public int getItem(final int constantPoolEntryIndex) { + return cpInfoOffsets[constantPoolEntryIndex]; + } + + /** + * Returns a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + * + * @return a conservative estimate of the maximum length of the strings contained in the class's + * constant pool table. + */ + public int getMaxStringLength() { + return maxStringLength; + } + + /** + * Reads a byte value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readByte(final int offset) { + return classFileBuffer[offset] & 0xFF; + } + + /** + * Reads an unsigned short value in this {@link ClassReader}. This method is intended for + * {@link Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start index of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readUnsignedShort(final int offset) { + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF); + } + + /** + * Reads a signed short value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public short readShort(final int offset) { + byte[] classBuffer = classFileBuffer; + return (short) (((classBuffer[offset] & 0xFF) << 8) | (classBuffer[offset + 1] & 0xFF)); + } + + /** + * Reads a signed int value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public int readInt(final int offset) { + byte[] classBuffer = classFileBuffer; + return ((classBuffer[offset] & 0xFF) << 24) + | ((classBuffer[offset + 1] & 0xFF) << 16) + | ((classBuffer[offset + 2] & 0xFF) << 8) + | (classBuffer[offset + 3] & 0xFF); + } + + /** + * Reads a signed long value in this {@link ClassReader}. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param offset the start offset of the value to be read in this {@link ClassReader}. + * @return the read value. + */ + public long readLong(final int offset) { + long l1 = readInt(offset); + long l0 = readInt(offset + 4) & 0xFFFFFFFFL; + return (l1 << 32) | l0; + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Utf8 entry in the class's constant pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public String readUTF8(final int offset, final char[] charBuffer) { + int constantPoolEntryIndex = readUnsignedShort(offset); + if (offset == 0 || constantPoolEntryIndex == 0) { + return null; + } + return readUtf(constantPoolEntryIndex, charBuffer); + } + + /** + * Reads a CONSTANT_Utf8 constant pool entry in {@link #classFileBuffer}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Utf8 entry in the class's constant pool + * table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Utf8 entry. + */ + final String readUtf(final int constantPoolEntryIndex, final char[] charBuffer) { + String value = constantUtf8Values[constantPoolEntryIndex]; + if (value != null) { + return value; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + return constantUtf8Values[constantPoolEntryIndex] = + readUtf(cpInfoOffset + 2, readUnsignedShort(cpInfoOffset), charBuffer); + } + + /** + * Reads an UTF8 string in {@link #classFileBuffer}. + * + * @param utfOffset the start offset of the UTF8 string to be read. + * @param utfLength the length of the UTF8 string to be read. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified UTF8 string. + */ + private String readUtf(final int utfOffset, final int utfLength, final char[] charBuffer) { + int currentOffset = utfOffset; + int endOffset = currentOffset + utfLength; + int strLength = 0; + byte[] classBuffer = classFileBuffer; + while (currentOffset < endOffset) { + int currentByte = classBuffer[currentOffset++]; + if ((currentByte & 0x80) == 0) { + charBuffer[strLength++] = (char) (currentByte & 0x7F); + } else if ((currentByte & 0xE0) == 0xC0) { + charBuffer[strLength++] = + (char) (((currentByte & 0x1F) << 6) + (classBuffer[currentOffset++] & 0x3F)); + } else { + charBuffer[strLength++] = + (char) + (((currentByte & 0xF) << 12) + + ((classBuffer[currentOffset++] & 0x3F) << 6) + + (classBuffer[currentOffset++] & 0x3F)); + } + } + return new String(charBuffer, 0, strLength); + } + + /** + * Reads a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, CONSTANT_Module or + * CONSTANT_Package constant pool entry in {@link #classFileBuffer}. This method is intended + * for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in {@link #classFileBuffer}, whose + * value is the index of a CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_Module or CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified constant pool entry. + */ + private String readStringish(final int offset, final char[] charBuffer) { + // Get the start offset of the cp_info structure (plus one), and read the CONSTANT_Utf8 entry + // designated by the first two bytes of this cp_info. + return readUTF8(cpInfoOffsets[readUnsignedShort(offset)], charBuffer); + } + + /** + * Reads a CONSTANT_Class constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Class entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Class entry. + */ + public String readClass(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Module constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Module entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Module entry. + */ + public String readModule(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Package constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param offset the start offset of an unsigned short value in this {@link ClassReader}, whose + * value is the index of a CONSTANT_Package entry in class's constant pool table. + * @param charBuffer the buffer to be used to read the item. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the String corresponding to the specified CONSTANT_Package entry. + */ + public String readPackage(final int offset, final char[] charBuffer) { + return readStringish(offset, charBuffer); + } + + /** + * Reads a CONSTANT_Dynamic constant pool entry in {@link #classFileBuffer}. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Dynamic entry in the class's constant + * pool table. + * @param charBuffer the buffer to be used to read the string. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the ConstantDynamic corresponding to the specified CONSTANT_Dynamic entry. + */ + private ConstantDynamic readConstantDynamic( + final int constantPoolEntryIndex, final char[] charBuffer) { + ConstantDynamic constantDynamic = constantDynamicValues[constantPoolEntryIndex]; + if (constantDynamic != null) { + return constantDynamic; + } + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 2)]; + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + int bootstrapMethodOffset = bootstrapMethodOffsets[readUnsignedShort(cpInfoOffset)]; + Handle handle = (Handle) readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + Object[] bootstrapMethodArguments = new Object[readUnsignedShort(bootstrapMethodOffset + 2)]; + bootstrapMethodOffset += 4; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + bootstrapMethodArguments[i] = readConst(readUnsignedShort(bootstrapMethodOffset), charBuffer); + bootstrapMethodOffset += 2; + } + return constantDynamicValues[constantPoolEntryIndex] = + new ConstantDynamic(name, descriptor, handle, bootstrapMethodArguments); + } + + /** + * Reads a numeric or string constant pool entry in this {@link ClassReader}. This method is + * intended for {@link Attribute} sub classes, and is normally not needed by class generators or + * adapters. + * + * @param constantPoolEntryIndex the index of a CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long, + * CONSTANT_Double, CONSTANT_Class, CONSTANT_String, CONSTANT_MethodType, + * CONSTANT_MethodHandle or CONSTANT_Dynamic entry in the class's constant pool. + * @param charBuffer the buffer to be used to read strings. This buffer must be sufficiently + * large. It is not automatically resized. + * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, + * {@link Type}, {@link Handle} or {@link ConstantDynamic} corresponding to the specified + * constant pool entry. + */ + public Object readConst(final int constantPoolEntryIndex, final char[] charBuffer) { + int cpInfoOffset = cpInfoOffsets[constantPoolEntryIndex]; + switch (classFileBuffer[cpInfoOffset - 1]) { + case Symbol.CONSTANT_INTEGER_TAG: + return readInt(cpInfoOffset); + case Symbol.CONSTANT_FLOAT_TAG: + return Float.intBitsToFloat(readInt(cpInfoOffset)); + case Symbol.CONSTANT_LONG_TAG: + return readLong(cpInfoOffset); + case Symbol.CONSTANT_DOUBLE_TAG: + return Double.longBitsToDouble(readLong(cpInfoOffset)); + case Symbol.CONSTANT_CLASS_TAG: + return Type.getObjectType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_STRING_TAG: + return readUTF8(cpInfoOffset, charBuffer); + case Symbol.CONSTANT_METHOD_TYPE_TAG: + return Type.getMethodType(readUTF8(cpInfoOffset, charBuffer)); + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int referenceKind = readByte(cpInfoOffset); + int referenceCpInfoOffset = cpInfoOffsets[readUnsignedShort(cpInfoOffset + 1)]; + int nameAndTypeCpInfoOffset = cpInfoOffsets[readUnsignedShort(referenceCpInfoOffset + 2)]; + String owner = readClass(referenceCpInfoOffset, charBuffer); + String name = readUTF8(nameAndTypeCpInfoOffset, charBuffer); + String descriptor = readUTF8(nameAndTypeCpInfoOffset + 2, charBuffer); + boolean isInterface = + classFileBuffer[referenceCpInfoOffset - 1] == Symbol.CONSTANT_INTERFACE_METHODREF_TAG; + return new Handle(referenceKind, owner, name, descriptor, isInterface); + case Symbol.CONSTANT_DYNAMIC_TAG: + return readConstantDynamic(constantPoolEntryIndex, charBuffer); + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassTooLargeException.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassTooLargeException.class new file mode 100644 index 00000000..f036989c Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassTooLargeException.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassTooLargeException.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassTooLargeException.java new file mode 100644 index 00000000..c110ec34 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassTooLargeException.java @@ -0,0 +1,104 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * Exception thrown when the constant pool of a class produced by a {@link ClassWriter} is too + * large. + * + * @author Jason Zaugg + */ +public final class ClassTooLargeException extends IndexOutOfBoundsException { + private static final long serialVersionUID = 160715609518896765L; + + private final String className; + private final int constantPoolCount; + + /** + * Constructs a new {@link ClassTooLargeException}. + * + * @param className the internal name of the class (see {@link + * jdk.internal.org.objectweb.asm.Type#getInternalName()}). + * @param constantPoolCount the number of constant pool items of the class. + */ + public ClassTooLargeException(final String className, final int constantPoolCount) { + super("Class too large: " + className); + this.className = className; + this.constantPoolCount = constantPoolCount; + } + + /** + * Returns the internal name of the class (see {@link jdk.internal.org.objectweb.asm.Type#getInternalName()}). + * + * @return the internal name of the class. + */ + public String getClassName() { + return className; + } + + /** + * Returns the number of constant pool items of the class. + * + * @return the number of constant pool items of the class. + */ + public int getConstantPoolCount() { + return constantPoolCount; + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassVisitor.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassVisitor.class new file mode 100644 index 00000000..0ddcbe24 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassVisitor.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassVisitor.java new file mode 100644 index 00000000..21a9c721 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassVisitor.java @@ -0,0 +1,426 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A visitor to visit a Java class. The methods of this class must be called in the following order: + * {@code visit} [ {@code visitSource} ] [ {@code visitModule} ][ {@code visitNestHost} ][ {@code + * visitOuterClass} ] ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code + * visitAttribute} )* ( {@code visitNestMember} | [ {@code * visitPermittedSubclass} ] | {@code + * visitInnerClass} | {@code visitRecordComponent} | {@code visitField} | {@code visitMethod} )* + * {@code visitEnd}. + * + * @author Eric Bruneton + */ +public abstract class ClassVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. + */ + protected final int api; + + /** The class visitor to which this visitor must delegate method calls. May be {@literal null}. */ + protected ClassVisitor cv; + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + */ + protected ClassVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ClassVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + * @param classVisitor the class visitor to which this visitor must delegate method calls. May be + * null. + */ + protected ClassVisitor(final int api, final ClassVisitor classVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + this.cv = classVisitor; + } + + /** + * The class visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the class visitor to which this visitor must delegate method calls, or {@literal null}. + */ + public ClassVisitor getDelegate() { + return cv; + } + + /** + * Visits the header of the class. + * + * @param version the class version. The minor version is stored in the 16 most significant bits, + * and the major version in the 16 least significant bits. + * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if + * the class is deprecated {@link Opcodes#ACC_DEPRECATED} or a record {@link + * Opcodes#ACC_RECORD}. + * @param name the internal name of the class (see {@link Type#getInternalName()}). + * @param signature the signature of this class. May be {@literal null} if the class is not a + * generic one, and does not extend or implement generic classes or interfaces. + * @param superName the internal of name of the super class (see {@link Type#getInternalName()}). + * For interfaces, the super class is {@link Object}. May be {@literal null}, but only for the + * {@link Object} class. + * @param interfaces the internal names of the class's interfaces (see {@link + * Type#getInternalName()}). May be {@literal null}. + */ + public void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + if (api < Opcodes.ASM8 && (access & Opcodes.ACC_RECORD) != 0) { + throw new UnsupportedOperationException("Records requires ASM8"); + } + if (cv != null) { + cv.visit(version, access, name, signature, superName, interfaces); + } + } + + /** + * Visits the source of the class. + * + * @param source the name of the source file from which the class was compiled. May be {@literal + * null}. + * @param debug additional debug information to compute the correspondence between source and + * compiled elements of the class. May be {@literal null}. + */ + public void visitSource(final String source, final String debug) { + if (cv != null) { + cv.visitSource(source, debug); + } + } + + /** + * Visit the module corresponding to the class. + * + * @param name the fully qualified name (using dots) of the module. + * @param access the module access flags, among {@code ACC_OPEN}, {@code ACC_SYNTHETIC} and {@code + * ACC_MANDATED}. + * @param version the module version, or {@literal null}. + * @return a visitor to visit the module values, or {@literal null} if this visitor is not + * interested in visiting this module. + */ + public ModuleVisitor visitModule(final String name, final int access, final String version) { + if (api < Opcodes.ASM6) { + throw new UnsupportedOperationException("Module requires ASM6"); + } + if (cv != null) { + return cv.visitModule(name, access, version); + } + return null; + } + + /** + * Visits the nest host class of the class. A nest is a set of classes of the same package that + * share access to their private members. One of these classes, called the host, lists the other + * members of the nest, which in turn should link to the host of their nest. This method must be + * called only once and only if the visited class is a non-host member of a nest. A class is + * implicitly its own nest, so it's invalid to call this method with the visited class name as + * argument. + * + * @param nestHost the internal name of the host class of the nest (see {@link + * Type#getInternalName()}). + */ + public void visitNestHost(final String nestHost) { + if (api < Opcodes.ASM7) { + throw new UnsupportedOperationException("NestHost requires ASM7"); + } + if (cv != null) { + cv.visitNestHost(nestHost); + } + } + + /** + * Visits the enclosing class of the class. This method must be called only if this class is a + * local or anonymous class. See the JVMS 4.7.7 section for more details. + * + * @param owner internal name of the enclosing class of the class (see {@link + * Type#getInternalName()}). + * @param name the name of the method that contains the class, or {@literal null} if the class is + * not enclosed in a method or constructor of its enclosing class (e.g. if it is enclosed in + * an instance initializer, static initializer, instance variable initializer, or class + * variable initializer). + * @param descriptor the descriptor of the method that contains the class, or {@literal null} if + * the class is not enclosed in a method or constructor of its enclosing class (e.g. if it is + * enclosed in an instance initializer, static initializer, instance variable initializer, or + * class variable initializer). + */ + public void visitOuterClass(final String owner, final String name, final String descriptor) { + if (cv != null) { + cv.visitOuterClass(owner, name, descriptor); + } + } + + /** + * Visits an annotation of the class. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (cv != null) { + return cv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the class signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException("TypeAnnotation requires ASM5"); + } + if (cv != null) { + return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the class. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (cv != null) { + cv.visitAttribute(attribute); + } + } + + /** + * Visits a member of the nest. A nest is a set of classes of the same package that share access + * to their private members. One of these classes, called the host, lists the other members of the + * nest, which in turn should link to the host of their nest. This method must be called only if + * the visited class is the host of a nest. A nest host is implicitly a member of its own nest, so + * it's invalid to call this method with the visited class name as argument. + * + * @param nestMember the internal name of a nest member (see {@link Type#getInternalName()}). + */ + public void visitNestMember(final String nestMember) { + if (api < Opcodes.ASM7) { + throw new UnsupportedOperationException("NestMember requires ASM7"); + } + if (cv != null) { + cv.visitNestMember(nestMember); + } + } + + /** + * Visits a permitted subclasses. A permitted subclass is one of the allowed subclasses of the + * current class. + * + * @param permittedSubclass the internal name of a permitted subclass (see {@link + * Type#getInternalName()}). + */ + public void visitPermittedSubclass(final String permittedSubclass) { + if (api < Opcodes.ASM9) { + throw new UnsupportedOperationException("PermittedSubclasses requires ASM9"); + } + if (cv != null) { + cv.visitPermittedSubclass(permittedSubclass); + } + } + + /** + * Visits information about an inner class. This inner class is not necessarily a member of the + * class being visited. More precisely, every class or interface C which is referenced by this + * class and which is not a package member must be visited with this method. This class must + * reference its nested class or interface members, and its enclosing class, if any. See the JVMS + * 4.7.6 section for more details. + * + * @param name the internal name of C (see {@link Type#getInternalName()}). + * @param outerName the internal name of the class or interface C is a member of (see {@link + * Type#getInternalName()}). Must be {@literal null} if C is not the member of a class or + * interface (e.g. for local or anonymous classes). + * @param innerName the (simple) name of C. Must be {@literal null} for anonymous inner classes. + * @param access the access flags of C originally declared in the source code from which this + * class was compiled. + */ + public void visitInnerClass( + final String name, final String outerName, final String innerName, final int access) { + if (cv != null) { + cv.visitInnerClass(name, outerName, innerName, access); + } + } + + /** + * Visits a record component of the class. + * + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null} if the record component + * type does not use generic types. + * @return a visitor to visit this record component annotations and attributes, or {@literal null} + * if this class visitor is not interested in visiting these annotations and attributes. + */ + public RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + if (api < Opcodes.ASM8) { + throw new UnsupportedOperationException("Record requires ASM8"); + } + if (cv != null) { + return cv.visitRecordComponent(name, descriptor, signature); + } + return null; + } + + /** + * Visits a field of the class. + * + * @param access the field's access flags (see {@link Opcodes}). This parameter also indicates if + * the field is synthetic and/or deprecated. + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be {@literal null} if the field's type does not use + * generic types. + * @param value the field's initial value. This parameter, which may be {@literal null} if the + * field does not have an initial value, must be an {@link Integer}, a {@link Float}, a {@link + * Long}, a {@link Double} or a {@link String} (for {@code int}, {@code float}, {@code long} + * or {@code String} fields respectively). This parameter is only used for static + * fields. Its value is ignored for non static fields, which must be initialized through + * bytecode instructions in constructors or methods. + * @return a visitor to visit field annotations and attributes, or {@literal null} if this class + * visitor is not interested in visiting these annotations and attributes. + */ + public FieldVisitor visitField( + final int access, + final String name, + final String descriptor, + final String signature, + final Object value) { + if (cv != null) { + return cv.visitField(access, name, descriptor, signature, value); + } + return null; + } + + /** + * Visits a method of the class. This method must return a new {@link MethodVisitor} + * instance (or {@literal null}) each time it is called, i.e., it should not return a previously + * returned visitor. + * + * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if + * the method is synthetic and/or deprecated. + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be {@literal null} if the method parameters, + * return type and exceptions do not use generic types. + * @param exceptions the internal names of the method's exception classes (see {@link + * Type#getInternalName()}). May be {@literal null}. + * @return an object to visit the byte code of the method, or {@literal null} if this class + * visitor is not interested in visiting the code of this method. + */ + public MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + if (cv != null) { + return cv.visitMethod(access, name, descriptor, signature, exceptions); + } + return null; + } + + /** + * Visits the end of the class. This method, which is the last one to be called, is used to inform + * the visitor that all the fields and methods of the class have been visited. + */ + public void visitEnd() { + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassWriter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassWriter.class new file mode 100644 index 00000000..accc6b16 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassWriter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassWriter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassWriter.java new file mode 100644 index 00000000..3996cbdf --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ClassWriter.java @@ -0,0 +1,1113 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A {@link ClassVisitor} that generates a corresponding ClassFile structure, as defined in the Java + * Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from + * scratch", or with one or more {@link ClassReader} and adapter {@link ClassVisitor} to generate a + * modified class from one or more existing Java classes. + * + * @see JVMS 4 + * @author Eric Bruneton + */ +public class ClassWriter extends ClassVisitor { + + /** + * A flag to automatically compute the maximum stack size and the maximum number of local + * variables of methods. If this flag is set, then the arguments of the {@link + * MethodVisitor#visitMaxs} method of the {@link MethodVisitor} returned by the {@link + * #visitMethod} method will be ignored, and computed automatically from the signature and the + * bytecode of each method. + * + *

Note: for classes whose version is {@link Opcodes#V1_7} of more, this option requires + * valid stack map frames. The maximum stack size is then computed from these frames, and from the + * bytecode instructions in between. If stack map frames are not present or must be recomputed, + * used {@link #COMPUTE_FRAMES} instead. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_MAXS = 1; + + /** + * A flag to automatically compute the stack map frames of methods from scratch. If this flag is + * set, then the calls to the {@link MethodVisitor#visitFrame} method are ignored, and the stack + * map frames are recomputed from the methods bytecode. The arguments of the {@link + * MethodVisitor#visitMaxs} method are also ignored and recomputed from the bytecode. In other + * words, {@link #COMPUTE_FRAMES} implies {@link #COMPUTE_MAXS}. + * + * @see #ClassWriter(int) + */ + public static final int COMPUTE_FRAMES = 2; + + /** + * The flags passed to the constructor. Must be zero or more of {@link #COMPUTE_MAXS} and {@link + * #COMPUTE_FRAMES}. + */ + private final int flags; + + // Note: fields are ordered as in the ClassFile structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The minor_version and major_version fields of the JVMS ClassFile structure. minor_version is + * stored in the 16 most significant bits, and major_version in the 16 least significant bits. + */ + private int version; + + /** The symbol table for this class (contains the constant_pool and the BootstrapMethods). */ + private final SymbolTable symbolTable; + + /** + * The access_flags field of the JVMS ClassFile structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED} or {@link Opcodes#ACC_RECORD}, which are + * removed when generating the ClassFile structure. + */ + private int accessFlags; + + /** The this_class field of the JVMS ClassFile structure. */ + private int thisClass; + + /** The super_class field of the JVMS ClassFile structure. */ + private int superClass; + + /** The interface_count field of the JVMS ClassFile structure. */ + private int interfaceCount; + + /** The 'interfaces' array of the JVMS ClassFile structure. */ + private int[] interfaces; + + /** + * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their + * {@link FieldWriter#fv} field. This field stores the first element of this list. + */ + private FieldWriter firstField; + + /** + * The fields of this class, stored in a linked list of {@link FieldWriter} linked via their + * {@link FieldWriter#fv} field. This field stores the last element of this list. + */ + private FieldWriter lastField; + + /** + * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their + * {@link MethodWriter#mv} field. This field stores the first element of this list. + */ + private MethodWriter firstMethod; + + /** + * The methods of this class, stored in a linked list of {@link MethodWriter} linked via their + * {@link MethodWriter#mv} field. This field stores the last element of this list. + */ + private MethodWriter lastMethod; + + /** The number_of_classes field of the InnerClasses attribute, or 0. */ + private int numberOfInnerClasses; + + /** The 'classes' array of the InnerClasses attribute, or {@literal null}. */ + private ByteVector innerClasses; + + /** The class_index field of the EnclosingMethod attribute, or 0. */ + private int enclosingClassIndex; + + /** The method_index field of the EnclosingMethod attribute. */ + private int enclosingMethodIndex; + + /** The signature_index field of the Signature attribute, or 0. */ + private int signatureIndex; + + /** The source_file_index field of the SourceFile attribute, or 0. */ + private int sourceFileIndex; + + /** The debug_extension field of the SourceDebugExtension attribute, or {@literal null}. */ + private ByteVector debugExtension; + + /** + * The last runtime visible annotation of this class. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this class. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this class. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this class. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** The Module attribute of this class, or {@literal null}. */ + private ModuleWriter moduleWriter; + + /** The host_class_index field of the NestHost attribute, or 0. */ + private int nestHostClassIndex; + + /** The number_of_classes field of the NestMembers attribute, or 0. */ + private int numberOfNestMemberClasses; + + /** The 'classes' array of the NestMembers attribute, or {@literal null}. */ + private ByteVector nestMemberClasses; + + /** The number_of_classes field of the PermittedSubclasses attribute, or 0. */ + private int numberOfPermittedSubclasses; + + /** The 'classes' array of the PermittedSubclasses attribute, or {@literal null}. */ + private ByteVector permittedSubclasses; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the first + * element of this list. + */ + private RecordComponentWriter firstRecordComponent; + + /** + * The record components of this class, stored in a linked list of {@link RecordComponentWriter} + * linked via their {@link RecordComponentWriter#delegate} field. This field stores the last + * element of this list. + */ + private RecordComponentWriter lastRecordComponent; + + /** + * The first non standard attribute of this class. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #toByteArray} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + /** + * Indicates what must be automatically computed in {@link MethodWriter}. Must be one of {@link + * MethodWriter#COMPUTE_NOTHING}, {@link MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL}, {@link + * MethodWriter#COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link + * MethodWriter#COMPUTE_INSERTED_FRAMES}, or {@link MethodWriter#COMPUTE_ALL_FRAMES}. + */ + private int compute; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link ClassWriter} object. + * + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. + */ + public ClassWriter(final int flags) { + this(null, flags); + } + + /** + * Constructs a new {@link ClassWriter} object and enables optimizations for "mostly add" bytecode + * transformations. These optimizations are the following: + * + *

    + *
  • The constant pool and bootstrap methods from the original class are copied as is in the + * new class, which saves time. New constant pool entries and new bootstrap methods will be + * added at the end if necessary, but unused constant pool entries or bootstrap methods + * won't be removed. + *
  • Methods that are not transformed are copied as is in the new class, directly from the + * original class bytecode (i.e. without emitting visit events for all the method + * instructions), which saves a lot of time. Untransformed methods are detected by + * the fact that the {@link ClassReader} receives {@link MethodVisitor} objects that come + * from a {@link ClassWriter} (and not from any other {@link ClassVisitor} instance). + *
+ * + * @param classReader the {@link ClassReader} used to read the original class. It will be used to + * copy the entire constant pool and bootstrap methods from the original class and also to + * copy other fragments of original bytecode where applicable. + * @param flags option flags that can be used to modify the default behavior of this class. Must + * be zero or more of {@link #COMPUTE_MAXS} and {@link #COMPUTE_FRAMES}. These option flags + * do not affect methods that are copied as is in the new class. This means that neither the + * maximum stack size nor the stack frames will be computed for these methods. + */ + @SuppressWarnings("this-escape") + public ClassWriter(final ClassReader classReader, final int flags) { + super(/* latest api = */ Opcodes.ASM9); + this.flags = flags; + symbolTable = classReader == null ? new SymbolTable(this) : new SymbolTable(this, classReader); + if ((flags & COMPUTE_FRAMES) != 0) { + compute = MethodWriter.COMPUTE_ALL_FRAMES; + } else if ((flags & COMPUTE_MAXS) != 0) { + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL; + } else { + compute = MethodWriter.COMPUTE_NOTHING; + } + } + + // ----------------------------------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Returns true if all the given flags were passed to the constructor. + * + * @param flags some option flags. Must be zero or more of {@link #COMPUTE_MAXS} and {@link + * #COMPUTE_FRAMES}. + * @return true if all the given flags, or more, were passed to the constructor. + */ + public boolean hasFlags(final int flags) { + return (this.flags & flags) == flags; + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the ClassVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public final void visit( + final int version, + final int access, + final String name, + final String signature, + final String superName, + final String[] interfaces) { + this.version = version; + this.accessFlags = access; + this.thisClass = symbolTable.setMajorVersionAndClassName(version & 0xFFFF, name); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + this.superClass = superName == null ? 0 : symbolTable.addConstantClass(superName).index; + if (interfaces != null && interfaces.length > 0) { + interfaceCount = interfaces.length; + this.interfaces = new int[interfaceCount]; + for (int i = 0; i < interfaceCount; ++i) { + this.interfaces[i] = symbolTable.addConstantClass(interfaces[i]).index; + } + } + if (compute == MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL && (version & 0xFFFF) >= Opcodes.V1_7) { + compute = MethodWriter.COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES; + } + } + + @Override + public final void visitSource(final String file, final String debug) { + if (file != null) { + sourceFileIndex = symbolTable.addConstantUtf8(file); + } + if (debug != null) { + debugExtension = new ByteVector().encodeUtf8(debug, 0, Integer.MAX_VALUE); + } + } + + @Override + public final ModuleVisitor visitModule( + final String name, final int access, final String version) { + return moduleWriter = + new ModuleWriter( + symbolTable, + symbolTable.addConstantModule(name).index, + access, + version == null ? 0 : symbolTable.addConstantUtf8(version)); + } + + @Override + public final void visitNestHost(final String nestHost) { + nestHostClassIndex = symbolTable.addConstantClass(nestHost).index; + } + + @Override + public final void visitOuterClass( + final String owner, final String name, final String descriptor) { + enclosingClassIndex = symbolTable.addConstantClass(owner).index; + if (name != null && descriptor != null) { + enclosingMethodIndex = symbolTable.addConstantNameAndType(name, descriptor); + } + } + + @Override + public final AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public final AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public final void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public final void visitNestMember(final String nestMember) { + if (nestMemberClasses == null) { + nestMemberClasses = new ByteVector(); + } + ++numberOfNestMemberClasses; + nestMemberClasses.putShort(symbolTable.addConstantClass(nestMember).index); + } + + @Override + public final void visitPermittedSubclass(final String permittedSubclass) { + if (permittedSubclasses == null) { + permittedSubclasses = new ByteVector(); + } + ++numberOfPermittedSubclasses; + permittedSubclasses.putShort(symbolTable.addConstantClass(permittedSubclass).index); + } + + @Override + public final void visitInnerClass( + final String name, final String outerName, final String innerName, final int access) { + if (innerClasses == null) { + innerClasses = new ByteVector(); + } + // Section 4.7.6 of the JVMS states "Every CONSTANT_Class_info entry in the constant_pool table + // which represents a class or interface C that is not a package member must have exactly one + // corresponding entry in the classes array". To avoid duplicates we keep track in the info + // field of the Symbol of each CONSTANT_Class_info entry C whether an inner class entry has + // already been added for C. If so, we store the index of this inner class entry (plus one) in + // the info field. This trick allows duplicate detection in O(1) time. + Symbol nameSymbol = symbolTable.addConstantClass(name); + if (nameSymbol.info == 0) { + ++numberOfInnerClasses; + innerClasses.putShort(nameSymbol.index); + innerClasses.putShort(outerName == null ? 0 : symbolTable.addConstantClass(outerName).index); + innerClasses.putShort(innerName == null ? 0 : symbolTable.addConstantUtf8(innerName)); + innerClasses.putShort(access); + nameSymbol.info = numberOfInnerClasses; + } + // Else, compare the inner classes entry nameSymbol.info - 1 with the arguments of this method + // and throw an exception if there is a difference? + } + + @Override + public final RecordComponentVisitor visitRecordComponent( + final String name, final String descriptor, final String signature) { + RecordComponentWriter recordComponentWriter = + new RecordComponentWriter(symbolTable, name, descriptor, signature); + if (firstRecordComponent == null) { + firstRecordComponent = recordComponentWriter; + } else { + lastRecordComponent.delegate = recordComponentWriter; + } + return lastRecordComponent = recordComponentWriter; + } + + @Override + public final FieldVisitor visitField( + final int access, + final String name, + final String descriptor, + final String signature, + final Object value) { + FieldWriter fieldWriter = + new FieldWriter(symbolTable, access, name, descriptor, signature, value); + if (firstField == null) { + firstField = fieldWriter; + } else { + lastField.fv = fieldWriter; + } + return lastField = fieldWriter; + } + + @Override + public final MethodVisitor visitMethod( + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions) { + MethodWriter methodWriter = + new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute); + if (firstMethod == null) { + firstMethod = methodWriter; + } else { + lastMethod.mv = methodWriter; + } + return lastMethod = methodWriter; + } + + @Override + public final void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Other public methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the content of the class file that was built by this ClassWriter. + * + * @return the binary content of the JVMS ClassFile structure that was built by this ClassWriter. + * @throws ClassTooLargeException if the constant pool of the class is too large. + * @throws MethodTooLargeException if the Code attribute of a method is too large. + */ + public byte[] toByteArray() { + // First step: compute the size in bytes of the ClassFile structure. + // The magic field uses 4 bytes, 10 mandatory fields (minor_version, major_version, + // constant_pool_count, access_flags, this_class, super_class, interfaces_count, fields_count, + // methods_count and attributes_count) use 2 bytes each, and each interface uses 2 bytes too. + int size = 24 + 2 * interfaceCount; + int fieldsCount = 0; + FieldWriter fieldWriter = firstField; + while (fieldWriter != null) { + ++fieldsCount; + size += fieldWriter.computeFieldInfoSize(); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + int methodsCount = 0; + MethodWriter methodWriter = firstMethod; + while (methodWriter != null) { + ++methodsCount; + size += methodWriter.computeMethodInfoSize(); + methodWriter = (MethodWriter) methodWriter.mv; + } + + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (innerClasses != null) { + ++attributesCount; + size += 8 + innerClasses.length; + symbolTable.addConstantUtf8(Constants.INNER_CLASSES); + } + if (enclosingClassIndex != 0) { + ++attributesCount; + size += 10; + symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { + ++attributesCount; + size += 6; + symbolTable.addConstantUtf8(Constants.SYNTHETIC); + } + if (signatureIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.SIGNATURE); + } + if (sourceFileIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.SOURCE_FILE); + } + if (debugExtension != null) { + ++attributesCount; + size += 6 + debugExtension.length; + symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributesCount; + size += 6; + symbolTable.addConstantUtf8(Constants.DEPRECATED); + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + size += + lastRuntimeVisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_ANNOTATIONS); + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + size += + lastRuntimeInvisibleAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_ANNOTATIONS); + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + size += + lastRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + size += + lastRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (symbolTable.computeBootstrapMethodsSize() > 0) { + ++attributesCount; + size += symbolTable.computeBootstrapMethodsSize(); + } + if (moduleWriter != null) { + attributesCount += moduleWriter.getAttributeCount(); + size += moduleWriter.computeAttributesSize(); + } + if (nestHostClassIndex != 0) { + ++attributesCount; + size += 8; + symbolTable.addConstantUtf8(Constants.NEST_HOST); + } + if (nestMemberClasses != null) { + ++attributesCount; + size += 8 + nestMemberClasses.length; + symbolTable.addConstantUtf8(Constants.NEST_MEMBERS); + } + if (permittedSubclasses != null) { + ++attributesCount; + size += 8 + permittedSubclasses.length; + symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES); + } + int recordComponentCount = 0; + int recordSize = 0; + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + ++recordComponentCount; + recordSize += recordComponentWriter.computeRecordComponentInfoSize(); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + ++attributesCount; + size += 8 + recordSize; + symbolTable.addConstantUtf8(Constants.RECORD); + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + size += firstAttribute.computeAttributesSize(symbolTable); + } + // IMPORTANT: this must be the last part of the ClassFile size computation, because the previous + // statements can add attribute names to the constant pool, thereby changing its size! + size += symbolTable.getConstantPoolLength(); + int constantPoolCount = symbolTable.getConstantPoolCount(); + if (constantPoolCount > 0xFFFF) { + throw new ClassTooLargeException(symbolTable.getClassName(), constantPoolCount); + } + + // Second step: allocate a ByteVector of the correct size (in order to avoid any array copy in + // dynamic resizes) and fill it with the ClassFile content. + ByteVector result = new ByteVector(size); + result.putInt(0xCAFEBABE).putInt(version); + symbolTable.putConstantPool(result); + int mask = (version & 0xFFFF) < Opcodes.V1_5 ? Opcodes.ACC_SYNTHETIC : 0; + result.putShort(accessFlags & ~mask).putShort(thisClass).putShort(superClass); + result.putShort(interfaceCount); + for (int i = 0; i < interfaceCount; ++i) { + result.putShort(interfaces[i]); + } + result.putShort(fieldsCount); + fieldWriter = firstField; + while (fieldWriter != null) { + fieldWriter.putFieldInfo(result); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + result.putShort(methodsCount); + boolean hasFrames = false; + boolean hasAsmInstructions = false; + methodWriter = firstMethod; + while (methodWriter != null) { + hasFrames |= methodWriter.hasFrames(); + hasAsmInstructions |= methodWriter.hasAsmInstructions(); + methodWriter.putMethodInfo(result); + methodWriter = (MethodWriter) methodWriter.mv; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + result.putShort(attributesCount); + if (innerClasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.INNER_CLASSES)) + .putInt(innerClasses.length + 2) + .putShort(numberOfInnerClasses) + .putByteArray(innerClasses.data, 0, innerClasses.length); + } + if (enclosingClassIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.ENCLOSING_METHOD)) + .putInt(4) + .putShort(enclosingClassIndex) + .putShort(enclosingMethodIndex); + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xFFFF) < Opcodes.V1_5) { + result.putShort(symbolTable.addConstantUtf8(Constants.SYNTHETIC)).putInt(0); + } + if (signatureIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.SIGNATURE)) + .putInt(2) + .putShort(signatureIndex); + } + if (sourceFileIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_FILE)) + .putInt(2) + .putShort(sourceFileIndex); + } + if (debugExtension != null) { + int length = debugExtension.length; + result + .putShort(symbolTable.addConstantUtf8(Constants.SOURCE_DEBUG_EXTENSION)) + .putInt(length) + .putByteArray(debugExtension.data, 0, length); + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + result.putShort(symbolTable.addConstantUtf8(Constants.DEPRECATED)).putInt(0); + } + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + result); + symbolTable.putBootstrapMethods(result); + if (moduleWriter != null) { + moduleWriter.putAttributes(result); + } + if (nestHostClassIndex != 0) { + result + .putShort(symbolTable.addConstantUtf8(Constants.NEST_HOST)) + .putInt(2) + .putShort(nestHostClassIndex); + } + if (nestMemberClasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.NEST_MEMBERS)) + .putInt(nestMemberClasses.length + 2) + .putShort(numberOfNestMemberClasses) + .putByteArray(nestMemberClasses.data, 0, nestMemberClasses.length); + } + if (permittedSubclasses != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.PERMITTED_SUBCLASSES)) + .putInt(permittedSubclasses.length + 2) + .putShort(numberOfPermittedSubclasses) + .putByteArray(permittedSubclasses.data, 0, permittedSubclasses.length); + } + if ((accessFlags & Opcodes.ACC_RECORD) != 0 || firstRecordComponent != null) { + result + .putShort(symbolTable.addConstantUtf8(Constants.RECORD)) + .putInt(recordSize + 2) + .putShort(recordComponentCount); + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.putRecordComponentInfo(result); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, result); + } + + // Third step: replace the ASM specific instructions, if any. + if (hasAsmInstructions) { + return replaceAsmInstructions(result.data, hasFrames); + } else { + return result.data; + } + } + + /** + * Returns the equivalent of the given class file, with the ASM specific instructions replaced + * with standard ones. This is done with a ClassReader -> ClassWriter round trip. + * + * @param classFile a class file containing ASM specific instructions, generated by this + * ClassWriter. + * @param hasFrames whether there is at least one stack map frames in 'classFile'. + * @return an equivalent of 'classFile', with the ASM specific instructions replaced with standard + * ones. + */ + private byte[] replaceAsmInstructions(final byte[] classFile, final boolean hasFrames) { + final Attribute[] attributes = getAttributePrototypes(); + firstField = null; + lastField = null; + firstMethod = null; + lastMethod = null; + lastRuntimeVisibleAnnotation = null; + lastRuntimeInvisibleAnnotation = null; + lastRuntimeVisibleTypeAnnotation = null; + lastRuntimeInvisibleTypeAnnotation = null; + moduleWriter = null; + nestHostClassIndex = 0; + numberOfNestMemberClasses = 0; + nestMemberClasses = null; + numberOfPermittedSubclasses = 0; + permittedSubclasses = null; + firstRecordComponent = null; + lastRecordComponent = null; + firstAttribute = null; + compute = hasFrames ? MethodWriter.COMPUTE_INSERTED_FRAMES : MethodWriter.COMPUTE_NOTHING; + new ClassReader(classFile, 0, /* checkClassVersion = */ false) + .accept( + this, + attributes, + (hasFrames ? ClassReader.EXPAND_FRAMES : 0) | ClassReader.EXPAND_ASM_INSNS); + return toByteArray(); + } + + /** + * Returns the prototypes of the attributes used by this class, its fields and its methods. + * + * @return the prototypes of the attributes used by this class, its fields and its methods. + */ + private Attribute[] getAttributePrototypes() { + Attribute.Set attributePrototypes = new Attribute.Set(); + attributePrototypes.addAttributes(firstAttribute); + FieldWriter fieldWriter = firstField; + while (fieldWriter != null) { + fieldWriter.collectAttributePrototypes(attributePrototypes); + fieldWriter = (FieldWriter) fieldWriter.fv; + } + MethodWriter methodWriter = firstMethod; + while (methodWriter != null) { + methodWriter.collectAttributePrototypes(attributePrototypes); + methodWriter = (MethodWriter) methodWriter.mv; + } + RecordComponentWriter recordComponentWriter = firstRecordComponent; + while (recordComponentWriter != null) { + recordComponentWriter.collectAttributePrototypes(attributePrototypes); + recordComponentWriter = (RecordComponentWriter) recordComponentWriter.delegate; + } + return attributePrototypes.toArray(); + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: constant pool management for Attribute sub classes + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a number or string constant to the constant pool of the class being build. Does nothing if + * the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param value the value of the constant to be added to the constant pool. This parameter must be + * an {@link Integer}, a {@link Float}, a {@link Long}, a {@link Double} or a {@link String}. + * @return the index of a new or already existing constant item with the given value. + */ + public int newConst(final Object value) { + return symbolTable.addConstant(value).index; + } + + /** + * Adds an UTF8 string to the constant pool of the class being build. Does nothing if the constant + * pool already contains a similar item. This method is intended for {@link Attribute} sub + * classes, and is normally not needed by class generators or adapters. + * + * @param value the String value. + * @return the index of a new or already existing UTF8 item. + */ + // DontCheck(AbbreviationAsWordInName): can't be renamed (for backward binary compatibility). + public int newUTF8(final String value) { + return symbolTable.addConstantUtf8(value); + } + + /** + * Adds a class reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param value the internal name of the class (see {@link Type#getInternalName()}). + * @return the index of a new or already existing class reference item. + */ + public int newClass(final String value) { + return symbolTable.addConstantClass(value).index; + } + + /** + * Adds a method type reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param methodDescriptor method descriptor of the method type. + * @return the index of a new or already existing method type reference item. + */ + public int newMethodType(final String methodDescriptor) { + return symbolTable.addConstantMethodType(methodDescriptor).index; + } + + /** + * Adds a module reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param moduleName name of the module. + * @return the index of a new or already existing module reference item. + */ + public int newModule(final String moduleName) { + return symbolTable.addConstantModule(moduleName).index; + } + + /** + * Adds a package reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param packageName name of the package in its internal form. + * @return the index of a new or already existing module reference item. + */ + public int newPackage(final String packageName) { + return symbolTable.addConstantPackage(packageName).index; + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool + * already contains a similar item. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link + * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class (see {@link + * Type#getInternalName()}). + * @param name the name of the field or method. + * @param descriptor the descriptor of the field or method. + * @return the index of a new or already existing method type reference item. + * @deprecated this method is superseded by {@link #newHandle(int, String, String, String, + * boolean)}. + */ + @Deprecated + public int newHandle( + final int tag, final String owner, final String name, final String descriptor) { + return newHandle(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Adds a handle to the constant pool of the class being build. Does nothing if the constant pool + * already contains a similar item. This method is intended for {@link Attribute} sub classes, + * and is normally not needed by class generators or adapters. + * + * @param tag the kind of this handle. Must be {@link Opcodes#H_GETFIELD}, {@link + * Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the field or method owner class (see {@link + * Type#getInternalName()}). + * @param name the name of the field or method. + * @param descriptor the descriptor of the field or method. + * @param isInterface true if the owner is an interface. + * @return the index of a new or already existing method type reference item. + */ + public int newHandle( + final int tag, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + return symbolTable.addConstantMethodHandle(tag, owner, name, descriptor, isInterface).index; + } + + /** + * Adds a dynamic constant reference to the constant pool of the class being build. Does nothing + * if the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param name name of the invoked method. + * @param descriptor field descriptor of the constant type. + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. + * @return the index of a new or already existing dynamic constant reference item. + */ + public int newConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + return symbolTable.addConstantDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) + .index; + } + + /** + * Adds an invokedynamic reference to the constant pool of the class being build. Does nothing if + * the constant pool already contains a similar item. This method is intended for {@link + * Attribute} sub classes, and is normally not needed by class generators or adapters. + * + * @param name name of the invoked method. + * @param descriptor descriptor of the invoke method. + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. + * @return the index of a new or already existing invokedynamic reference item. + */ + public int newInvokeDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + return symbolTable.addConstantInvokeDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments) + .index; + } + + /** + * Adds a field reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}). + * @param name the field's name. + * @param descriptor the field's descriptor. + * @return the index of a new or already existing field reference item. + */ + public int newField(final String owner, final String name, final String descriptor) { + return symbolTable.addConstantFieldref(owner, name, descriptor).index; + } + + /** + * Adds a method reference to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor. + * @param isInterface {@literal true} if {@code owner} is an interface. + * @return the index of a new or already existing method reference item. + */ + public int newMethod( + final String owner, final String name, final String descriptor, final boolean isInterface) { + return symbolTable.addConstantMethodref(owner, name, descriptor, isInterface).index; + } + + /** + * Adds a name and type to the constant pool of the class being build. Does nothing if the + * constant pool already contains a similar item. This method is intended for {@link Attribute} + * sub classes, and is normally not needed by class generators or adapters. + * + * @param name a name. + * @param descriptor a type descriptor. + * @return the index of a new or already existing name and type item. + */ + public int newNameType(final String name, final String descriptor) { + return symbolTable.addConstantNameAndType(name, descriptor); + } + + // ----------------------------------------------------------------------------------------------- + // Default method to compute common super classes when computing stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the common super type of the two given types. The default implementation of this method + * loads the two given classes and uses the java.lang.Class methods to find the common + * super class. It can be overridden to compute this common super type in other ways, in + * particular without actually loading any class, or to take into account the class that is + * currently being generated by this ClassWriter, which can of course not be loaded since it is + * under construction. + * + * @param type1 the internal name of a class (see {@link Type#getInternalName()}). + * @param type2 the internal name of another class (see {@link Type#getInternalName()}). + * @return the internal name of the common super class of the two given classes (see {@link + * Type#getInternalName()}). + */ + protected String getCommonSuperClass(final String type1, final String type2) { + ClassLoader classLoader = getClassLoader(); + Class class1; + try { + class1 = Class.forName(type1.replace('/', '.'), false, classLoader); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(type1, e); + } + Class class2; + try { + class2 = Class.forName(type2.replace('/', '.'), false, classLoader); + } catch (ClassNotFoundException e) { + throw new TypeNotPresentException(type2, e); + } + if (class1.isAssignableFrom(class2)) { + return type1; + } + if (class2.isAssignableFrom(class1)) { + return type2; + } + if (class1.isInterface() || class2.isInterface()) { + return "java/lang/Object"; + } else { + do { + class1 = class1.getSuperclass(); + } while (!class1.isAssignableFrom(class2)); + return class1.getName().replace('.', '/'); + } + } + + /** + * Returns the {@link ClassLoader} to be used by the default implementation of {@link + * #getCommonSuperClass(String, String)}, that of this {@link ClassWriter}'s runtime type by + * default. + * + * @return ClassLoader + */ + protected ClassLoader getClassLoader() { + return getClass().getClassLoader(); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ConstantDynamic.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ConstantDynamic.class new file mode 100644 index 00000000..e0f789a8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ConstantDynamic.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ConstantDynamic.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ConstantDynamic.java new file mode 100644 index 00000000..cb992f83 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ConstantDynamic.java @@ -0,0 +1,210 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +import java.util.Arrays; + +/** + * A constant whose value is computed at runtime, with a bootstrap method. + * + * @author Remi Forax + */ +public final class ConstantDynamic { + + /** The constant name (can be arbitrary). */ + private final String name; + + /** The constant type (must be a field descriptor). */ + private final String descriptor; + + /** The bootstrap method to use to compute the constant value at runtime. */ + private final Handle bootstrapMethod; + + /** + * The arguments to pass to the bootstrap method, in order to compute the constant value at + * runtime. + */ + private final Object[] bootstrapMethodArguments; + + /** + * Constructs a new {@link ConstantDynamic}. + * + * @param name the constant name (can be arbitrary). + * @param descriptor the constant type (must be a field descriptor). + * @param bootstrapMethod the bootstrap method to use to compute the constant value at runtime. + * @param bootstrapMethodArguments the arguments to pass to the bootstrap method, in order to + * compute the constant value at runtime. + */ + public ConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethod, + final Object... bootstrapMethodArguments) { + this.name = name; + this.descriptor = descriptor; + this.bootstrapMethod = bootstrapMethod; + this.bootstrapMethodArguments = bootstrapMethodArguments; + } + + /** + * Returns the name of this constant. + * + * @return the name of this constant. + */ + public String getName() { + return name; + } + + /** + * Returns the type of this constant. + * + * @return the type of this constant, as a field descriptor. + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Returns the bootstrap method used to compute the value of this constant. + * + * @return the bootstrap method used to compute the value of this constant. + */ + public Handle getBootstrapMethod() { + return bootstrapMethod; + } + + /** + * Returns the number of arguments passed to the bootstrap method, in order to compute the value + * of this constant. + * + * @return the number of arguments passed to the bootstrap method, in order to compute the value + * of this constant. + */ + public int getBootstrapMethodArgumentCount() { + return bootstrapMethodArguments.length; + } + + /** + * Returns an argument passed to the bootstrap method, in order to compute the value of this + * constant. + * + * @param index an argument index, between 0 and {@link #getBootstrapMethodArgumentCount()} + * (exclusive). + * @return the argument passed to the bootstrap method, with the given index. + */ + public Object getBootstrapMethodArgument(final int index) { + return bootstrapMethodArguments[index]; + } + + /** + * Returns the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. WARNING: this array must not be modified, and must not be returned to the user. + * + * @return the arguments to pass to the bootstrap method, in order to compute the value of this + * constant. + */ + Object[] getBootstrapMethodArgumentsUnsafe() { + return bootstrapMethodArguments; + } + + /** + * Returns the size of this constant. + * + * @return the size of this constant, i.e., 2 for {@code long} and {@code double}, 1 otherwise. + */ + public int getSize() { + char firstCharOfDescriptor = descriptor.charAt(0); + return (firstCharOfDescriptor == 'J' || firstCharOfDescriptor == 'D') ? 2 : 1; + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (!(object instanceof ConstantDynamic)) { + return false; + } + ConstantDynamic constantDynamic = (ConstantDynamic) object; + return name.equals(constantDynamic.name) + && descriptor.equals(constantDynamic.descriptor) + && bootstrapMethod.equals(constantDynamic.bootstrapMethod) + && Arrays.equals(bootstrapMethodArguments, constantDynamic.bootstrapMethodArguments); + } + + @Override + public int hashCode() { + return name.hashCode() + ^ Integer.rotateLeft(descriptor.hashCode(), 8) + ^ Integer.rotateLeft(bootstrapMethod.hashCode(), 16) + ^ Integer.rotateLeft(Arrays.hashCode(bootstrapMethodArguments), 24); + } + + @Override + public String toString() { + return name + + " : " + + descriptor + + ' ' + + bootstrapMethod + + ' ' + + Arrays.toString(bootstrapMethodArguments); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Constants.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Constants.class new file mode 100644 index 00000000..eb33346d Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Constants.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Constants.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Constants.java new file mode 100644 index 00000000..0fc081fe --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Constants.java @@ -0,0 +1,253 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Pattern; + +/** + * Defines additional JVM opcodes, access flags and constants which are not part of the ASM public + * API. + * + * @see JVMS 6 + * @author Eric Bruneton + */ +final class Constants { + + // The ClassFile attribute names, in the order they are defined in + // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.7-300. + + static final String CONSTANT_VALUE = "ConstantValue"; + static final String CODE = "Code"; + static final String STACK_MAP_TABLE = "StackMapTable"; + static final String EXCEPTIONS = "Exceptions"; + static final String INNER_CLASSES = "InnerClasses"; + static final String ENCLOSING_METHOD = "EnclosingMethod"; + static final String SYNTHETIC = "Synthetic"; + static final String SIGNATURE = "Signature"; + static final String SOURCE_FILE = "SourceFile"; + static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension"; + static final String LINE_NUMBER_TABLE = "LineNumberTable"; + static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable"; + static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; + static final String DEPRECATED = "Deprecated"; + static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; + static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; + static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = "RuntimeVisibleParameterAnnotations"; + static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = + "RuntimeInvisibleParameterAnnotations"; + static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations"; + static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = "RuntimeInvisibleTypeAnnotations"; + static final String ANNOTATION_DEFAULT = "AnnotationDefault"; + static final String BOOTSTRAP_METHODS = "BootstrapMethods"; + static final String METHOD_PARAMETERS = "MethodParameters"; + static final String MODULE = "Module"; + static final String MODULE_PACKAGES = "ModulePackages"; + static final String MODULE_MAIN_CLASS = "ModuleMainClass"; + static final String NEST_HOST = "NestHost"; + static final String NEST_MEMBERS = "NestMembers"; + static final String PERMITTED_SUBCLASSES = "PermittedSubclasses"; + static final String RECORD = "Record"; + + // ASM specific access flags. + // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard + // access flags, and also to make sure that these flags are automatically filtered out when + // written in class files (because access flags are stored using 16 bits only). + + static final int ACC_CONSTRUCTOR = 0x40000; // method access flag. + + // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}. + + /** + * A frame inserted between already existing frames. This internal stack map frame type (in + * addition to the ones declared in {@link Opcodes}) can only be used if the frame content can be + * computed from the previous existing frame and from the instructions between this existing frame + * and the inserted one, without any knowledge of the type hierarchy. This kind of frame is only + * used when an unconditional jump is inserted in a method while expanding an ASM specific + * instruction. Keep in sync with Opcodes.java. + */ + static final int F_INSERT = 256; + + // The JVM opcode values which are not part of the ASM public API. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html. + + static final int LDC_W = 19; + static final int LDC2_W = 20; + static final int ILOAD_0 = 26; + static final int ILOAD_1 = 27; + static final int ILOAD_2 = 28; + static final int ILOAD_3 = 29; + static final int LLOAD_0 = 30; + static final int LLOAD_1 = 31; + static final int LLOAD_2 = 32; + static final int LLOAD_3 = 33; + static final int FLOAD_0 = 34; + static final int FLOAD_1 = 35; + static final int FLOAD_2 = 36; + static final int FLOAD_3 = 37; + static final int DLOAD_0 = 38; + static final int DLOAD_1 = 39; + static final int DLOAD_2 = 40; + static final int DLOAD_3 = 41; + static final int ALOAD_0 = 42; + static final int ALOAD_1 = 43; + static final int ALOAD_2 = 44; + static final int ALOAD_3 = 45; + static final int ISTORE_0 = 59; + static final int ISTORE_1 = 60; + static final int ISTORE_2 = 61; + static final int ISTORE_3 = 62; + static final int LSTORE_0 = 63; + static final int LSTORE_1 = 64; + static final int LSTORE_2 = 65; + static final int LSTORE_3 = 66; + static final int FSTORE_0 = 67; + static final int FSTORE_1 = 68; + static final int FSTORE_2 = 69; + static final int FSTORE_3 = 70; + static final int DSTORE_0 = 71; + static final int DSTORE_1 = 72; + static final int DSTORE_2 = 73; + static final int DSTORE_3 = 74; + static final int ASTORE_0 = 75; + static final int ASTORE_1 = 76; + static final int ASTORE_2 = 77; + static final int ASTORE_3 = 78; + static final int WIDE = 196; + static final int GOTO_W = 200; + static final int JSR_W = 201; + + // Constants to convert between normal and wide jump instructions. + + // The delta between the GOTO_W and JSR_W opcodes and GOTO and JUMP. + static final int WIDE_JUMP_OPCODE_DELTA = GOTO_W - Opcodes.GOTO; + + // Constants to convert JVM opcodes to the equivalent ASM specific opcodes, and vice versa. + + // The delta between the ASM_IFEQ, ..., ASM_IF_ACMPNE, ASM_GOTO and ASM_JSR opcodes + // and IFEQ, ..., IF_ACMPNE, GOTO and JSR. + static final int ASM_OPCODE_DELTA = 49; + + // The delta between the ASM_IFNULL and ASM_IFNONNULL opcodes and IFNULL and IFNONNULL. + static final int ASM_IFNULL_OPCODE_DELTA = 20; + + // ASM specific opcodes, used for long forward jump instructions. + + static final int ASM_IFEQ = Opcodes.IFEQ + ASM_OPCODE_DELTA; + static final int ASM_IFNE = Opcodes.IFNE + ASM_OPCODE_DELTA; + static final int ASM_IFLT = Opcodes.IFLT + ASM_OPCODE_DELTA; + static final int ASM_IFGE = Opcodes.IFGE + ASM_OPCODE_DELTA; + static final int ASM_IFGT = Opcodes.IFGT + ASM_OPCODE_DELTA; + static final int ASM_IFLE = Opcodes.IFLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPEQ = Opcodes.IF_ICMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPNE = Opcodes.IF_ICMPNE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLT = Opcodes.IF_ICMPLT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGE = Opcodes.IF_ICMPGE + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPGT = Opcodes.IF_ICMPGT + ASM_OPCODE_DELTA; + static final int ASM_IF_ICMPLE = Opcodes.IF_ICMPLE + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPEQ = Opcodes.IF_ACMPEQ + ASM_OPCODE_DELTA; + static final int ASM_IF_ACMPNE = Opcodes.IF_ACMPNE + ASM_OPCODE_DELTA; + static final int ASM_GOTO = Opcodes.GOTO + ASM_OPCODE_DELTA; + static final int ASM_JSR = Opcodes.JSR + ASM_OPCODE_DELTA; + static final int ASM_IFNULL = Opcodes.IFNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_IFNONNULL = Opcodes.IFNONNULL + ASM_IFNULL_OPCODE_DELTA; + static final int ASM_GOTO_W = 220; + + private Constants() {} + + static void checkAsmExperimental(final Object caller) { + Class callerClass = caller.getClass(); + String internalName = callerClass.getName().replace('.', '/'); + if (!isWhitelisted(internalName)) { + checkIsPreview(callerClass.getClassLoader().getResourceAsStream(internalName + ".class")); + } + } + + static boolean isWhitelisted(final String internalName) { + if (!internalName.startsWith("jdk/internal/org/objectweb/asm/")) { + return false; + } + String member = "(Annotation|Class|Field|Method|Module|RecordComponent|Signature)"; + return internalName.contains("Test$") + || Pattern.matches( + "jdk/internal/org/objectweb/asm/util/Trace" + member + "Visitor(\\$.*)?", internalName) + || Pattern.matches( + "jdk/internal/org/objectweb/asm/util/Check" + member + "Adapter(\\$.*)?", internalName); + } + + static void checkIsPreview(final InputStream classInputStream) { + if (classInputStream == null) { + throw new IllegalStateException("Bytecode not available, can't check class version"); + } + int minorVersion; + try (DataInputStream callerClassStream = new DataInputStream(classInputStream); ) { + callerClassStream.readInt(); + minorVersion = callerClassStream.readUnsignedShort(); + } catch (IOException ioe) { + throw new IllegalStateException("I/O error, can't check class version", ioe); + } + if (minorVersion != 0xFFFF) { + throw new IllegalStateException( + "ASM9_EXPERIMENTAL can only be used by classes compiled with --enable-preview"); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Context.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Context.class new file mode 100644 index 00000000..c2754c3b Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Context.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Context.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Context.java new file mode 100644 index 00000000..bec67f53 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Context.java @@ -0,0 +1,168 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * Information about a class being parsed in a {@link ClassReader}. + * + * @author Eric Bruneton + */ +final class Context { + + /** The prototypes of the attributes that must be parsed in this class. */ + Attribute[] attributePrototypes; + + /** + * The options used to parse this class. One or more of {@link ClassReader#SKIP_CODE}, {@link + * ClassReader#SKIP_DEBUG}, {@link ClassReader#SKIP_FRAMES}, {@link ClassReader#EXPAND_FRAMES} or + * {@link ClassReader#EXPAND_ASM_INSNS}. + */ + int parsingOptions; + + /** The buffer used to read strings in the constant pool. */ + char[] charBuffer; + + // Information about the current method, i.e. the one read in the current (or latest) call + // to {@link ClassReader#readMethod()}. + + /** The access flags of the current method. */ + int currentMethodAccessFlags; + + /** The name of the current method. */ + String currentMethodName; + + /** The descriptor of the current method. */ + String currentMethodDescriptor; + + /** + * The labels of the current method, indexed by bytecode offset (only bytecode offsets for which a + * label is needed have a non null associated Label). + */ + Label[] currentMethodLabels; + + // Information about the current type annotation target, i.e. the one read in the current + // (or latest) call to {@link ClassReader#readAnnotationTarget()}. + + /** + * The target_type and target_info of the current type annotation target, encoded as described in + * {@link TypeReference}. + */ + int currentTypeAnnotationTarget; + + /** The target_path of the current type annotation target. */ + TypePath currentTypeAnnotationTargetPath; + + /** The start of each local variable range in the current local variable annotation. */ + Label[] currentLocalVariableAnnotationRangeStarts; + + /** The end of each local variable range in the current local variable annotation. */ + Label[] currentLocalVariableAnnotationRangeEnds; + + /** + * The local variable index of each local variable range in the current local variable annotation. + */ + int[] currentLocalVariableAnnotationRangeIndices; + + // Information about the current stack map frame, i.e. the one read in the current (or latest) + // call to {@link ClassReader#readFrame()}. + + /** The bytecode offset of the current stack map frame. */ + int currentFrameOffset; + + /** + * The type of the current stack map frame. One of {@link Opcodes#F_FULL}, {@link + * Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or {@link Opcodes#F_SAME1}. + */ + int currentFrameType; + + /** + * The number of local variable types in the current stack map frame. Each type is represented + * with a single array element (even long and double). + */ + int currentFrameLocalCount; + + /** + * The delta number of local variable types in the current stack map frame (each type is + * represented with a single array element - even long and double). This is the number of local + * variable types in this frame, minus the number of local variable types in the previous frame. + */ + int currentFrameLocalCountDelta; + + /** + * The types of the local variables in the current stack map frame. Each type is represented with + * a single array element (even long and double), using the format described in {@link + * MethodVisitor#visitFrame}. Depending on {@link #currentFrameType}, this contains the types of + * all the local variables, or only those of the additional ones (compared to the previous frame). + */ + Object[] currentFrameLocalTypes; + + /** + * The number stack element types in the current stack map frame. Each type is represented with a + * single array element (even long and double). + */ + int currentFrameStackCount; + + /** + * The types of the stack elements in the current stack map frame. Each type is represented with a + * single array element (even long and double), using the format described in {@link + * MethodVisitor#visitFrame}. + */ + Object[] currentFrameStackTypes; +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/CurrentFrame.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/CurrentFrame.class new file mode 100644 index 00000000..4a875802 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/CurrentFrame.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/CurrentFrame.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/CurrentFrame.java new file mode 100644 index 00000000..580b9a95 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/CurrentFrame.java @@ -0,0 +1,87 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * Information about the input stack map frame at the "current" instruction of a method. This is + * implemented as a Frame subclass for a "basic block" containing only one instruction. + * + * @author Eric Bruneton + */ +final class CurrentFrame extends Frame { + + CurrentFrame(final Label owner) { + super(owner); + } + + /** + * Sets this CurrentFrame to the input stack map frame of the next "current" instruction, i.e. the + * instruction just after the given one. It is assumed that the value of this object when this + * method is called is the stack map frame status just before the given instruction is executed. + */ + @Override + void execute( + final int opcode, final int arg, final Symbol symbolArg, final SymbolTable symbolTable) { + super.execute(opcode, arg, symbolArg, symbolTable); + Frame successor = new Frame(null); + merge(symbolTable, successor, 0); + copyFrom(successor); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Edge.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Edge.class new file mode 100644 index 00000000..449ab00e Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Edge.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Edge.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Edge.java new file mode 100644 index 00000000..59a7ee83 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Edge.java @@ -0,0 +1,123 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * An edge in the control flow graph of a method. Each node of this graph is a basic block, + * represented with the Label corresponding to its first instruction. Each edge goes from one node + * to another, i.e. from one basic block to another (called the predecessor and successor blocks, + * respectively). An edge corresponds either to a jump or ret instruction or to an exception + * handler. + * + * @see Label + * @author Eric Bruneton + */ +final class Edge { + + /** + * A control flow graph edge corresponding to a jump or ret instruction. Only used with {@link + * ClassWriter#COMPUTE_FRAMES}. + */ + static final int JUMP = 0; + + /** + * A control flow graph edge corresponding to an exception handler. Only used with {@link + * ClassWriter#COMPUTE_MAXS}. + */ + static final int EXCEPTION = 0x7FFFFFFF; + + /** + * Information about this control flow graph edge. + * + *
    + *
  • If {@link ClassWriter#COMPUTE_MAXS} is used, this field contains either a stack size + * delta (for an edge corresponding to a jump instruction), or the value EXCEPTION (for an + * edge corresponding to an exception handler). The stack size delta is the stack size just + * after the jump instruction, minus the stack size at the beginning of the predecessor + * basic block, i.e. the one containing the jump instruction. + *
  • If {@link ClassWriter#COMPUTE_FRAMES} is used, this field contains either the value JUMP + * (for an edge corresponding to a jump instruction), or the index, in the {@link + * ClassWriter} type table, of the exception type that is handled (for an edge corresponding + * to an exception handler). + *
+ */ + final int info; + + /** The successor block of this control flow graph edge. */ + final Label successor; + + /** + * The next edge in the list of outgoing edges of a basic block. See {@link Label#outgoingEdges}. + */ + Edge nextEdge; + + /** + * Constructs a new Edge. + * + * @param info see {@link #info}. + * @param successor see {@link #successor}. + * @param nextEdge see {@link #nextEdge}. + */ + Edge(final int info, final Label successor, final Edge nextEdge) { + this.info = info; + this.successor = successor; + this.nextEdge = nextEdge; + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldVisitor.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldVisitor.class new file mode 100644 index 00000000..b06e5c17 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldVisitor.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldVisitor.java new file mode 100644 index 00000000..7d29f3d4 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldVisitor.java @@ -0,0 +1,179 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A visitor to visit a Java field. The methods of this class must be called in the following order: + * ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code + * visitEnd}. + * + * @author Eric Bruneton + */ +public abstract class FieldVisitor { + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. + */ + protected final int api; + + /** The field visitor to which this visitor must delegate method calls. May be {@literal null}. */ + protected FieldVisitor fv; + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + */ + protected FieldVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link FieldVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + * @param fieldVisitor the field visitor to which this visitor must delegate method calls. May be + * null. + */ + protected FieldVisitor(final int api, final FieldVisitor fieldVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + this.fv = fieldVisitor; + } + + /** + * The field visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the field visitor to which this visitor must delegate method calls, or {@literal null}. + */ + public FieldVisitor getDelegate() { + return fv; + } + + /** + * Visits an annotation of the field. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (fv != null) { + return fv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on the type of the field. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#FIELD}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException("This feature requires ASM5"); + } + if (fv != null) { + return fv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the field. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (fv != null) { + fv.visitAttribute(attribute); + } + } + + /** + * Visits the end of the field. This method, which is the last one to be called, is used to inform + * the visitor that all the annotations and attributes of the field have been visited. + */ + public void visitEnd() { + if (fv != null) { + fv.visitEnd(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldWriter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldWriter.class new file mode 100644 index 00000000..3076050d Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldWriter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldWriter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldWriter.java new file mode 100644 index 00000000..c3e78d04 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/FieldWriter.java @@ -0,0 +1,316 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A {@link FieldVisitor} that generates a corresponding 'field_info' structure, as defined in the + * Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.5 + * @author Eric Bruneton + */ +final class FieldWriter extends FieldVisitor { + + /** Where the constants used in this FieldWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the field_info structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The access_flags field of the field_info JVMS structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private final int accessFlags; + + /** The name_index field of the field_info JVMS structure. */ + private final int nameIndex; + + /** The descriptor_index field of the field_info JVMS structure. */ + private final int descriptorIndex; + + /** + * The signature_index field of the Signature attribute of this field_info, or 0 if there is no + * Signature attribute. + */ + private int signatureIndex; + + /** + * The constantvalue_index field of the ConstantValue attribute of this field_info, or 0 if there + * is no ConstantValue attribute. + */ + private int constantValueIndex; + + /** + * The last runtime visible annotation of this field. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this field. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this field. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this field. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of this field. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putFieldInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link FieldWriter}. + * + * @param symbolTable where the constants used in this FieldWriter must be stored. + * @param access the field's access flags (see {@link Opcodes}). + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + * @param signature the field's signature. May be {@literal null}. + * @param constantValue the field's constant value. May be {@literal null}. + */ + FieldWriter( + final SymbolTable symbolTable, + final int access, + final String name, + final String descriptor, + final String signature, + final Object constantValue) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.accessFlags = access; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + if (constantValue != null) { + this.constantValueIndex = symbolTable.addConstant(constantValue).index; + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the FieldVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of the field_info JVMS structure generated by this FieldWriter. Also adds the + * names of the attributes of this field in the constant pool. + * + * @return the size in bytes of the field_info JVMS structure. + */ + int computeFieldInfoSize() { + // The access_flags, name_index, descriptor_index and attributes_count fields use 8 bytes. + int size = 8; + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (constantValueIndex != 0) { + // ConstantValue attributes always use 8 bytes. + symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE); + size += 8; + } + size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the field_info JVMS structure generated by this FieldWriter into the given + * ByteVector. + * + * @param output where the field_info structure must be put. + */ + void putFieldInfo(final ByteVector output) { + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + // Put the access_flags, name_index and descriptor_index fields. + int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0; + output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); + // Compute and put the attributes_count field. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (constantValueIndex != 0) { + ++attributesCount; + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + ++attributesCount; + } + if (signatureIndex != 0) { + ++attributesCount; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributesCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + } + output.putShort(attributesCount); + // Put the field_info attributes. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (constantValueIndex != 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.CONSTANT_VALUE)) + .putInt(2) + .putShort(constantValueIndex); + } + Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this field into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Frame.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Frame.class new file mode 100644 index 00000000..2ad1f5ac Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Frame.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Frame.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Frame.java new file mode 100644 index 00000000..cfb892e6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Frame.java @@ -0,0 +1,1522 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * The input and output stack map frames of a basic block. + * + *

Stack map frames are computed in two steps: + * + *

    + *
  • During the visit of each instruction in MethodWriter, the state of the frame at the end of + * the current basic block is updated by simulating the action of the instruction on the + * previous state of this so called "output frame". + *
  • After all instructions have been visited, a fix point algorithm is used in MethodWriter to + * compute the "input frame" of each basic block (i.e. the stack map frame at the beginning of + * the basic block). See {@link MethodWriter#computeAllFrames}. + *
+ * + *

Output stack map frames are computed relatively to the input frame of the basic block, which + * is not yet known when output frames are computed. It is therefore necessary to be able to + * represent abstract types such as "the type at position x in the input frame locals" or "the type + * at position x from the top of the input frame stack" or even "the type at position x in the input + * frame, with y more (or less) array dimensions". This explains the rather complicated type format + * used in this class, explained below. + * + *

The local variables and the operand stack of input and output frames contain values called + * "abstract types" hereafter. An abstract type is represented with 4 fields named DIM, KIND, FLAGS + * and VALUE, packed in a single int value for better performance and memory efficiency: + * + *

+ *   =====================================
+ *   |...DIM|KIND|.F|...............VALUE|
+ *   =====================================
+ * 
+ * + *
    + *
  • the DIM field, stored in the 6 most significant bits, is a signed number of array + * dimensions (from -32 to 31, included). It can be retrieved with {@link #DIM_MASK} and a + * right shift of {@link #DIM_SHIFT}. + *
  • the KIND field, stored in 4 bits, indicates the kind of VALUE used. These 4 bits can be + * retrieved with {@link #KIND_MASK} and, without any shift, must be equal to {@link + * #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND}, {@link + * #FORWARD_UNINITIALIZED_KIND},{@link #LOCAL_KIND} or {@link #STACK_KIND}. + *
  • the FLAGS field, stored in 2 bits, contains up to 2 boolean flags. Currently only one flag + * is defined, namely {@link #TOP_IF_LONG_OR_DOUBLE_FLAG}. + *
  • the VALUE field, stored in the remaining 20 bits, contains either + *
      + *
    • one of the constants {@link #ITEM_TOP}, {@link #ITEM_ASM_BOOLEAN}, {@link + * #ITEM_ASM_BYTE}, {@link #ITEM_ASM_CHAR} or {@link #ITEM_ASM_SHORT}, {@link + * #ITEM_INTEGER}, {@link #ITEM_FLOAT}, {@link #ITEM_LONG}, {@link #ITEM_DOUBLE}, {@link + * #ITEM_NULL} or {@link #ITEM_UNINITIALIZED_THIS}, if KIND is equal to {@link + * #CONSTANT_KIND}. + *
    • the index of a {@link Symbol#TYPE_TAG} {@link Symbol} in the type table of a {@link + * SymbolTable}, if KIND is equal to {@link #REFERENCE_KIND}. + *
    • the index of an {@link Symbol#UNINITIALIZED_TYPE_TAG} {@link Symbol} in the type + * table of a {@link SymbolTable}, if KIND is equal to {@link #UNINITIALIZED_KIND}. + *
    • the index of a {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG} {@link Symbol} in the + * type table of a {@link SymbolTable}, if KIND is equal to {@link + * #FORWARD_UNINITIALIZED_KIND}. + *
    • the index of a local variable in the input stack frame, if KIND is equal to {@link + * #LOCAL_KIND}. + *
    • a position relatively to the top of the stack of the input stack frame, if KIND is + * equal to {@link #STACK_KIND}, + *
    + *
+ * + *

Output frames can contain abstract types of any kind and with a positive or negative array + * dimension (and even unassigned types, represented by 0 - which does not correspond to any valid + * abstract type value). Input frames can only contain CONSTANT_KIND, REFERENCE_KIND, + * UNINITIALIZED_KIND or FORWARD_UNINITIALIZED_KIND abstract types of positive or {@literal null} + * array dimension. In all cases the type table contains only internal type names (array type + * descriptors are forbidden - array dimensions must be represented through the DIM field). + * + *

The LONG and DOUBLE types are always represented by using two slots (LONG + TOP or DOUBLE + + * TOP), for local variables as well as in the operand stack. This is necessary to be able to + * simulate DUPx_y instructions, whose effect would be dependent on the concrete types represented + * by the abstract types in the stack (which are not always known). + * + * @author Eric Bruneton + */ +class Frame { + + // Constants used in the StackMapTable attribute. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.4. + + static final int SAME_FRAME = 0; + static final int SAME_LOCALS_1_STACK_ITEM_FRAME = 64; + static final int RESERVED = 128; + static final int SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED = 247; + static final int CHOP_FRAME = 248; + static final int SAME_FRAME_EXTENDED = 251; + static final int APPEND_FRAME = 252; + static final int FULL_FRAME = 255; + + static final int ITEM_TOP = 0; + static final int ITEM_INTEGER = 1; + static final int ITEM_FLOAT = 2; + static final int ITEM_DOUBLE = 3; + static final int ITEM_LONG = 4; + static final int ITEM_NULL = 5; + static final int ITEM_UNINITIALIZED_THIS = 6; + static final int ITEM_OBJECT = 7; + static final int ITEM_UNINITIALIZED = 8; + // Additional, ASM specific constants used in abstract types below. + private static final int ITEM_ASM_BOOLEAN = 9; + private static final int ITEM_ASM_BYTE = 10; + private static final int ITEM_ASM_CHAR = 11; + private static final int ITEM_ASM_SHORT = 12; + + // The size and offset in bits of each field of an abstract type. + + private static final int DIM_SIZE = 6; + private static final int KIND_SIZE = 4; + private static final int FLAGS_SIZE = 2; + private static final int VALUE_SIZE = 32 - DIM_SIZE - KIND_SIZE - FLAGS_SIZE; + + private static final int DIM_SHIFT = KIND_SIZE + FLAGS_SIZE + VALUE_SIZE; + private static final int KIND_SHIFT = FLAGS_SIZE + VALUE_SIZE; + private static final int FLAGS_SHIFT = VALUE_SIZE; + + // Bitmasks to get each field of an abstract type. + + private static final int DIM_MASK = ((1 << DIM_SIZE) - 1) << DIM_SHIFT; + private static final int KIND_MASK = ((1 << KIND_SIZE) - 1) << KIND_SHIFT; + private static final int VALUE_MASK = (1 << VALUE_SIZE) - 1; + + // Constants to manipulate the DIM field of an abstract type. + + /** The constant to be added to an abstract type to get one with one more array dimension. */ + private static final int ARRAY_OF = +1 << DIM_SHIFT; + + /** The constant to be added to an abstract type to get one with one less array dimension. */ + private static final int ELEMENT_OF = -1 << DIM_SHIFT; + + // Possible values for the KIND field of an abstract type. + + private static final int CONSTANT_KIND = 1 << KIND_SHIFT; + private static final int REFERENCE_KIND = 2 << KIND_SHIFT; + private static final int UNINITIALIZED_KIND = 3 << KIND_SHIFT; + private static final int FORWARD_UNINITIALIZED_KIND = 4 << KIND_SHIFT; + private static final int LOCAL_KIND = 5 << KIND_SHIFT; + private static final int STACK_KIND = 6 << KIND_SHIFT; + + // Possible flags for the FLAGS field of an abstract type. + + /** + * A flag used for LOCAL_KIND and STACK_KIND abstract types, indicating that if the resolved, + * concrete type is LONG or DOUBLE, TOP should be used instead (because the value has been + * partially overridden with an xSTORE instruction). + */ + private static final int TOP_IF_LONG_OR_DOUBLE_FLAG = 1 << FLAGS_SHIFT; + + // Useful predefined abstract types (all the possible CONSTANT_KIND types). + + private static final int TOP = CONSTANT_KIND | ITEM_TOP; + private static final int BOOLEAN = CONSTANT_KIND | ITEM_ASM_BOOLEAN; + private static final int BYTE = CONSTANT_KIND | ITEM_ASM_BYTE; + private static final int CHAR = CONSTANT_KIND | ITEM_ASM_CHAR; + private static final int SHORT = CONSTANT_KIND | ITEM_ASM_SHORT; + private static final int INTEGER = CONSTANT_KIND | ITEM_INTEGER; + private static final int FLOAT = CONSTANT_KIND | ITEM_FLOAT; + private static final int LONG = CONSTANT_KIND | ITEM_LONG; + private static final int DOUBLE = CONSTANT_KIND | ITEM_DOUBLE; + private static final int NULL = CONSTANT_KIND | ITEM_NULL; + private static final int UNINITIALIZED_THIS = CONSTANT_KIND | ITEM_UNINITIALIZED_THIS; + + // ----------------------------------------------------------------------------------------------- + // Instance fields + // ----------------------------------------------------------------------------------------------- + + /** The basic block to which these input and output stack map frames correspond. */ + Label owner; + + /** The input stack map frame locals. This is an array of abstract types. */ + private int[] inputLocals; + + /** The input stack map frame stack. This is an array of abstract types. */ + private int[] inputStack; + + /** The output stack map frame locals. This is an array of abstract types. */ + private int[] outputLocals; + + /** The output stack map frame stack. This is an array of abstract types. */ + private int[] outputStack; + + /** + * The start of the output stack, relatively to the input stack. This offset is always negative or + * null. A null offset means that the output stack must be appended to the input stack. A -n + * offset means that the first n output stack elements must replace the top n input stack + * elements, and that the other elements must be appended to the input stack. + */ + private short outputStackStart; + + /** The index of the top stack element in {@link #outputStack}. */ + private short outputStackTop; + + /** The number of types that are initialized in the basic block. See {@link #initializations}. */ + private int initializationCount; + + /** + * The abstract types that are initialized in the basic block. A constructor invocation on an + * UNINITIALIZED, FORWARD_UNINITIALIZED or UNINITIALIZED_THIS abstract type must replace every + * occurrence of this type in the local variables and in the operand stack. This cannot be + * done during the first step of the algorithm since, during this step, the local variables and + * the operand stack types are still abstract. It is therefore necessary to store the abstract + * types of the constructors which are invoked in the basic block, in order to do this replacement + * during the second step of the algorithm, where the frames are fully computed. Note that this + * array can contain abstract types that are relative to the input locals or to the input stack. + */ + private int[] initializations; + + // ----------------------------------------------------------------------------------------------- + // Constructor + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new Frame. + * + * @param owner the basic block to which these input and output stack map frames correspond. + */ + Frame(final Label owner) { + this.owner = owner; + } + + /** + * Sets this frame to the value of the given frame. + * + *

WARNING: after this method is called the two frames share the same data structures. It is + * recommended to discard the given frame to avoid unexpected side effects. + * + * @param frame The new frame value. + */ + final void copyFrom(final Frame frame) { + inputLocals = frame.inputLocals; + inputStack = frame.inputStack; + outputStackStart = 0; + outputLocals = frame.outputLocals; + outputStack = frame.outputStack; + outputStackTop = frame.outputStackTop; + initializationCount = frame.initializationCount; + initializations = frame.initializations; + } + + // ----------------------------------------------------------------------------------------------- + // Static methods to get abstract types from other type formats + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the abstract type corresponding to the given public API frame element type. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param type a frame element type described using the same format as in {@link + * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or + * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating + * a NEW instruction (for uninitialized types). + * @return the abstract type corresponding to the given frame element type. + */ + static int getAbstractTypeFromApiFormat(final SymbolTable symbolTable, final Object type) { + if (type instanceof Integer) { + return CONSTANT_KIND | ((Integer) type).intValue(); + } else if (type instanceof String) { + String descriptor = Type.getObjectType((String) type).getDescriptor(); + return getAbstractTypeFromDescriptor(symbolTable, descriptor, 0); + } else { + Label label = (Label) type; + if ((label.flags & Label.FLAG_RESOLVED) != 0) { + return UNINITIALIZED_KIND | symbolTable.addUninitializedType("", label.bytecodeOffset); + } else { + return FORWARD_UNINITIALIZED_KIND | symbolTable.addForwardUninitializedType("", label); + } + } + } + + /** + * Returns the abstract type corresponding to the internal name of a class. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param internalName the internal name of a class. This must not be an array type + * descriptor. + * @return the abstract type value corresponding to the given internal name. + */ + static int getAbstractTypeFromInternalName( + final SymbolTable symbolTable, final String internalName) { + return REFERENCE_KIND | symbolTable.addType(internalName); + } + + /** + * Returns the abstract type corresponding to the given type descriptor. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param buffer a string ending with a type descriptor. + * @param offset the start offset of the type descriptor in buffer. + * @return the abstract type corresponding to the given type descriptor. + */ + private static int getAbstractTypeFromDescriptor( + final SymbolTable symbolTable, final String buffer, final int offset) { + String internalName; + switch (buffer.charAt(offset)) { + case 'V': + return 0; + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + return INTEGER; + case 'F': + return FLOAT; + case 'J': + return LONG; + case 'D': + return DOUBLE; + case 'L': + internalName = buffer.substring(offset + 1, buffer.length() - 1); + return REFERENCE_KIND | symbolTable.addType(internalName); + case '[': + int elementDescriptorOffset = offset + 1; + while (buffer.charAt(elementDescriptorOffset) == '[') { + ++elementDescriptorOffset; + } + int typeValue; + switch (buffer.charAt(elementDescriptorOffset)) { + case 'Z': + typeValue = BOOLEAN; + break; + case 'C': + typeValue = CHAR; + break; + case 'B': + typeValue = BYTE; + break; + case 'S': + typeValue = SHORT; + break; + case 'I': + typeValue = INTEGER; + break; + case 'F': + typeValue = FLOAT; + break; + case 'J': + typeValue = LONG; + break; + case 'D': + typeValue = DOUBLE; + break; + case 'L': + internalName = buffer.substring(elementDescriptorOffset + 1, buffer.length() - 1); + typeValue = REFERENCE_KIND | symbolTable.addType(internalName); + break; + default: + throw new IllegalArgumentException( + "Invalid descriptor fragment: " + buffer.substring(elementDescriptorOffset)); + } + return ((elementDescriptorOffset - offset) << DIM_SHIFT) | typeValue; + default: + throw new IllegalArgumentException("Invalid descriptor: " + buffer.substring(offset)); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to the input frame + // ----------------------------------------------------------------------------------------------- + + /** + * Sets the input frame from the given method description. This method is used to initialize the + * first frame of a method, which is implicit (i.e. not stored explicitly in the StackMapTable + * attribute). + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param access the method's access flags. + * @param descriptor the method descriptor. + * @param maxLocals the maximum number of local variables of the method. + */ + final void setInputFrameFromDescriptor( + final SymbolTable symbolTable, + final int access, + final String descriptor, + final int maxLocals) { + inputLocals = new int[maxLocals]; + inputStack = new int[0]; + int inputLocalIndex = 0; + if ((access & Opcodes.ACC_STATIC) == 0) { + if ((access & Constants.ACC_CONSTRUCTOR) == 0) { + inputLocals[inputLocalIndex++] = + REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName()); + } else { + inputLocals[inputLocalIndex++] = UNINITIALIZED_THIS; + } + } + for (Type argumentType : Type.getArgumentTypes(descriptor)) { + int abstractType = + getAbstractTypeFromDescriptor(symbolTable, argumentType.getDescriptor(), 0); + inputLocals[inputLocalIndex++] = abstractType; + if (abstractType == LONG || abstractType == DOUBLE) { + inputLocals[inputLocalIndex++] = TOP; + } + } + while (inputLocalIndex < maxLocals) { + inputLocals[inputLocalIndex++] = TOP; + } + } + + /** + * Sets the input frame from the given public API frame description. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param numLocal the number of local variables. + * @param local the local variable types, described using the same format as in {@link + * MethodVisitor#visitFrame}. + * @param numStack the number of operand stack elements. + * @param stack the operand stack types, described using the same format as in {@link + * MethodVisitor#visitFrame}. + */ + final void setInputFrameFromApiFormat( + final SymbolTable symbolTable, + final int numLocal, + final Object[] local, + final int numStack, + final Object[] stack) { + int inputLocalIndex = 0; + for (int i = 0; i < numLocal; ++i) { + inputLocals[inputLocalIndex++] = getAbstractTypeFromApiFormat(symbolTable, local[i]); + if (local[i] == Opcodes.LONG || local[i] == Opcodes.DOUBLE) { + inputLocals[inputLocalIndex++] = TOP; + } + } + while (inputLocalIndex < inputLocals.length) { + inputLocals[inputLocalIndex++] = TOP; + } + int numStackTop = 0; + for (int i = 0; i < numStack; ++i) { + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + ++numStackTop; + } + } + inputStack = new int[numStack + numStackTop]; + int inputStackIndex = 0; + for (int i = 0; i < numStack; ++i) { + inputStack[inputStackIndex++] = getAbstractTypeFromApiFormat(symbolTable, stack[i]); + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + inputStack[inputStackIndex++] = TOP; + } + } + outputStackTop = 0; + initializationCount = 0; + } + + final int getInputStackSize() { + return inputStack.length; + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to the output frame + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the abstract type stored at the given local variable index in the output frame. + * + * @param localIndex the index of the local variable whose value must be returned. + * @return the abstract type stored at the given local variable index in the output frame. + */ + private int getLocal(final int localIndex) { + if (outputLocals == null || localIndex >= outputLocals.length) { + // If this local has never been assigned in this basic block, it is still equal to its value + // in the input frame. + return LOCAL_KIND | localIndex; + } else { + int abstractType = outputLocals[localIndex]; + if (abstractType == 0) { + // If this local has never been assigned in this basic block, so it is still equal to its + // value in the input frame. + abstractType = outputLocals[localIndex] = LOCAL_KIND | localIndex; + } + return abstractType; + } + } + + /** + * Replaces the abstract type stored at the given local variable index in the output frame. + * + * @param localIndex the index of the output frame local variable that must be set. + * @param abstractType the value that must be set. + */ + private void setLocal(final int localIndex, final int abstractType) { + // Create and/or resize the output local variables array if necessary. + if (outputLocals == null) { + outputLocals = new int[10]; + } + int outputLocalsLength = outputLocals.length; + if (localIndex >= outputLocalsLength) { + int[] newOutputLocals = new int[Math.max(localIndex + 1, 2 * outputLocalsLength)]; + System.arraycopy(outputLocals, 0, newOutputLocals, 0, outputLocalsLength); + outputLocals = newOutputLocals; + } + // Set the local variable. + outputLocals[localIndex] = abstractType; + } + + /** + * Pushes the given abstract type on the output frame stack. + * + * @param abstractType an abstract type. + */ + private void push(final int abstractType) { + // Create and/or resize the output stack array if necessary. + if (outputStack == null) { + outputStack = new int[10]; + } + int outputStackLength = outputStack.length; + if (outputStackTop >= outputStackLength) { + int[] newOutputStack = new int[Math.max(outputStackTop + 1, 2 * outputStackLength)]; + System.arraycopy(outputStack, 0, newOutputStack, 0, outputStackLength); + outputStack = newOutputStack; + } + // Pushes the abstract type on the output stack. + outputStack[outputStackTop++] = abstractType; + // Updates the maximum size reached by the output stack, if needed (note that this size is + // relative to the input stack size, which is not known yet). + short outputStackSize = (short) (outputStackStart + outputStackTop); + if (outputStackSize > owner.outputStackMax) { + owner.outputStackMax = outputStackSize; + } + } + + /** + * Pushes the abstract type corresponding to the given descriptor on the output frame stack. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param descriptor a type or method descriptor (in which case its return type is pushed). + */ + private void push(final SymbolTable symbolTable, final String descriptor) { + int typeDescriptorOffset = + descriptor.charAt(0) == '(' ? Type.getReturnTypeOffset(descriptor) : 0; + int abstractType = getAbstractTypeFromDescriptor(symbolTable, descriptor, typeDescriptorOffset); + if (abstractType != 0) { + push(abstractType); + if (abstractType == LONG || abstractType == DOUBLE) { + push(TOP); + } + } + } + + /** + * Pops an abstract type from the output frame stack and returns its value. + * + * @return the abstract type that has been popped from the output frame stack. + */ + private int pop() { + if (outputStackTop > 0) { + return outputStack[--outputStackTop]; + } else { + // If the output frame stack is empty, pop from the input stack. + return STACK_KIND | -(--outputStackStart); + } + } + + /** + * Pops the given number of abstract types from the output frame stack. + * + * @param elements the number of abstract types that must be popped. + */ + private void pop(final int elements) { + if (outputStackTop >= elements) { + outputStackTop -= (short)elements; + } else { + // If the number of elements to be popped is greater than the number of elements in the output + // stack, clear it, and pop the remaining elements from the input stack. + outputStackStart -= (short) (elements - outputStackTop); + outputStackTop = 0; + } + } + + /** + * Pops as many abstract types from the output frame stack as described by the given descriptor. + * + * @param descriptor a type or method descriptor (in which case its argument types are popped). + */ + private void pop(final String descriptor) { + char firstDescriptorChar = descriptor.charAt(0); + if (firstDescriptorChar == '(') { + pop((Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1); + } else if (firstDescriptorChar == 'J' || firstDescriptorChar == 'D') { + pop(2); + } else { + pop(1); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to handle uninitialized types + // ----------------------------------------------------------------------------------------------- + + /** + * Adds an abstract type to the list of types on which a constructor is invoked in the basic + * block. + * + * @param abstractType an abstract type on a which a constructor is invoked. + */ + private void addInitializedType(final int abstractType) { + // Create and/or resize the initializations array if necessary. + if (initializations == null) { + initializations = new int[2]; + } + int initializationsLength = initializations.length; + if (initializationCount >= initializationsLength) { + int[] newInitializations = + new int[Math.max(initializationCount + 1, 2 * initializationsLength)]; + System.arraycopy(initializations, 0, newInitializations, 0, initializationsLength); + initializations = newInitializations; + } + // Store the abstract type. + initializations[initializationCount++] = abstractType; + } + + /** + * Returns the "initialized" abstract type corresponding to the given abstract type. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param abstractType an abstract type. + * @return the REFERENCE_KIND abstract type corresponding to abstractType if it is + * UNINITIALIZED_THIS or an UNINITIALIZED_KIND or FORWARD_UNINITIALIZED_KIND abstract type for + * one of the types on which a constructor is invoked in the basic block. Otherwise returns + * abstractType. + */ + private int getInitializedType(final SymbolTable symbolTable, final int abstractType) { + if (abstractType == UNINITIALIZED_THIS + || (abstractType & (DIM_MASK | KIND_MASK)) == UNINITIALIZED_KIND + || (abstractType & (DIM_MASK | KIND_MASK)) == FORWARD_UNINITIALIZED_KIND) { + for (int i = 0; i < initializationCount; ++i) { + int initializedType = initializations[i]; + int dim = initializedType & DIM_MASK; + int kind = initializedType & KIND_MASK; + int value = initializedType & VALUE_MASK; + if (kind == LOCAL_KIND) { + initializedType = dim + inputLocals[value]; + } else if (kind == STACK_KIND) { + initializedType = dim + inputStack[inputStack.length - value]; + } + if (abstractType == initializedType) { + if (abstractType == UNINITIALIZED_THIS) { + return REFERENCE_KIND | symbolTable.addType(symbolTable.getClassName()); + } else { + return REFERENCE_KIND + | symbolTable.addType(symbolTable.getType(abstractType & VALUE_MASK).value); + } + } + } + } + return abstractType; + } + + // ----------------------------------------------------------------------------------------------- + // Main method, to simulate the execution of each instruction on the output frame + // ----------------------------------------------------------------------------------------------- + + /** + * Simulates the action of the given instruction on the output stack frame. + * + * @param opcode the opcode of the instruction. + * @param arg the numeric operand of the instruction, if any. + * @param argSymbol the Symbol operand of the instruction, if any. + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + */ + void execute( + final int opcode, final int arg, final Symbol argSymbol, final SymbolTable symbolTable) { + // Abstract types popped from the stack or read from local variables. + int abstractType1; + int abstractType2; + int abstractType3; + int abstractType4; + switch (opcode) { + case Opcodes.NOP: + case Opcodes.INEG: + case Opcodes.LNEG: + case Opcodes.FNEG: + case Opcodes.DNEG: + case Opcodes.I2B: + case Opcodes.I2C: + case Opcodes.I2S: + case Opcodes.GOTO: + case Opcodes.RETURN: + break; + case Opcodes.ACONST_NULL: + push(NULL); + break; + case Opcodes.ICONST_M1: + case Opcodes.ICONST_0: + case Opcodes.ICONST_1: + case Opcodes.ICONST_2: + case Opcodes.ICONST_3: + case Opcodes.ICONST_4: + case Opcodes.ICONST_5: + case Opcodes.BIPUSH: + case Opcodes.SIPUSH: + case Opcodes.ILOAD: + push(INTEGER); + break; + case Opcodes.LCONST_0: + case Opcodes.LCONST_1: + case Opcodes.LLOAD: + push(LONG); + push(TOP); + break; + case Opcodes.FCONST_0: + case Opcodes.FCONST_1: + case Opcodes.FCONST_2: + case Opcodes.FLOAD: + push(FLOAT); + break; + case Opcodes.DCONST_0: + case Opcodes.DCONST_1: + case Opcodes.DLOAD: + push(DOUBLE); + push(TOP); + break; + case Opcodes.LDC: + switch (argSymbol.tag) { + case Symbol.CONSTANT_INTEGER_TAG: + push(INTEGER); + break; + case Symbol.CONSTANT_LONG_TAG: + push(LONG); + push(TOP); + break; + case Symbol.CONSTANT_FLOAT_TAG: + push(FLOAT); + break; + case Symbol.CONSTANT_DOUBLE_TAG: + push(DOUBLE); + push(TOP); + break; + case Symbol.CONSTANT_CLASS_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/Class")); + break; + case Symbol.CONSTANT_STRING_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/String")); + break; + case Symbol.CONSTANT_METHOD_TYPE_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodType")); + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + push(REFERENCE_KIND | symbolTable.addType("java/lang/invoke/MethodHandle")); + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + push(symbolTable, argSymbol.value); + break; + default: + throw new AssertionError(); + } + break; + case Opcodes.ALOAD: + push(getLocal(arg)); + break; + case Opcodes.LALOAD: + case Opcodes.D2L: + pop(2); + push(LONG); + push(TOP); + break; + case Opcodes.DALOAD: + case Opcodes.L2D: + pop(2); + push(DOUBLE); + push(TOP); + break; + case Opcodes.AALOAD: + pop(1); + abstractType1 = pop(); + push(abstractType1 == NULL ? abstractType1 : ELEMENT_OF + abstractType1); + break; + case Opcodes.ISTORE: + case Opcodes.FSTORE: + case Opcodes.ASTORE: + abstractType1 = pop(); + setLocal(arg, abstractType1); + if (arg > 0) { + int previousLocalType = getLocal(arg - 1); + if (previousLocalType == LONG || previousLocalType == DOUBLE) { + setLocal(arg - 1, TOP); + } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND + || (previousLocalType & KIND_MASK) == STACK_KIND) { + // The type of the previous local variable is not known yet, but if it later appears + // to be LONG or DOUBLE, we should then use TOP instead. + setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG); + } + } + break; + case Opcodes.LSTORE: + case Opcodes.DSTORE: + pop(1); + abstractType1 = pop(); + setLocal(arg, abstractType1); + setLocal(arg + 1, TOP); + if (arg > 0) { + int previousLocalType = getLocal(arg - 1); + if (previousLocalType == LONG || previousLocalType == DOUBLE) { + setLocal(arg - 1, TOP); + } else if ((previousLocalType & KIND_MASK) == LOCAL_KIND + || (previousLocalType & KIND_MASK) == STACK_KIND) { + // The type of the previous local variable is not known yet, but if it later appears + // to be LONG or DOUBLE, we should then use TOP instead. + setLocal(arg - 1, previousLocalType | TOP_IF_LONG_OR_DOUBLE_FLAG); + } + } + break; + case Opcodes.IASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + case Opcodes.FASTORE: + case Opcodes.AASTORE: + pop(3); + break; + case Opcodes.LASTORE: + case Opcodes.DASTORE: + pop(4); + break; + case Opcodes.POP: + case Opcodes.IFEQ: + case Opcodes.IFNE: + case Opcodes.IFLT: + case Opcodes.IFGE: + case Opcodes.IFGT: + case Opcodes.IFLE: + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.TABLESWITCH: + case Opcodes.LOOKUPSWITCH: + case Opcodes.ATHROW: + case Opcodes.MONITORENTER: + case Opcodes.MONITOREXIT: + case Opcodes.IFNULL: + case Opcodes.IFNONNULL: + pop(1); + break; + case Opcodes.POP2: + case Opcodes.IF_ICMPEQ: + case Opcodes.IF_ICMPNE: + case Opcodes.IF_ICMPLT: + case Opcodes.IF_ICMPGE: + case Opcodes.IF_ICMPGT: + case Opcodes.IF_ICMPLE: + case Opcodes.IF_ACMPEQ: + case Opcodes.IF_ACMPNE: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + pop(2); + break; + case Opcodes.DUP: + abstractType1 = pop(); + push(abstractType1); + push(abstractType1); + break; + case Opcodes.DUP_X1: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType1); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP_X2: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + push(abstractType1); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2_X1: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.DUP2_X2: + abstractType1 = pop(); + abstractType2 = pop(); + abstractType3 = pop(); + abstractType4 = pop(); + push(abstractType2); + push(abstractType1); + push(abstractType4); + push(abstractType3); + push(abstractType2); + push(abstractType1); + break; + case Opcodes.SWAP: + abstractType1 = pop(); + abstractType2 = pop(); + push(abstractType1); + push(abstractType2); + break; + case Opcodes.IALOAD: + case Opcodes.BALOAD: + case Opcodes.CALOAD: + case Opcodes.SALOAD: + case Opcodes.IADD: + case Opcodes.ISUB: + case Opcodes.IMUL: + case Opcodes.IDIV: + case Opcodes.IREM: + case Opcodes.IAND: + case Opcodes.IOR: + case Opcodes.IXOR: + case Opcodes.ISHL: + case Opcodes.ISHR: + case Opcodes.IUSHR: + case Opcodes.L2I: + case Opcodes.D2I: + case Opcodes.FCMPL: + case Opcodes.FCMPG: + pop(2); + push(INTEGER); + break; + case Opcodes.LADD: + case Opcodes.LSUB: + case Opcodes.LMUL: + case Opcodes.LDIV: + case Opcodes.LREM: + case Opcodes.LAND: + case Opcodes.LOR: + case Opcodes.LXOR: + pop(4); + push(LONG); + push(TOP); + break; + case Opcodes.FALOAD: + case Opcodes.FADD: + case Opcodes.FSUB: + case Opcodes.FMUL: + case Opcodes.FDIV: + case Opcodes.FREM: + case Opcodes.L2F: + case Opcodes.D2F: + pop(2); + push(FLOAT); + break; + case Opcodes.DADD: + case Opcodes.DSUB: + case Opcodes.DMUL: + case Opcodes.DDIV: + case Opcodes.DREM: + pop(4); + push(DOUBLE); + push(TOP); + break; + case Opcodes.LSHL: + case Opcodes.LSHR: + case Opcodes.LUSHR: + pop(3); + push(LONG); + push(TOP); + break; + case Opcodes.IINC: + setLocal(arg, INTEGER); + break; + case Opcodes.I2L: + case Opcodes.F2L: + pop(1); + push(LONG); + push(TOP); + break; + case Opcodes.I2F: + pop(1); + push(FLOAT); + break; + case Opcodes.I2D: + case Opcodes.F2D: + pop(1); + push(DOUBLE); + push(TOP); + break; + case Opcodes.F2I: + case Opcodes.ARRAYLENGTH: + case Opcodes.INSTANCEOF: + pop(1); + push(INTEGER); + break; + case Opcodes.LCMP: + case Opcodes.DCMPL: + case Opcodes.DCMPG: + pop(4); + push(INTEGER); + break; + case Opcodes.JSR: + case Opcodes.RET: + throw new IllegalArgumentException("JSR/RET are not supported with computeFrames option"); + case Opcodes.GETSTATIC: + push(symbolTable, argSymbol.value); + break; + case Opcodes.PUTSTATIC: + pop(argSymbol.value); + break; + case Opcodes.GETFIELD: + pop(1); + push(symbolTable, argSymbol.value); + break; + case Opcodes.PUTFIELD: + pop(argSymbol.value); + pop(); + break; + case Opcodes.INVOKEVIRTUAL: + case Opcodes.INVOKESPECIAL: + case Opcodes.INVOKESTATIC: + case Opcodes.INVOKEINTERFACE: + pop(argSymbol.value); + if (opcode != Opcodes.INVOKESTATIC) { + abstractType1 = pop(); + if (opcode == Opcodes.INVOKESPECIAL && argSymbol.name.charAt(0) == '<') { + addInitializedType(abstractType1); + } + } + push(symbolTable, argSymbol.value); + break; + case Opcodes.INVOKEDYNAMIC: + pop(argSymbol.value); + push(symbolTable, argSymbol.value); + break; + case Opcodes.NEW: + push(UNINITIALIZED_KIND | symbolTable.addUninitializedType(argSymbol.value, arg)); + break; + case Opcodes.NEWARRAY: + pop(); + switch (arg) { + case Opcodes.T_BOOLEAN: + push(ARRAY_OF | BOOLEAN); + break; + case Opcodes.T_CHAR: + push(ARRAY_OF | CHAR); + break; + case Opcodes.T_BYTE: + push(ARRAY_OF | BYTE); + break; + case Opcodes.T_SHORT: + push(ARRAY_OF | SHORT); + break; + case Opcodes.T_INT: + push(ARRAY_OF | INTEGER); + break; + case Opcodes.T_FLOAT: + push(ARRAY_OF | FLOAT); + break; + case Opcodes.T_DOUBLE: + push(ARRAY_OF | DOUBLE); + break; + case Opcodes.T_LONG: + push(ARRAY_OF | LONG); + break; + default: + throw new IllegalArgumentException(); + } + break; + case Opcodes.ANEWARRAY: + String arrayElementType = argSymbol.value; + pop(); + if (arrayElementType.charAt(0) == '[') { + push(symbolTable, '[' + arrayElementType); + } else { + push(ARRAY_OF | REFERENCE_KIND | symbolTable.addType(arrayElementType)); + } + break; + case Opcodes.CHECKCAST: + String castType = argSymbol.value; + pop(); + if (castType.charAt(0) == '[') { + push(symbolTable, castType); + } else { + push(REFERENCE_KIND | symbolTable.addType(castType)); + } + break; + case Opcodes.MULTIANEWARRAY: + pop(arg); + push(symbolTable, argSymbol.value); + break; + default: + throw new IllegalArgumentException(); + } + } + + // ----------------------------------------------------------------------------------------------- + // Frame merging methods, used in the second step of the stack map frame computation algorithm + // ----------------------------------------------------------------------------------------------- + + /** + * Computes the concrete output type corresponding to a given abstract output type. + * + * @param abstractOutputType an abstract output type. + * @param numStack the size of the input stack, used to resolve abstract output types of + * STACK_KIND kind. + * @return the concrete output type corresponding to 'abstractOutputType'. + */ + private int getConcreteOutputType(final int abstractOutputType, final int numStack) { + int dim = abstractOutputType & DIM_MASK; + int kind = abstractOutputType & KIND_MASK; + if (kind == LOCAL_KIND) { + // By definition, a LOCAL_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + int concreteOutputType = dim + inputLocals[abstractOutputType & VALUE_MASK]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; + } + return concreteOutputType; + } else if (kind == STACK_KIND) { + // By definition, a STACK_KIND type designates the concrete type of a local variable at + // the beginning of the basic block corresponding to this frame (which is known when + // this method is called, but was not when the abstract type was computed). + int concreteOutputType = dim + inputStack[numStack - (abstractOutputType & VALUE_MASK)]; + if ((abstractOutputType & TOP_IF_LONG_OR_DOUBLE_FLAG) != 0 + && (concreteOutputType == LONG || concreteOutputType == DOUBLE)) { + concreteOutputType = TOP; + } + return concreteOutputType; + } else { + return abstractOutputType; + } + } + + /** + * Merges the input frame of the given {@link Frame} with the input and output frames of this + * {@link Frame}. Returns {@literal true} if the given frame has been changed by this operation + * (the input and output frames of this {@link Frame} are never changed). + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param dstFrame the {@link Frame} whose input frame must be updated. This should be the frame + * of a successor, in the control flow graph, of the basic block corresponding to this frame. + * @param catchTypeIndex if 'frame' corresponds to an exception handler basic block, the type + * table index of the caught exception type, otherwise 0. + * @return {@literal true} if the input frame of 'frame' has been changed by this operation. + */ + final boolean merge( + final SymbolTable symbolTable, final Frame dstFrame, final int catchTypeIndex) { + boolean frameChanged = false; + + // Compute the concrete types of the local variables at the end of the basic block corresponding + // to this frame, by resolving its abstract output types, and merge these concrete types with + // those of the local variables in the input frame of dstFrame. + int numLocal = inputLocals.length; + int numStack = inputStack.length; + if (dstFrame.inputLocals == null) { + dstFrame.inputLocals = new int[numLocal]; + frameChanged = true; + } + for (int i = 0; i < numLocal; ++i) { + int concreteOutputType; + if (outputLocals != null && i < outputLocals.length) { + int abstractOutputType = outputLocals[i]; + if (abstractOutputType == 0) { + // If the local variable has never been assigned in this basic block, it is equal to its + // value at the beginning of the block. + concreteOutputType = inputLocals[i]; + } else { + concreteOutputType = getConcreteOutputType(abstractOutputType, numStack); + } + } else { + // If the local variable has never been assigned in this basic block, it is equal to its + // value at the beginning of the block. + concreteOutputType = inputLocals[i]; + } + // concreteOutputType might be an uninitialized type from the input locals or from the input + // stack. However, if a constructor has been called for this class type in the basic block, + // then this type is no longer uninitialized at the end of basic block. + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputLocals, i); + } + + // If dstFrame is an exception handler block, it can be reached from any instruction of the + // basic block corresponding to this frame, in particular from the first one. Therefore, the + // input locals of dstFrame should be compatible (i.e. merged) with the input locals of this + // frame (and the input stack of dstFrame should be compatible, i.e. merged, with a one + // element stack containing the caught exception type). + if (catchTypeIndex > 0) { + for (int i = 0; i < numLocal; ++i) { + frameChanged |= merge(symbolTable, inputLocals[i], dstFrame.inputLocals, i); + } + if (dstFrame.inputStack == null) { + dstFrame.inputStack = new int[1]; + frameChanged = true; + } + frameChanged |= merge(symbolTable, catchTypeIndex, dstFrame.inputStack, 0); + return frameChanged; + } + + // Compute the concrete types of the stack operands at the end of the basic block corresponding + // to this frame, by resolving its abstract output types, and merge these concrete types with + // those of the stack operands in the input frame of dstFrame. + int numInputStack = inputStack.length + outputStackStart; + if (dstFrame.inputStack == null) { + dstFrame.inputStack = new int[numInputStack + outputStackTop]; + frameChanged = true; + } + // First, do this for the stack operands that have not been popped in the basic block + // corresponding to this frame, and which are therefore equal to their value in the input + // frame (except for uninitialized types, which may have been initialized). + for (int i = 0; i < numInputStack; ++i) { + int concreteOutputType = inputStack[i]; + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= merge(symbolTable, concreteOutputType, dstFrame.inputStack, i); + } + // Then, do this for the stack operands that have pushed in the basic block (this code is the + // same as the one above for local variables). + for (int i = 0; i < outputStackTop; ++i) { + int abstractOutputType = outputStack[i]; + int concreteOutputType = getConcreteOutputType(abstractOutputType, numStack); + if (initializations != null) { + concreteOutputType = getInitializedType(symbolTable, concreteOutputType); + } + frameChanged |= + merge(symbolTable, concreteOutputType, dstFrame.inputStack, numInputStack + i); + } + return frameChanged; + } + + /** + * Merges the type at the given index in the given abstract type array with the given type. + * Returns {@literal true} if the type array has been modified by this operation. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param sourceType the abstract type with which the abstract type array element must be merged. + * This type should be of {@link #CONSTANT_KIND}, {@link #REFERENCE_KIND}, {@link + * #UNINITIALIZED_KIND} or {@link #FORWARD_UNINITIALIZED_KIND} kind, with positive or + * {@literal null} array dimensions. + * @param dstTypes an array of abstract types. These types should be of {@link #CONSTANT_KIND}, + * {@link #REFERENCE_KIND}, {@link #UNINITIALIZED_KIND} or {@link #FORWARD_UNINITIALIZED_KIND} + * kind, with positive or {@literal null} array dimensions. + * @param dstIndex the index of the type that must be merged in dstTypes. + * @return {@literal true} if the type array has been modified by this operation. + */ + private static boolean merge( + final SymbolTable symbolTable, + final int sourceType, + final int[] dstTypes, + final int dstIndex) { + int dstType = dstTypes[dstIndex]; + if (dstType == sourceType) { + // If the types are equal, merge(sourceType, dstType) = dstType, so there is no change. + return false; + } + int srcType = sourceType; + if ((sourceType & ~DIM_MASK) == NULL) { + if (dstType == NULL) { + return false; + } + srcType = NULL; + } + if (dstType == 0) { + // If dstTypes[dstIndex] has never been assigned, merge(srcType, dstType) = srcType. + dstTypes[dstIndex] = srcType; + return true; + } + int mergedType; + if ((dstType & DIM_MASK) != 0 || (dstType & KIND_MASK) == REFERENCE_KIND) { + // If dstType is a reference type of any array dimension. + if (srcType == NULL) { + // If srcType is the NULL type, merge(srcType, dstType) = dstType, so there is no change. + return false; + } else if ((srcType & (DIM_MASK | KIND_MASK)) == (dstType & (DIM_MASK | KIND_MASK))) { + // If srcType has the same array dimension and the same kind as dstType. + if ((dstType & KIND_MASK) == REFERENCE_KIND) { + // If srcType and dstType are reference types with the same array dimension, + // merge(srcType, dstType) = dim(srcType) | common super class of srcType and dstType. + mergedType = + (srcType & DIM_MASK) + | REFERENCE_KIND + | symbolTable.addMergedType(srcType & VALUE_MASK, dstType & VALUE_MASK); + } else { + // If srcType and dstType are array types of equal dimension but different element types, + // merge(srcType, dstType) = dim(srcType) - 1 | java/lang/Object. + int mergedDim = ELEMENT_OF + (srcType & DIM_MASK); + mergedType = mergedDim | REFERENCE_KIND | symbolTable.addType("java/lang/Object"); + } + } else if ((srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND) { + // If srcType is any other reference or array type, + // merge(srcType, dstType) = min(srcDdim, dstDim) | java/lang/Object + // where srcDim is the array dimension of srcType, minus 1 if srcType is an array type + // with a non reference element type (and similarly for dstDim). + int srcDim = srcType & DIM_MASK; + if (srcDim != 0 && (srcType & KIND_MASK) != REFERENCE_KIND) { + srcDim = ELEMENT_OF + srcDim; + } + int dstDim = dstType & DIM_MASK; + if (dstDim != 0 && (dstType & KIND_MASK) != REFERENCE_KIND) { + dstDim = ELEMENT_OF + dstDim; + } + mergedType = + Math.min(srcDim, dstDim) | REFERENCE_KIND | symbolTable.addType("java/lang/Object"); + } else { + // If srcType is any other type, merge(srcType, dstType) = TOP. + mergedType = TOP; + } + } else if (dstType == NULL) { + // If dstType is the NULL type, merge(srcType, dstType) = srcType, or TOP if srcType is not a + // an array type or a reference type. + mergedType = + (srcType & DIM_MASK) != 0 || (srcType & KIND_MASK) == REFERENCE_KIND ? srcType : TOP; + } else { + // If dstType is any other type, merge(srcType, dstType) = TOP whatever srcType. + mergedType = TOP; + } + if (mergedType != dstType) { + dstTypes[dstIndex] = mergedType; + return true; + } + return false; + } + + // ----------------------------------------------------------------------------------------------- + // Frame output methods, to generate StackMapFrame attributes + // ----------------------------------------------------------------------------------------------- + + /** + * Makes the given {@link MethodWriter} visit the input frame of this {@link Frame}. The visit is + * done with the {@link MethodWriter#visitFrameStart}, {@link MethodWriter#visitAbstractType} and + * {@link MethodWriter#visitFrameEnd} methods. + * + * @param methodWriter the {@link MethodWriter} that should visit the input frame of this {@link + * Frame}. + */ + final void accept(final MethodWriter methodWriter) { + // Compute the number of locals, ignoring TOP types that are just after a LONG or a DOUBLE, and + // all trailing TOP types. + int[] localTypes = inputLocals; + int numLocal = 0; + int numTrailingTop = 0; + int i = 0; + while (i < localTypes.length) { + int localType = localTypes[i]; + i += (localType == LONG || localType == DOUBLE) ? 2 : 1; + if (localType == TOP) { + numTrailingTop++; + } else { + numLocal += numTrailingTop + 1; + numTrailingTop = 0; + } + } + // Compute the stack size, ignoring TOP types that are just after a LONG or a DOUBLE. + int[] stackTypes = inputStack; + int numStack = 0; + i = 0; + while (i < stackTypes.length) { + int stackType = stackTypes[i]; + i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; + numStack++; + } + // Visit the frame and its content. + int frameIndex = methodWriter.visitFrameStart(owner.bytecodeOffset, numLocal, numStack); + i = 0; + while (numLocal-- > 0) { + int localType = localTypes[i]; + i += (localType == LONG || localType == DOUBLE) ? 2 : 1; + methodWriter.visitAbstractType(frameIndex++, localType); + } + i = 0; + while (numStack-- > 0) { + int stackType = stackTypes[i]; + i += (stackType == LONG || stackType == DOUBLE) ? 2 : 1; + methodWriter.visitAbstractType(frameIndex++, stackType); + } + methodWriter.visitFrameEnd(); + } + + /** + * Put the given abstract type in the given ByteVector, using the JVMS verification_type_info + * format used in StackMapTable attributes. + * + * @param symbolTable the type table to use to lookup and store type {@link Symbol}. + * @param abstractType an abstract type, restricted to {@link Frame#CONSTANT_KIND}, {@link + * Frame#REFERENCE_KIND}, {@link Frame#UNINITIALIZED_KIND} or {@link + * Frame#FORWARD_UNINITIALIZED_KIND} types. + * @param output where the abstract type must be put. + * @see JVMS + * 4.7.4 + */ + static void putAbstractType( + final SymbolTable symbolTable, final int abstractType, final ByteVector output) { + int arrayDimensions = (abstractType & Frame.DIM_MASK) >> DIM_SHIFT; + if (arrayDimensions == 0) { + int typeValue = abstractType & VALUE_MASK; + switch (abstractType & KIND_MASK) { + case CONSTANT_KIND: + output.putByte(typeValue); + break; + case REFERENCE_KIND: + output + .putByte(ITEM_OBJECT) + .putShort(symbolTable.addConstantClass(symbolTable.getType(typeValue).value).index); + break; + case UNINITIALIZED_KIND: + output.putByte(ITEM_UNINITIALIZED).putShort((int) symbolTable.getType(typeValue).data); + break; + case FORWARD_UNINITIALIZED_KIND: + output.putByte(ITEM_UNINITIALIZED); + symbolTable.getForwardUninitializedLabel(typeValue).put(output); + break; + default: + throw new AssertionError(); + } + } else { + // Case of an array type, we need to build its descriptor first. + StringBuilder typeDescriptor = new StringBuilder(); + while (arrayDimensions-- > 0) { + typeDescriptor.append('['); + } + if ((abstractType & KIND_MASK) == REFERENCE_KIND) { + typeDescriptor + .append('L') + .append(symbolTable.getType(abstractType & VALUE_MASK).value) + .append(';'); + } else { + switch (abstractType & VALUE_MASK) { + case Frame.ITEM_ASM_BOOLEAN: + typeDescriptor.append('Z'); + break; + case Frame.ITEM_ASM_BYTE: + typeDescriptor.append('B'); + break; + case Frame.ITEM_ASM_CHAR: + typeDescriptor.append('C'); + break; + case Frame.ITEM_ASM_SHORT: + typeDescriptor.append('S'); + break; + case Frame.ITEM_INTEGER: + typeDescriptor.append('I'); + break; + case Frame.ITEM_FLOAT: + typeDescriptor.append('F'); + break; + case Frame.ITEM_LONG: + typeDescriptor.append('J'); + break; + case Frame.ITEM_DOUBLE: + typeDescriptor.append('D'); + break; + default: + throw new AssertionError(); + } + } + output + .putByte(ITEM_OBJECT) + .putShort(symbolTable.addConstantClass(typeDescriptor.toString()).index); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Handle.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handle.class new file mode 100644 index 00000000..04955212 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handle.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Handle.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handle.java new file mode 100644 index 00000000..7e5a9e1a --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handle.java @@ -0,0 +1,221 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A reference to a field or a method. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public final class Handle { + + /** + * The kind of field or method designated by this Handle. Should be {@link Opcodes#H_GETFIELD}, + * {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link + * Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, + * {@link Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + */ + private final int tag; + + /** The internal name of the class that owns the field or method designated by this handle. */ + private final String owner; + + /** The name of the field or method designated by this handle. */ + private final String name; + + /** The descriptor of the field or method designated by this handle. */ + private final String descriptor; + + /** Whether the owner is an interface or not. */ + private final boolean isInterface; + + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be {@link + * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link + * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link + * Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the class that owns the field or method designated by this + * handle (see {@link Type#getInternalName()}). + * @param name the name of the field or method designated by this handle. + * @param descriptor the descriptor of the field or method designated by this handle. + * @deprecated this constructor has been superseded by {@link #Handle(int, String, String, String, + * boolean)}. + */ + @Deprecated + public Handle(final int tag, final String owner, final String name, final String descriptor) { + this(tag, owner, name, descriptor, tag == Opcodes.H_INVOKEINTERFACE); + } + + /** + * Constructs a new field or method handle. + * + * @param tag the kind of field or method designated by this Handle. Must be {@link + * Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, {@link + * Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link Opcodes#H_INVOKESTATIC}, + * {@link Opcodes#H_INVOKESPECIAL}, {@link Opcodes#H_NEWINVOKESPECIAL} or {@link + * Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of the class that owns the field or method designated by this + * handle (see {@link Type#getInternalName()}). + * @param name the name of the field or method designated by this handle. + * @param descriptor the descriptor of the field or method designated by this handle. + * @param isInterface whether the owner is an interface or not. + */ + public Handle( + final int tag, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + this.tag = tag; + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + this.isInterface = isInterface; + } + + /** + * Returns the kind of field or method designated by this handle. + * + * @return {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link Opcodes#H_PUTFIELD}, + * {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + */ + public int getTag() { + return tag; + } + + /** + * Returns the internal name of the class that owns the field or method designated by this handle. + * + * @return the internal name of the class that owns the field or method designated by this handle + * (see {@link Type#getInternalName()}). + */ + public String getOwner() { + return owner; + } + + /** + * Returns the name of the field or method designated by this handle. + * + * @return the name of the field or method designated by this handle. + */ + public String getName() { + return name; + } + + /** + * Returns the descriptor of the field or method designated by this handle. + * + * @return the descriptor of the field or method designated by this handle. + */ + public String getDesc() { + return descriptor; + } + + /** + * Returns true if the owner of the field or method designated by this handle is an interface. + * + * @return true if the owner of the field or method designated by this handle is an interface. + */ + public boolean isInterface() { + return isInterface; + } + + @Override + public boolean equals(final Object object) { + if (object == this) { + return true; + } + if (!(object instanceof Handle)) { + return false; + } + Handle handle = (Handle) object; + return tag == handle.tag + && isInterface == handle.isInterface + && owner.equals(handle.owner) + && name.equals(handle.name) + && descriptor.equals(handle.descriptor); + } + + @Override + public int hashCode() { + return tag + + (isInterface ? 64 : 0) + + owner.hashCode() * name.hashCode() * descriptor.hashCode(); + } + + /** + * Returns the textual representation of this handle. The textual representation is: + * + *

    + *
  • for a reference to a class: owner "." name descriptor " (" tag ")", + *
  • for a reference to an interface: owner "." name descriptor " (" tag " itf)". + *
+ */ + @Override + public String toString() { + return owner + '.' + name + descriptor + " (" + tag + (isInterface ? " itf" : "") + ')'; + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Handler.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handler.class new file mode 100644 index 00000000..cd586df9 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handler.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Handler.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handler.java new file mode 100644 index 00000000..3d86e222 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Handler.java @@ -0,0 +1,230 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * Information about an exception handler. Corresponds to an element of the exception_table array of + * a Code attribute, as defined in the Java Virtual Machine Specification (JVMS). Handler instances + * can be chained together, with their {@link #nextHandler} field, to describe a full JVMS + * exception_table array. + * + * @see JVMS + * 4.7.3 + * @author Eric Bruneton + */ +final class Handler { + + /** + * The start_pc field of this JVMS exception_table entry. Corresponds to the beginning of the + * exception handler's scope (inclusive). + */ + final Label startPc; + + /** + * The end_pc field of this JVMS exception_table entry. Corresponds to the end of the exception + * handler's scope (exclusive). + */ + final Label endPc; + + /** + * The handler_pc field of this JVMS exception_table entry. Corresponding to the beginning of the + * exception handler's code. + */ + final Label handlerPc; + + /** + * The catch_type field of this JVMS exception_table entry. This is the constant pool index of the + * internal name of the type of exceptions handled by this handler, or 0 to catch any exceptions. + */ + final int catchType; + + /** + * The internal name of the type of exceptions handled by this handler, or {@literal null} to + * catch any exceptions. + */ + final String catchTypeDescriptor; + + /** The next exception handler. */ + Handler nextHandler; + + /** + * Constructs a new Handler. + * + * @param startPc the start_pc field of this JVMS exception_table entry. + * @param endPc the end_pc field of this JVMS exception_table entry. + * @param handlerPc the handler_pc field of this JVMS exception_table entry. + * @param catchType The catch_type field of this JVMS exception_table entry. + * @param catchTypeDescriptor The internal name of the type of exceptions handled by this handler, + * or {@literal null} to catch any exceptions. + */ + Handler( + final Label startPc, + final Label endPc, + final Label handlerPc, + final int catchType, + final String catchTypeDescriptor) { + this.startPc = startPc; + this.endPc = endPc; + this.handlerPc = handlerPc; + this.catchType = catchType; + this.catchTypeDescriptor = catchTypeDescriptor; + } + + /** + * Constructs a new Handler from the given one, with a different scope. + * + * @param handler an existing Handler. + * @param startPc the start_pc field of this JVMS exception_table entry. + * @param endPc the end_pc field of this JVMS exception_table entry. + */ + Handler(final Handler handler, final Label startPc, final Label endPc) { + this(startPc, endPc, handler.handlerPc, handler.catchType, handler.catchTypeDescriptor); + this.nextHandler = handler.nextHandler; + } + + /** + * Removes the range between start and end from the Handler list that begins with the given + * element. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @param start the start of the range to be removed. + * @param end the end of the range to be removed. Maybe {@literal null}. + * @return the exception handler list with the start-end range removed. + */ + static Handler removeRange(final Handler firstHandler, final Label start, final Label end) { + if (firstHandler == null) { + return null; + } else { + firstHandler.nextHandler = removeRange(firstHandler.nextHandler, start, end); + } + int handlerStart = firstHandler.startPc.bytecodeOffset; + int handlerEnd = firstHandler.endPc.bytecodeOffset; + int rangeStart = start.bytecodeOffset; + int rangeEnd = end == null ? Integer.MAX_VALUE : end.bytecodeOffset; + // Return early if [handlerStart,handlerEnd[ and [rangeStart,rangeEnd[ don't intersect. + if (rangeStart >= handlerEnd || rangeEnd <= handlerStart) { + return firstHandler; + } + if (rangeStart <= handlerStart) { + if (rangeEnd >= handlerEnd) { + // If [handlerStart,handlerEnd[ is included in [rangeStart,rangeEnd[, remove firstHandler. + return firstHandler.nextHandler; + } else { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [rangeEnd,handlerEnd[ + return new Handler(firstHandler, end, firstHandler.endPc); + } + } else if (rangeEnd >= handlerEnd) { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = [handlerStart,rangeStart[ + return new Handler(firstHandler, firstHandler.startPc, start); + } else { + // [handlerStart,handlerEnd[ - [rangeStart,rangeEnd[ = + // [handlerStart,rangeStart[ + [rangeEnd,handerEnd[ + firstHandler.nextHandler = new Handler(firstHandler, end, firstHandler.endPc); + return new Handler(firstHandler, firstHandler.startPc, start); + } + } + + /** + * Returns the number of elements of the Handler list that begins with the given element. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @return the number of elements of the Handler list that begins with 'handler'. + */ + static int getExceptionTableLength(final Handler firstHandler) { + int length = 0; + Handler handler = firstHandler; + while (handler != null) { + length++; + handler = handler.nextHandler; + } + return length; + } + + /** + * Returns the size in bytes of the JVMS exception_table corresponding to the Handler list that + * begins with the given element. This includes the exception_table_length field. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @return the size in bytes of the exception_table_length and exception_table structures. + */ + static int getExceptionTableSize(final Handler firstHandler) { + return 2 + 8 * getExceptionTableLength(firstHandler); + } + + /** + * Puts the JVMS exception_table corresponding to the Handler list that begins with the given + * element. This includes the exception_table_length field. + * + * @param firstHandler the beginning of a Handler list. May be {@literal null}. + * @param output where the exception_table_length and exception_table structures must be put. + */ + static void putExceptionTable(final Handler firstHandler, final ByteVector output) { + output.putShort(getExceptionTableLength(firstHandler)); + Handler handler = firstHandler; + while (handler != null) { + output + .putShort(handler.startPc.bytecodeOffset) + .putShort(handler.endPc.bytecodeOffset) + .putShort(handler.handlerPc.bytecodeOffset) + .putShort(handler.catchType); + handler = handler.nextHandler; + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Label.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Label.class new file mode 100644 index 00000000..04f693a3 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Label.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Label.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Label.java new file mode 100644 index 00000000..c321fc8a --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Label.java @@ -0,0 +1,686 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A position in the bytecode of a method. Labels are used for jump, goto, and switch instructions, + * and for try catch blocks. A label designates the instruction that is just after. Note + * however that there can be other elements between a label and the instruction it designates (such + * as other labels, stack map frames, line numbers, etc.). + * + * @author Eric Bruneton + */ +public class Label { + + /** + * A flag indicating that a label is only used for debug attributes. Such a label is not the start + * of a basic block, the target of a jump instruction, or an exception handler. It can be safely + * ignored in control flow graph analysis algorithms (for optimization purposes). + */ + static final int FLAG_DEBUG_ONLY = 1; + + /** + * A flag indicating that a label is the target of a jump instruction, or the start of an + * exception handler. + */ + static final int FLAG_JUMP_TARGET = 2; + + /** A flag indicating that the bytecode offset of a label is known. */ + static final int FLAG_RESOLVED = 4; + + /** A flag indicating that a label corresponds to a reachable basic block. */ + static final int FLAG_REACHABLE = 8; + + /** + * A flag indicating that the basic block corresponding to a label ends with a subroutine call. By + * construction in {@link MethodWriter#visitJumpInsn}, labels with this flag set have at least two + * outgoing edges: + * + *
    + *
  • the first one corresponds to the instruction that follows the jsr instruction in the + * bytecode, i.e. where execution continues when it returns from the jsr call. This is a + * virtual control flow edge, since execution never goes directly from the jsr to the next + * instruction. Instead, it goes to the subroutine and eventually returns to the instruction + * following the jsr. This virtual edge is used to compute the real outgoing edges of the + * basic blocks ending with a ret instruction, in {@link #addSubroutineRetSuccessors}. + *
  • the second one corresponds to the target of the jsr instruction, + *
+ */ + static final int FLAG_SUBROUTINE_CALLER = 16; + + /** + * A flag indicating that the basic block corresponding to a label is the start of a subroutine. + */ + static final int FLAG_SUBROUTINE_START = 32; + + /** A flag indicating that the basic block corresponding to a label is the end of a subroutine. */ + static final int FLAG_SUBROUTINE_END = 64; + + /** A flag indicating that this label has at least one associated line number. */ + static final int FLAG_LINE_NUMBER = 128; + + /** + * The number of elements to add to the {@link #otherLineNumbers} array when it needs to be + * resized to store a new source line number. + */ + static final int LINE_NUMBERS_CAPACITY_INCREMENT = 4; + + /** + * The number of elements to add to the {@link #forwardReferences} array when it needs to be + * resized to store a new forward reference. + */ + static final int FORWARD_REFERENCES_CAPACITY_INCREMENT = 6; + + /** + * The bit mask to extract the type of a forward reference to this label. The extracted type is + * either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link #FORWARD_REFERENCE_TYPE_WIDE}. + * + * @see #forwardReferences + */ + static final int FORWARD_REFERENCE_TYPE_MASK = 0xF0000000; + + /** + * The type of forward references stored with two bytes in the bytecode. This is the case, for + * instance, of a forward reference from an ifnull instruction. + */ + static final int FORWARD_REFERENCE_TYPE_SHORT = 0x10000000; + + /** + * The type of forward references stored in four bytes in the bytecode. This is the case, for + * instance, of a forward reference from a lookupswitch instruction. + */ + static final int FORWARD_REFERENCE_TYPE_WIDE = 0x20000000; + + /** + * The type of forward references stored in two bytes in the stack map table. This is the + * case of the labels of {@link Frame#ITEM_UNINITIALIZED} stack map frame elements, when the NEW + * instruction is after the <init> constructor call (in bytecode offset order). + */ + static final int FORWARD_REFERENCE_TYPE_STACK_MAP = 0x30000000; + + /** + * The bit mask to extract the 'handle' of a forward reference to this label. The extracted handle + * is the bytecode offset where the forward reference value is stored (using either 2 or 4 bytes, + * as indicated by the {@link #FORWARD_REFERENCE_TYPE_MASK}). + * + * @see #forwardReferences + */ + static final int FORWARD_REFERENCE_HANDLE_MASK = 0x0FFFFFFF; + + /** + * A sentinel element used to indicate the end of a list of labels. + * + * @see #nextListElement + */ + static final Label EMPTY_LIST = new Label(); + + /** + * A user managed state associated with this label. Warning: this field is used by the ASM tree + * package. In order to use it with the ASM tree package you must override the getLabelNode method + * in MethodNode. + */ + public Object info; + + /** + * The type and status of this label or its corresponding basic block. Must be zero or more of + * {@link #FLAG_DEBUG_ONLY}, {@link #FLAG_JUMP_TARGET}, {@link #FLAG_RESOLVED}, {@link + * #FLAG_REACHABLE}, {@link #FLAG_SUBROUTINE_CALLER}, {@link #FLAG_SUBROUTINE_START}, {@link + * #FLAG_SUBROUTINE_END}. + */ + short flags; + + /** + * The source line number corresponding to this label, if {@link #FLAG_LINE_NUMBER} is set. If + * there are several source line numbers corresponding to this label, the first one is stored in + * this field, and the remaining ones are stored in {@link #otherLineNumbers}. + */ + private short lineNumber; + + /** + * The source line numbers corresponding to this label, in addition to {@link #lineNumber}, or + * null. The first element of this array is the number n of source line numbers it contains, which + * are stored between indices 1 and n (inclusive). + */ + private int[] otherLineNumbers; + + /** + * The offset of this label in the bytecode of its method, in bytes. This value is set if and only + * if the {@link #FLAG_RESOLVED} flag is set. + */ + int bytecodeOffset; + + /** + * The forward references to this label. The first element is the number of forward references, + * times 2 (this corresponds to the index of the last element actually used in this array). Then, + * each forward reference is described with two consecutive integers noted + * 'sourceInsnBytecodeOffset' and 'reference': + * + *
    + *
  • 'sourceInsnBytecodeOffset' is the bytecode offset of the instruction that contains the + * forward reference, + *
  • 'reference' contains the type and the offset in the bytecode where the forward reference + * value must be stored, which can be extracted with {@link #FORWARD_REFERENCE_TYPE_MASK} + * and {@link #FORWARD_REFERENCE_HANDLE_MASK}. + *
+ * + *

For instance, for an ifnull instruction at bytecode offset x, 'sourceInsnBytecodeOffset' is + * equal to x, and 'reference' is of type {@link #FORWARD_REFERENCE_TYPE_SHORT} with value x + 1 + * (because the ifnull instruction uses a 2 bytes bytecode offset operand stored one byte after + * the start of the instruction itself). For the default case of a lookupswitch instruction at + * bytecode offset x, 'sourceInsnBytecodeOffset' is equal to x, and 'reference' is of type {@link + * #FORWARD_REFERENCE_TYPE_WIDE} with value between x + 1 and x + 4 (because the lookupswitch + * instruction uses a 4 bytes bytecode offset operand stored one to four bytes after the start of + * the instruction itself). + */ + private int[] forwardReferences; + + // ----------------------------------------------------------------------------------------------- + + // Fields for the control flow and data flow graph analysis algorithms (used to compute the + // maximum stack size or the stack map frames). A control flow graph contains one node per "basic + // block", and one edge per "jump" from one basic block to another. Each node (i.e., each basic + // block) is represented with the Label object that corresponds to the first instruction of this + // basic block. Each node also stores the list of its successors in the graph, as a linked list of + // Edge objects. + // + // The control flow analysis algorithms used to compute the maximum stack size or the stack map + // frames are similar and use two steps. The first step, during the visit of each instruction, + // builds information about the state of the local variables and the operand stack at the end of + // each basic block, called the "output frame", relatively to the frame state at the + // beginning of the basic block, which is called the "input frame", and which is unknown + // during this step. The second step, in {@link MethodWriter#computeAllFrames} and {@link + // MethodWriter#computeMaxStackAndLocal}, is a fix point algorithm + // that computes information about the input frame of each basic block, from the input state of + // the first basic block (known from the method signature), and by the using the previously + // computed relative output frames. + // + // The algorithm used to compute the maximum stack size only computes the relative output and + // absolute input stack heights, while the algorithm used to compute stack map frames computes + // relative output frames and absolute input frames. + + /** + * The number of elements in the input stack of the basic block corresponding to this label. This + * field is computed in {@link MethodWriter#computeMaxStackAndLocal}. + */ + short inputStackSize; + + /** + * The number of elements in the output stack, at the end of the basic block corresponding to this + * label. This field is only computed for basic blocks that end with a RET instruction. + */ + short outputStackSize; + + /** + * The maximum height reached by the output stack, relatively to the top of the input stack, in + * the basic block corresponding to this label. This maximum is always positive or {@literal + * null}. + */ + short outputStackMax; + + /** + * The id of the subroutine to which this basic block belongs, or 0. If the basic block belongs to + * several subroutines, this is the id of the "oldest" subroutine that contains it (with the + * convention that a subroutine calling another one is "older" than the callee). This field is + * computed in {@link MethodWriter#computeMaxStackAndLocal}, if the method contains JSR + * instructions. + */ + short subroutineId; + + /** + * The input and output stack map frames of the basic block corresponding to this label. This + * field is only used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} or {@link + * MethodWriter#COMPUTE_INSERTED_FRAMES} option is used. + */ + Frame frame; + + /** + * The successor of this label, in the order they are visited in {@link MethodVisitor#visitLabel}. + * This linked list does not include labels used for debug info only. If the {@link + * MethodWriter#COMPUTE_ALL_FRAMES} or {@link MethodWriter#COMPUTE_INSERTED_FRAMES} option is used + * then it does not contain either successive labels that denote the same bytecode offset (in this + * case only the first label appears in this list). + */ + Label nextBasicBlock; + + /** + * The outgoing edges of the basic block corresponding to this label, in the control flow graph of + * its method. These edges are stored in a linked list of {@link Edge} objects, linked to each + * other by their {@link Edge#nextEdge} field. + */ + Edge outgoingEdges; + + /** + * The next element in the list of labels to which this label belongs, or {@literal null} if it + * does not belong to any list. All lists of labels must end with the {@link #EMPTY_LIST} + * sentinel, in order to ensure that this field is null if and only if this label does not belong + * to a list of labels. Note that there can be several lists of labels at the same time, but that + * a label can belong to at most one list at a time (unless some lists share a common tail, but + * this is not used in practice). + * + *

List of labels are used in {@link MethodWriter#computeAllFrames} and {@link + * MethodWriter#computeMaxStackAndLocal} to compute stack map frames and the maximum stack size, + * respectively, as well as in {@link #markSubroutine} and {@link #addSubroutineRetSuccessors} to + * compute the basic blocks belonging to subroutines and their outgoing edges. Outside of these + * methods, this field should be null (this property is a precondition and a postcondition of + * these methods). + */ + Label nextListElement; + + // ----------------------------------------------------------------------------------------------- + // Constructor and accessors + // ----------------------------------------------------------------------------------------------- + + /** Constructs a new label. */ + public Label() { + // Nothing to do. + } + + /** + * Returns the bytecode offset corresponding to this label. This offset is computed from the start + * of the method's bytecode. This method is intended for {@link Attribute} sub classes, and is + * normally not needed by class generators or adapters. + * + * @return the bytecode offset corresponding to this label. + * @throws IllegalStateException if this label is not resolved yet. + */ + public int getOffset() { + if ((flags & FLAG_RESOLVED) == 0) { + throw new IllegalStateException("Label offset position has not been resolved yet"); + } + return bytecodeOffset; + } + + /** + * Returns the "canonical" {@link Label} instance corresponding to this label's bytecode offset, + * if known, otherwise the label itself. The canonical instance is the first label (in the order + * of their visit by {@link MethodVisitor#visitLabel}) corresponding to this bytecode offset. It + * cannot be known for labels which have not been visited yet. + * + *

This method should only be used when the {@link MethodWriter#COMPUTE_ALL_FRAMES} option + * is used. + * + * @return the label itself if {@link #frame} is null, otherwise the Label's frame owner. This + * corresponds to the "canonical" label instance described above thanks to the way the label + * frame is set in {@link MethodWriter#visitLabel}. + */ + final Label getCanonicalInstance() { + return frame == null ? this : frame.owner; + } + + // ----------------------------------------------------------------------------------------------- + // Methods to manage line numbers + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a source line number corresponding to this label. + * + * @param lineNumber a source line number (which should be strictly positive). + */ + final void addLineNumber(final int lineNumber) { + if ((flags & FLAG_LINE_NUMBER) == 0) { + flags |= FLAG_LINE_NUMBER; + this.lineNumber = (short) lineNumber; + } else { + if (otherLineNumbers == null) { + otherLineNumbers = new int[LINE_NUMBERS_CAPACITY_INCREMENT]; + } + int otherLineNumberIndex = ++otherLineNumbers[0]; + if (otherLineNumberIndex >= otherLineNumbers.length) { + int[] newLineNumbers = new int[otherLineNumbers.length + LINE_NUMBERS_CAPACITY_INCREMENT]; + System.arraycopy(otherLineNumbers, 0, newLineNumbers, 0, otherLineNumbers.length); + otherLineNumbers = newLineNumbers; + } + otherLineNumbers[otherLineNumberIndex] = lineNumber; + } + } + + /** + * Makes the given visitor visit this label and its source line numbers, if applicable. + * + * @param methodVisitor a method visitor. + * @param visitLineNumbers whether to visit of the label's source line numbers, if any. + */ + final void accept(final MethodVisitor methodVisitor, final boolean visitLineNumbers) { + methodVisitor.visitLabel(this); + if (visitLineNumbers && (flags & FLAG_LINE_NUMBER) != 0) { + methodVisitor.visitLineNumber(lineNumber & 0xFFFF, this); + if (otherLineNumbers != null) { + for (int i = 1; i <= otherLineNumbers[0]; ++i) { + methodVisitor.visitLineNumber(otherLineNumbers[i], this); + } + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to compute offsets and to manage forward references + // ----------------------------------------------------------------------------------------------- + + /** + * Puts a reference to this label in the bytecode of a method. If the bytecode offset of the label + * is known, the relative bytecode offset between the label and the instruction referencing it is + * computed and written directly. Otherwise, a null relative offset is written and a new forward + * reference is declared for this label. + * + * @param code the bytecode of the method. This is where the reference is appended. + * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the + * reference to be appended. + * @param wideReference whether the reference must be stored in 4 bytes (instead of 2 bytes). + */ + final void put( + final ByteVector code, final int sourceInsnBytecodeOffset, final boolean wideReference) { + if ((flags & FLAG_RESOLVED) == 0) { + if (wideReference) { + addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_WIDE, code.length); + code.putInt(-1); + } else { + addForwardReference(sourceInsnBytecodeOffset, FORWARD_REFERENCE_TYPE_SHORT, code.length); + code.putShort(-1); + } + } else { + if (wideReference) { + code.putInt(bytecodeOffset - sourceInsnBytecodeOffset); + } else { + code.putShort(bytecodeOffset - sourceInsnBytecodeOffset); + } + } + } + + /** + * Puts a reference to this label in the stack map table of a method. If the bytecode + * offset of the label is known, it is written directly. Otherwise, a null relative offset is + * written and a new forward reference is declared for this label. + * + * @param stackMapTableEntries the stack map table where the label offset must be added. + */ + final void put(final ByteVector stackMapTableEntries) { + if ((flags & FLAG_RESOLVED) == 0) { + addForwardReference(0, FORWARD_REFERENCE_TYPE_STACK_MAP, stackMapTableEntries.length); + } + stackMapTableEntries.putShort(bytecodeOffset); + } + + /** + * Adds a forward reference to this label. This method must be called only for a true forward + * reference, i.e. only if this label is not resolved yet. For backward references, the relative + * bytecode offset of the reference can be, and must be, computed and stored directly. + * + * @param sourceInsnBytecodeOffset the bytecode offset of the instruction that contains the + * reference stored at referenceHandle. + * @param referenceType either {@link #FORWARD_REFERENCE_TYPE_SHORT} or {@link + * #FORWARD_REFERENCE_TYPE_WIDE}. + * @param referenceHandle the offset in the bytecode where the forward reference value must be + * stored. + */ + private void addForwardReference( + final int sourceInsnBytecodeOffset, final int referenceType, final int referenceHandle) { + if (forwardReferences == null) { + forwardReferences = new int[FORWARD_REFERENCES_CAPACITY_INCREMENT]; + } + int lastElementIndex = forwardReferences[0]; + if (lastElementIndex + 2 >= forwardReferences.length) { + int[] newValues = new int[forwardReferences.length + FORWARD_REFERENCES_CAPACITY_INCREMENT]; + System.arraycopy(forwardReferences, 0, newValues, 0, forwardReferences.length); + forwardReferences = newValues; + } + forwardReferences[++lastElementIndex] = sourceInsnBytecodeOffset; + forwardReferences[++lastElementIndex] = referenceType | referenceHandle; + forwardReferences[0] = lastElementIndex; + } + + /** + * Sets the bytecode offset of this label to the given value and resolves the forward references + * to this label, if any. This method must be called when this label is added to the bytecode of + * the method, i.e. when its bytecode offset becomes known. This method fills in the blanks that + * where left in the bytecode (and optionally in the stack map table) by each forward reference + * previously added to this label. + * + * @param code the bytecode of the method. + * @param stackMapTableEntries the 'entries' array of the StackMapTable code attribute of the + * method. Maybe {@literal null}. + * @param bytecodeOffset the bytecode offset of this label. + * @return {@literal true} if a blank that was left for this label was too small to store the + * offset. In such a case the corresponding jump instruction is replaced with an equivalent + * ASM specific instruction using an unsigned two bytes offset. These ASM specific + * instructions are later replaced with standard bytecode instructions with wider offsets (4 + * bytes instead of 2), in ClassReader. + */ + final boolean resolve( + final byte[] code, final ByteVector stackMapTableEntries, final int bytecodeOffset) { + this.flags |= FLAG_RESOLVED; + this.bytecodeOffset = bytecodeOffset; + if (forwardReferences == null) { + return false; + } + boolean hasAsmInstructions = false; + for (int i = forwardReferences[0]; i > 0; i -= 2) { + final int sourceInsnBytecodeOffset = forwardReferences[i - 1]; + final int reference = forwardReferences[i]; + final int relativeOffset = bytecodeOffset - sourceInsnBytecodeOffset; + int handle = reference & FORWARD_REFERENCE_HANDLE_MASK; + if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_SHORT) { + if (relativeOffset < Short.MIN_VALUE || relativeOffset > Short.MAX_VALUE) { + // Change the opcode of the jump instruction, in order to be able to find it later in + // ClassReader. These ASM specific opcodes are similar to jump instruction opcodes, except + // that the 2 bytes offset is unsigned (and can therefore represent values from 0 to + // 65535, which is sufficient since the size of a method is limited to 65535 bytes). + int opcode = code[sourceInsnBytecodeOffset] & 0xFF; + if (opcode < Opcodes.IFNULL) { + // Change IFEQ ... JSR to ASM_IFEQ ... ASM_JSR. + code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_OPCODE_DELTA); + } else { + // Change IFNULL and IFNONNULL to ASM_IFNULL and ASM_IFNONNULL. + code[sourceInsnBytecodeOffset] = (byte) (opcode + Constants.ASM_IFNULL_OPCODE_DELTA); + } + hasAsmInstructions = true; + } + code[handle++] = (byte) (relativeOffset >>> 8); + code[handle] = (byte) relativeOffset; + } else if ((reference & FORWARD_REFERENCE_TYPE_MASK) == FORWARD_REFERENCE_TYPE_WIDE) { + code[handle++] = (byte) (relativeOffset >>> 24); + code[handle++] = (byte) (relativeOffset >>> 16); + code[handle++] = (byte) (relativeOffset >>> 8); + code[handle] = (byte) relativeOffset; + } else { + stackMapTableEntries.data[handle++] = (byte) (bytecodeOffset >>> 8); + stackMapTableEntries.data[handle] = (byte) bytecodeOffset; + } + } + return hasAsmInstructions; + } + + // ----------------------------------------------------------------------------------------------- + // Methods related to subroutines + // ----------------------------------------------------------------------------------------------- + + /** + * Finds the basic blocks that belong to the subroutine starting with the basic block + * corresponding to this label, and marks these blocks as belonging to this subroutine. This + * method follows the control flow graph to find all the blocks that are reachable from the + * current basic block WITHOUT following any jsr target. + * + *

Note: a precondition and postcondition of this method is that all labels must have a null + * {@link #nextListElement}. + * + * @param subroutineId the id of the subroutine starting with the basic block corresponding to + * this label. + */ + final void markSubroutine(final short subroutineId) { + // Data flow algorithm: put this basic block in a list of blocks to process (which are blocks + // belonging to subroutine subroutineId) and, while there are blocks to process, remove one from + // the list, mark it as belonging to the subroutine, and add its successor basic blocks in the + // control flow graph to the list of blocks to process (if not already done). + Label listOfBlocksToProcess = this; + listOfBlocksToProcess.nextListElement = EMPTY_LIST; + while (listOfBlocksToProcess != EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + basicBlock.nextListElement = null; + + // If it is not already marked as belonging to a subroutine, mark it as belonging to + // subroutineId and add its successors to the list of blocks to process (unless already done). + if (basicBlock.subroutineId == 0) { + basicBlock.subroutineId = subroutineId; + listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess); + } + } + } + + /** + * Finds the basic blocks that end a subroutine starting with the basic block corresponding to + * this label and, for each one of them, adds an outgoing edge to the basic block following the + * given subroutine call. In other words, completes the control flow graph by adding the edges + * corresponding to the return from this subroutine, when called from the given caller basic + * block. + * + *

Note: a precondition and postcondition of this method is that all labels must have a null + * {@link #nextListElement}. + * + * @param subroutineCaller a basic block that ends with a jsr to the basic block corresponding to + * this label. This label is supposed to correspond to the start of a subroutine. + */ + final void addSubroutineRetSuccessors(final Label subroutineCaller) { + // Data flow algorithm: put this basic block in a list blocks to process (which are blocks + // belonging to a subroutine starting with this label) and, while there are blocks to process, + // remove one from the list, put it in a list of blocks that have been processed, add a return + // edge to the successor of subroutineCaller if applicable, and add its successor basic blocks + // in the control flow graph to the list of blocks to process (if not already done). + Label listOfProcessedBlocks = EMPTY_LIST; + Label listOfBlocksToProcess = this; + listOfBlocksToProcess.nextListElement = EMPTY_LIST; + while (listOfBlocksToProcess != EMPTY_LIST) { + // Move a basic block from the list of blocks to process to the list of processed blocks. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = basicBlock.nextListElement; + basicBlock.nextListElement = listOfProcessedBlocks; + listOfProcessedBlocks = basicBlock; + + // Add an edge from this block to the successor of the caller basic block, if this block is + // the end of a subroutine and if this block and subroutineCaller do not belong to the same + // subroutine. + if ((basicBlock.flags & FLAG_SUBROUTINE_END) != 0 + && basicBlock.subroutineId != subroutineCaller.subroutineId) { + basicBlock.outgoingEdges = + new Edge( + basicBlock.outputStackSize, + // By construction, the first outgoing edge of a basic block that ends with a jsr + // instruction leads to the jsr continuation block, i.e. where execution continues + // when ret is called (see {@link #FLAG_SUBROUTINE_CALLER}). + subroutineCaller.outgoingEdges.successor, + basicBlock.outgoingEdges); + } + // Add its successors to the list of blocks to process. Note that {@link #pushSuccessors} does + // not push basic blocks which are already in a list. Here this means either in the list of + // blocks to process, or in the list of already processed blocks. This second list is + // important to make sure we don't reprocess an already processed block. + listOfBlocksToProcess = basicBlock.pushSuccessors(listOfBlocksToProcess); + } + // Reset the {@link #nextListElement} of all the basic blocks that have been processed to null, + // so that this method can be called again with a different subroutine or subroutine caller. + while (listOfProcessedBlocks != EMPTY_LIST) { + Label newListOfProcessedBlocks = listOfProcessedBlocks.nextListElement; + listOfProcessedBlocks.nextListElement = null; + listOfProcessedBlocks = newListOfProcessedBlocks; + } + } + + /** + * Adds the successors of this label in the method's control flow graph (except those + * corresponding to a jsr target, and those already in a list of labels) to the given list of + * blocks to process, and returns the new list. + * + * @param listOfLabelsToProcess a list of basic blocks to process, linked together with their + * {@link #nextListElement} field. + * @return the new list of blocks to process. + */ + private Label pushSuccessors(final Label listOfLabelsToProcess) { + Label newListOfLabelsToProcess = listOfLabelsToProcess; + Edge outgoingEdge = outgoingEdges; + while (outgoingEdge != null) { + // By construction, the second outgoing edge of a basic block that ends with a jsr instruction + // leads to the jsr target (see {@link #FLAG_SUBROUTINE_CALLER}). + boolean isJsrTarget = + (flags & Label.FLAG_SUBROUTINE_CALLER) != 0 && outgoingEdge == outgoingEdges.nextEdge; + if (!isJsrTarget && outgoingEdge.successor.nextListElement == null) { + // Add this successor to the list of blocks to process, if it does not already belong to a + // list of labels. + outgoingEdge.successor.nextListElement = newListOfLabelsToProcess; + newListOfLabelsToProcess = outgoingEdge.successor; + } + outgoingEdge = outgoingEdge.nextEdge; + } + return newListOfLabelsToProcess; + } + + // ----------------------------------------------------------------------------------------------- + // Overridden Object methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns a string representation of this label. + * + * @return a string representation of this label. + */ + @Override + public String toString() { + return "L" + System.identityHashCode(this); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodTooLargeException.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodTooLargeException.class new file mode 100644 index 00000000..a877d4c0 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodTooLargeException.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodTooLargeException.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodTooLargeException.java new file mode 100644 index 00000000..a2c9cee1 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodTooLargeException.java @@ -0,0 +1,131 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * Exception thrown when the Code attribute of a method produced by a {@link ClassWriter} is too + * large. + * + * @author Jason Zaugg + */ +public final class MethodTooLargeException extends IndexOutOfBoundsException { + private static final long serialVersionUID = 6807380416709738314L; + + private final String className; + private final String methodName; + private final String descriptor; + private final int codeSize; + + /** + * Constructs a new {@link MethodTooLargeException}. + * + * @param className the internal name of the owner class (see {@link Type#getInternalName()}). + * @param methodName the name of the method. + * @param descriptor the descriptor of the method. + * @param codeSize the size of the method's Code attribute, in bytes. + */ + public MethodTooLargeException( + final String className, + final String methodName, + final String descriptor, + final int codeSize) { + super("Method too large: " + className + "." + methodName + " " + descriptor); + this.className = className; + this.methodName = methodName; + this.descriptor = descriptor; + this.codeSize = codeSize; + } + + /** + * Returns the internal name of the owner class. + * + * @return the internal name of the owner class (see {@link Type#getInternalName()}). + */ + public String getClassName() { + return className; + } + + /** + * Returns the name of the method. + * + * @return the name of the method. + */ + public String getMethodName() { + return methodName; + } + + /** + * Returns the descriptor of the method. + * + * @return the descriptor of the method. + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Returns the size of the method's Code attribute, in bytes. + * + * @return the size of the method's Code attribute, in bytes. + */ + public int getCodeSize() { + return codeSize; + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodVisitor.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodVisitor.class new file mode 100644 index 00000000..15b1796f Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodVisitor.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodVisitor.java new file mode 100644 index 00000000..08c3adb2 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodVisitor.java @@ -0,0 +1,827 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A visitor to visit a Java method. The methods of this class must be called in the following + * order: ( {@code visitParameter} )* [ {@code visitAnnotationDefault} ] ( {@code visitAnnotation} | + * {@code visitAnnotableParameterCount} | {@code visitParameterAnnotation} | {@code + * visitTypeAnnotation} | {@code visitAttribute} )* [ {@code visitCode} ( {@code visitFrame} | + * {@code visitXInsn} | {@code visitLabel} | {@code visitInsnAnnotation} | {@code + * visitTryCatchBlock} | {@code visitTryCatchAnnotation} | {@code visitLocalVariable} | {@code + * visitLocalVariableAnnotation} | {@code visitLineNumber} )* {@code visitMaxs} ] {@code visitEnd}. + * In addition, the {@code visitXInsn} and {@code visitLabel} methods must be called in the + * sequential order of the bytecode instructions of the visited code, {@code visitInsnAnnotation} + * must be called after the annotated instruction, {@code visitTryCatchBlock} must be called + * before the labels passed as arguments have been visited, {@code + * visitTryCatchBlockAnnotation} must be called after the corresponding try catch block has + * been visited, and the {@code visitLocalVariable}, {@code visitLocalVariableAnnotation} and {@code + * visitLineNumber} methods must be called after the labels passed as arguments have been + * visited. + * + * @author Eric Bruneton + */ +public abstract class MethodVisitor { + + private static final String REQUIRES_ASM5 = "This feature requires ASM5"; + + /** + * The ASM API version implemented by this visitor. The value of this field must be one of the + * {@code ASM}x values in {@link Opcodes}. + */ + protected final int api; + + /** + * The method visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + protected MethodVisitor mv; + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + */ + protected MethodVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link MethodVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + * @param methodVisitor the method visitor to which this visitor must delegate method calls. May + * be null. + */ + protected MethodVisitor(final int api, final MethodVisitor methodVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + this.mv = methodVisitor; + } + + /** + * The method visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the method visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public MethodVisitor getDelegate() { + return mv; + } + + // ----------------------------------------------------------------------------------------------- + // Parameters, annotations and non standard attributes + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a parameter of this method. + * + * @param name parameter name or {@literal null} if none is provided. + * @param access the parameter's access flags, only {@code ACC_FINAL}, {@code ACC_SYNTHETIC} + * or/and {@code ACC_MANDATED} are allowed (see {@link Opcodes}). + */ + public void visitParameter(final String name, final int access) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + mv.visitParameter(name, access); + } + } + + /** + * Visits the default value of this annotation interface method. + * + * @return a visitor to the visit the actual default value of this annotation interface method, or + * {@literal null} if this visitor is not interested in visiting this default value. The + * 'name' parameters passed to the methods of this annotation visitor are ignored. Moreover, + * exactly one visit method must be called on this annotation visitor, followed by visitEnd. + */ + public AnnotationVisitor visitAnnotationDefault() { + if (mv != null) { + return mv.visitAnnotationDefault(); + } + return null; + } + + /** + * Visits an annotation of this method. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (mv != null) { + return mv.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the method signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#METHOD_TYPE_PARAMETER}, {@link + * TypeReference#METHOD_TYPE_PARAMETER_BOUND}, {@link TypeReference#METHOD_RETURN}, {@link + * TypeReference#METHOD_RECEIVER}, {@link TypeReference#METHOD_FORMAL_PARAMETER} or {@link + * TypeReference#THROWS}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits the number of method parameters that can have annotations. By default (i.e. when this + * method is not called), all the method parameters defined by the method descriptor can have + * annotations. + * + * @param parameterCount the number of method parameters than can have annotations. This number + * must be less or equal than the number of parameter types in the method descriptor. It can + * be strictly less when a method has synthetic parameters and when these parameters are + * ignored when computing parameter indices for the purpose of parameter annotations (see + * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). + * @param visible {@literal true} to define the number of method parameters that can have + * annotations visible at runtime, {@literal false} to define the number of method parameters + * that can have annotations invisible at runtime. + */ + public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { + if (mv != null) { + mv.visitAnnotableParameterCount(parameterCount, visible); + } + } + + /** + * Visits an annotation of a parameter this method. + * + * @param parameter the parameter index. This index must be strictly smaller than the number of + * parameters in the method descriptor, and strictly smaller than the parameter count + * specified in {@link #visitAnnotableParameterCount}. Important note: a parameter index i + * is not required to correspond to the i'th parameter descriptor in the method + * descriptor, in particular in case of synthetic parameters (see + * https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.18). + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitParameterAnnotation( + final int parameter, final String descriptor, final boolean visible) { + if (mv != null) { + return mv.visitParameterAnnotation(parameter, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of this method. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (mv != null) { + mv.visitAttribute(attribute); + } + } + + /** Starts the visit of the method's code, if any (i.e. non abstract method). */ + public void visitCode() { + if (mv != null) { + mv.visitCode(); + } + } + + /** + * Visits the current state of the local variables and operand stack elements. This method must(*) + * be called just before any instruction i that follows an unconditional branch + * instruction such as GOTO or THROW, that is the target of a jump instruction, or that starts an + * exception handler block. The visited types must describe the values of the local variables and + * of the operand stack elements just before i is executed.
+ *
+ * (*) this is mandatory only for classes whose version is greater than or equal to {@link + * Opcodes#V1_6}.
+ *
+ * The frames of a method must be given either in expanded form, or in compressed form (all frames + * must use the same format, i.e. you must not mix expanded and compressed frames within a single + * method): + * + *

    + *
  • In expanded form, all frames must have the F_NEW type. + *
  • In compressed form, frames are basically "deltas" from the state of the previous frame: + *
      + *
    • {@link Opcodes#F_SAME} representing frame with exactly the same locals as the + * previous frame and with the empty stack. + *
    • {@link Opcodes#F_SAME1} representing frame with exactly the same locals as the + * previous frame and with single value on the stack ( numStack is 1 and + * stack[0] contains value for the type of the stack item). + *
    • {@link Opcodes#F_APPEND} representing frame with current locals are the same as the + * locals in the previous frame, except that additional locals are defined ( + * numLocal is 1, 2 or 3 and local elements contains values + * representing added types). + *
    • {@link Opcodes#F_CHOP} representing frame with current locals are the same as the + * locals in the previous frame, except that the last 1-3 locals are absent and with + * the empty stack (numLocal is 1, 2 or 3). + *
    • {@link Opcodes#F_FULL} representing complete frame data. + *
    + *
+ * + *
+ * In both cases the first frame, corresponding to the method's parameters and access flags, is + * implicit and must not be visited. Also, it is illegal to visit two or more frames for the same + * code location (i.e., at least one instruction must be visited between two calls to visitFrame). + * + * @param type the type of this stack map frame. Must be {@link Opcodes#F_NEW} for expanded + * frames, or {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND}, {@link Opcodes#F_CHOP}, {@link + * Opcodes#F_SAME} or {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for compressed frames. + * @param numLocal the number of local variables in the visited frame. Long and double values + * count for one variable. + * @param local the local variable types in this frame. This array must not be modified. Primitive + * types are represented by {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL} or + * {@link Opcodes#UNINITIALIZED_THIS} (long and double are represented by a single element). + * Reference types are represented by String objects (representing internal names, see {@link + * Type#getInternalName()}), and uninitialized types by Label objects (this label designates + * the NEW instruction that created this uninitialized value). + * @param numStack the number of operand stack elements in the visited frame. Long and double + * values count for one stack element. + * @param stack the operand stack types in this frame. This array must not be modified. Its + * content has the same format as the "local" array. + * @throws IllegalStateException if a frame is visited just after another one, without any + * instruction between the two (unless this frame is a Opcodes#F_SAME frame, in which case it + * is silently ignored). + */ + public void visitFrame( + final int type, + final int numLocal, + final Object[] local, + final int numStack, + final Object[] stack) { + if (mv != null) { + mv.visitFrame(type, numLocal, local, numStack, stack); + } + } + + // ----------------------------------------------------------------------------------------------- + // Normal instructions + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a zero operand instruction. + * + * @param opcode the opcode of the instruction to be visited. This opcode is either NOP, + * ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, + * LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, + * FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, + * AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, + * SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, + * FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, + * LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, + * D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, + * DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, or MONITOREXIT. + */ + public void visitInsn(final int opcode) { + if (mv != null) { + mv.visitInsn(opcode); + } + } + + /** + * Visits an instruction with a single int operand. + * + * @param opcode the opcode of the instruction to be visited. This opcode is either BIPUSH, SIPUSH + * or NEWARRAY. + * @param operand the operand of the instruction to be visited.
+ * When opcode is BIPUSH, operand value should be between Byte.MIN_VALUE and Byte.MAX_VALUE. + *
+ * When opcode is SIPUSH, operand value should be between Short.MIN_VALUE and Short.MAX_VALUE. + *
+ * When opcode is NEWARRAY, operand value should be one of {@link Opcodes#T_BOOLEAN}, {@link + * Opcodes#T_CHAR}, {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE}, {@link Opcodes#T_BYTE}, + * {@link Opcodes#T_SHORT}, {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}. + */ + public void visitIntInsn(final int opcode, final int operand) { + if (mv != null) { + mv.visitIntInsn(opcode, operand); + } + } + + /** + * Visits a local variable instruction. A local variable instruction is an instruction that loads + * or stores the value of a local variable. + * + * @param opcode the opcode of the local variable instruction to be visited. This opcode is either + * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. + * @param varIndex the operand of the instruction to be visited. This operand is the index of a + * local variable. + */ + public void visitVarInsn(final int opcode, final int varIndex) { + if (mv != null) { + mv.visitVarInsn(opcode, varIndex); + } + } + + /** + * Visits a type instruction. A type instruction is an instruction that takes the internal name of + * a class as parameter (see {@link Type#getInternalName()}). + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either NEW, + * ANEWARRAY, CHECKCAST or INSTANCEOF. + * @param type the operand of the instruction to be visited. This operand must be the internal + * name of an object or array class (see {@link Type#getInternalName()}). + */ + public void visitTypeInsn(final int opcode, final String type) { + if (mv != null) { + mv.visitTypeInsn(opcode, type); + } + } + + /** + * Visits a field instruction. A field instruction is an instruction that loads or stores the + * value of a field of an object. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD. + * @param owner the internal name of the field's owner class (see {@link Type#getInternalName()}). + * @param name the field's name. + * @param descriptor the field's descriptor (see {@link Type}). + */ + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + if (mv != null) { + mv.visitFieldInsn(opcode, owner, name, descriptor); + } + } + + /** + * Visits a method instruction. A method instruction is an instruction that invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @deprecated use {@link #visitMethodInsn(int, String, String, String, boolean)} instead. + */ + @Deprecated + public void visitMethodInsn( + final int opcode, final String owner, final String name, final String descriptor) { + int opcodeAndSource = opcode | (api < Opcodes.ASM5 ? Opcodes.SOURCE_DEPRECATED : 0); + visitMethodInsn(opcodeAndSource, owner, name, descriptor, opcode == Opcodes.INVOKEINTERFACE); + } + + /** + * Visits a method instruction. A method instruction is an instruction that invokes a method. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either + * INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @param owner the internal name of the method's owner class (see {@link + * Type#getInternalName()}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param isInterface if the method's owner class is an interface. + */ + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) { + if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) { + throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); + } + visitMethodInsn(opcode, owner, name, descriptor); + return; + } + if (mv != null) { + mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface); + } + } + + /** + * Visits an invokedynamic instruction. + * + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param bootstrapMethodHandle the bootstrap method. + * @param bootstrapMethodArguments the bootstrap method constant arguments. Each argument must be + * an {@link Integer}, {@link Float}, {@link Long}, {@link Double}, {@link String}, {@link + * Type}, {@link Handle} or {@link ConstantDynamic} value. This method is allowed to modify + * the content of the array so a caller should expect that this array may change. + */ + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + } + + /** + * Visits a jump instruction. A jump instruction is an instruction that may jump to another + * instruction. + * + * @param opcode the opcode of the type instruction to be visited. This opcode is either IFEQ, + * IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, + * IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL. + * @param label the operand of the instruction to be visited. This operand is a label that + * designates the instruction to which the jump instruction may jump. + */ + public void visitJumpInsn(final int opcode, final Label label) { + if (mv != null) { + mv.visitJumpInsn(opcode, label); + } + } + + /** + * Visits a label. A label designates the instruction that will be visited just after it. + * + * @param label a {@link Label} object. + */ + public void visitLabel(final Label label) { + if (mv != null) { + mv.visitLabel(label); + } + } + + // ----------------------------------------------------------------------------------------------- + // Special instructions + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a LDC instruction. Note that new constant types may be added in future versions of the + * Java Virtual Machine. To easily detect new constant types, implementations of this method + * should check for unexpected constant types, like this: + * + *
+      * if (cst instanceof Integer) {
+      *     // ...
+      * } else if (cst instanceof Float) {
+      *     // ...
+      * } else if (cst instanceof Long) {
+      *     // ...
+      * } else if (cst instanceof Double) {
+      *     // ...
+      * } else if (cst instanceof String) {
+      *     // ...
+      * } else if (cst instanceof Type) {
+      *     int sort = ((Type) cst).getSort();
+      *     if (sort == Type.OBJECT) {
+      *         // ...
+      *     } else if (sort == Type.ARRAY) {
+      *         // ...
+      *     } else if (sort == Type.METHOD) {
+      *         // ...
+      *     } else {
+      *         // throw an exception
+      *     }
+      * } else if (cst instanceof Handle) {
+      *     // ...
+      * } else if (cst instanceof ConstantDynamic) {
+      *     // ...
+      * } else {
+      *     // throw an exception
+      * }
+      * 
+ * + * @param value the constant to be loaded on the stack. This parameter must be a non null {@link + * Integer}, a {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, a {@link + * Type} of OBJECT or ARRAY sort for {@code .class} constants, for classes whose version is + * 49, a {@link Type} of METHOD sort for MethodType, a {@link Handle} for MethodHandle + * constants, for classes whose version is 51 or a {@link ConstantDynamic} for a constant + * dynamic for classes whose version is 55. + */ + public void visitLdcInsn(final Object value) { + if (api < Opcodes.ASM5 + && (value instanceof Handle + || (value instanceof Type && ((Type) value).getSort() == Type.METHOD))) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (api < Opcodes.ASM7 && value instanceof ConstantDynamic) { + throw new UnsupportedOperationException("This feature requires ASM7"); + } + if (mv != null) { + mv.visitLdcInsn(value); + } + } + + /** + * Visits an IINC instruction. + * + * @param varIndex index of the local variable to be incremented. + * @param increment amount to increment the local variable by. + */ + public void visitIincInsn(final int varIndex, final int increment) { + if (mv != null) { + mv.visitIincInsn(varIndex, increment); + } + } + + /** + * Visits a TABLESWITCH instruction. + * + * @param min the minimum key value. + * @param max the maximum key value. + * @param dflt beginning of the default handler block. + * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the + * handler block for the {@code min + i} key. + */ + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + if (mv != null) { + mv.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + /** + * Visits a LOOKUPSWITCH instruction. + * + * @param dflt beginning of the default handler block. + * @param keys the values of the keys. + * @param labels beginnings of the handler blocks. {@code labels[i]} is the beginning of the + * handler block for the {@code keys[i]} key. + */ + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + if (mv != null) { + mv.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + /** + * Visits a MULTIANEWARRAY instruction. + * + * @param descriptor an array type descriptor (see {@link Type}). + * @param numDimensions the number of dimensions of the array to allocate. + */ + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + if (mv != null) { + mv.visitMultiANewArrayInsn(descriptor, numDimensions); + } + } + + /** + * Visits an annotation on an instruction. This method must be called just after the + * annotated instruction. It can be called several times for the same instruction. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#INSTANCEOF}, {@link TypeReference#NEW}, {@link + * TypeReference#CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE}, {@link + * TypeReference#CAST}, {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link + * TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link + * TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitInsnAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitInsnAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + // ----------------------------------------------------------------------------------------------- + // Exceptions table entries, debug information, max stack and max locals + // ----------------------------------------------------------------------------------------------- + + /** + * Visits a try catch block. + * + * @param start the beginning of the exception handler's scope (inclusive). + * @param end the end of the exception handler's scope (exclusive). + * @param handler the beginning of the exception handler's code. + * @param type the internal name of the type of exceptions handled by the handler (see {@link + * Type#getInternalName()}), or {@literal null} to catch any exceptions (for "finally" + * blocks). + * @throws IllegalArgumentException if one of the labels has already been visited by this visitor + * (by the {@link #visitLabel} method). + */ + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + if (mv != null) { + mv.visitTryCatchBlock(start, end, handler, type); + } + } + + /** + * Visits an annotation on an exception handler type. This method must be called after the + * {@link #visitTryCatchBlock} for the annotated exception handler. It can be called several times + * for the same exception handler. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#EXCEPTION_PARAMETER}. See {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTryCatchAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a local variable declaration. + * + * @param name the name of a local variable. + * @param descriptor the type descriptor of this local variable. + * @param signature the type signature of this local variable. May be {@literal null} if the local + * variable type does not use generic types. + * @param start the first instruction corresponding to the scope of this local variable + * (inclusive). + * @param end the last instruction corresponding to the scope of this local variable (exclusive). + * @param index the local variable's index. + * @throws IllegalArgumentException if one of the labels has not already been visited by this + * visitor (by the {@link #visitLabel} method). + */ + public void visitLocalVariable( + final String name, + final String descriptor, + final String signature, + final Label start, + final Label end, + final int index) { + if (mv != null) { + mv.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + + /** + * Visits an annotation on a local variable type. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE}. See {@link + * TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param start the fist instructions corresponding to the continuous ranges that make the scope + * of this local variable (inclusive). + * @param end the last instructions corresponding to the continuous ranges that make the scope of + * this local variable (exclusive). This array must have the same size as the 'start' array. + * @param index the local variable's index in each range. This array must have the same size as + * the 'start' array. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitLocalVariableAnnotation( + final int typeRef, + final TypePath typePath, + final Label[] start, + final Label[] end, + final int[] index, + final String descriptor, + final boolean visible) { + if (api < Opcodes.ASM5) { + throw new UnsupportedOperationException(REQUIRES_ASM5); + } + if (mv != null) { + return mv.visitLocalVariableAnnotation( + typeRef, typePath, start, end, index, descriptor, visible); + } + return null; + } + + /** + * Visits a line number declaration. + * + * @param line a line number. This number refers to the source file from which the class was + * compiled. + * @param start the first instruction corresponding to this line number. + * @throws IllegalArgumentException if {@code start} has not already been visited by this visitor + * (by the {@link #visitLabel} method). + */ + public void visitLineNumber(final int line, final Label start) { + if (mv != null) { + mv.visitLineNumber(line, start); + } + } + + /** + * Visits the maximum stack size and the maximum number of local variables of the method. + * + * @param maxStack maximum stack size of the method. + * @param maxLocals maximum number of local variables for the method. + */ + public void visitMaxs(final int maxStack, final int maxLocals) { + if (mv != null) { + mv.visitMaxs(maxStack, maxLocals); + } + } + + /** + * Visits the end of the method. This method, which is the last one to be called, is used to + * inform the visitor that all the annotations and attributes of the method have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodWriter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodWriter.class new file mode 100644 index 00000000..7df8abde Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodWriter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodWriter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodWriter.java new file mode 100644 index 00000000..efeffc7b --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/MethodWriter.java @@ -0,0 +1,2426 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A {@link MethodVisitor} that generates a corresponding 'method_info' structure, as defined in the + * Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.6 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +final class MethodWriter extends MethodVisitor { + + /** Indicates that nothing must be computed. */ + static final int COMPUTE_NOTHING = 0; + + /** + * Indicates that the maximum stack size and the maximum number of local variables must be + * computed, from scratch. + */ + static final int COMPUTE_MAX_STACK_AND_LOCAL = 1; + + /** + * Indicates that the maximum stack size and the maximum number of local variables must be + * computed, from the existing stack map frames. This can be done more efficiently than with the + * control flow graph algorithm used for {@link #COMPUTE_MAX_STACK_AND_LOCAL}, by using a linear + * scan of the bytecode instructions. + */ + static final int COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES = 2; + + /** + * Indicates that the stack map frames of type F_INSERT must be computed. The other frames are not + * computed. They should all be of type F_NEW and should be sufficient to compute the content of + * the F_INSERT frames, together with the bytecode instructions between a F_NEW and a F_INSERT + * frame - and without any knowledge of the type hierarchy (by definition of F_INSERT). + */ + static final int COMPUTE_INSERTED_FRAMES = 3; + + /** + * Indicates that all the stack map frames must be computed. In this case the maximum stack size + * and the maximum number of local variables is also computed. + */ + static final int COMPUTE_ALL_FRAMES = 4; + + /** Indicates that {@link #STACK_SIZE_DELTA} is not applicable (not constant or never used). */ + private static final int NA = 0; + + /** + * The stack size variation corresponding to each JVM opcode. The stack size variation for opcode + * 'o' is given by the array element at index 'o'. + * + * @see JVMS 6 + */ + private static final int[] STACK_SIZE_DELTA = { + 0, // nop = 0 (0x0) + 1, // aconst_null = 1 (0x1) + 1, // iconst_m1 = 2 (0x2) + 1, // iconst_0 = 3 (0x3) + 1, // iconst_1 = 4 (0x4) + 1, // iconst_2 = 5 (0x5) + 1, // iconst_3 = 6 (0x6) + 1, // iconst_4 = 7 (0x7) + 1, // iconst_5 = 8 (0x8) + 2, // lconst_0 = 9 (0x9) + 2, // lconst_1 = 10 (0xa) + 1, // fconst_0 = 11 (0xb) + 1, // fconst_1 = 12 (0xc) + 1, // fconst_2 = 13 (0xd) + 2, // dconst_0 = 14 (0xe) + 2, // dconst_1 = 15 (0xf) + 1, // bipush = 16 (0x10) + 1, // sipush = 17 (0x11) + 1, // ldc = 18 (0x12) + NA, // ldc_w = 19 (0x13) + NA, // ldc2_w = 20 (0x14) + 1, // iload = 21 (0x15) + 2, // lload = 22 (0x16) + 1, // fload = 23 (0x17) + 2, // dload = 24 (0x18) + 1, // aload = 25 (0x19) + NA, // iload_0 = 26 (0x1a) + NA, // iload_1 = 27 (0x1b) + NA, // iload_2 = 28 (0x1c) + NA, // iload_3 = 29 (0x1d) + NA, // lload_0 = 30 (0x1e) + NA, // lload_1 = 31 (0x1f) + NA, // lload_2 = 32 (0x20) + NA, // lload_3 = 33 (0x21) + NA, // fload_0 = 34 (0x22) + NA, // fload_1 = 35 (0x23) + NA, // fload_2 = 36 (0x24) + NA, // fload_3 = 37 (0x25) + NA, // dload_0 = 38 (0x26) + NA, // dload_1 = 39 (0x27) + NA, // dload_2 = 40 (0x28) + NA, // dload_3 = 41 (0x29) + NA, // aload_0 = 42 (0x2a) + NA, // aload_1 = 43 (0x2b) + NA, // aload_2 = 44 (0x2c) + NA, // aload_3 = 45 (0x2d) + -1, // iaload = 46 (0x2e) + 0, // laload = 47 (0x2f) + -1, // faload = 48 (0x30) + 0, // daload = 49 (0x31) + -1, // aaload = 50 (0x32) + -1, // baload = 51 (0x33) + -1, // caload = 52 (0x34) + -1, // saload = 53 (0x35) + -1, // istore = 54 (0x36) + -2, // lstore = 55 (0x37) + -1, // fstore = 56 (0x38) + -2, // dstore = 57 (0x39) + -1, // astore = 58 (0x3a) + NA, // istore_0 = 59 (0x3b) + NA, // istore_1 = 60 (0x3c) + NA, // istore_2 = 61 (0x3d) + NA, // istore_3 = 62 (0x3e) + NA, // lstore_0 = 63 (0x3f) + NA, // lstore_1 = 64 (0x40) + NA, // lstore_2 = 65 (0x41) + NA, // lstore_3 = 66 (0x42) + NA, // fstore_0 = 67 (0x43) + NA, // fstore_1 = 68 (0x44) + NA, // fstore_2 = 69 (0x45) + NA, // fstore_3 = 70 (0x46) + NA, // dstore_0 = 71 (0x47) + NA, // dstore_1 = 72 (0x48) + NA, // dstore_2 = 73 (0x49) + NA, // dstore_3 = 74 (0x4a) + NA, // astore_0 = 75 (0x4b) + NA, // astore_1 = 76 (0x4c) + NA, // astore_2 = 77 (0x4d) + NA, // astore_3 = 78 (0x4e) + -3, // iastore = 79 (0x4f) + -4, // lastore = 80 (0x50) + -3, // fastore = 81 (0x51) + -4, // dastore = 82 (0x52) + -3, // aastore = 83 (0x53) + -3, // bastore = 84 (0x54) + -3, // castore = 85 (0x55) + -3, // sastore = 86 (0x56) + -1, // pop = 87 (0x57) + -2, // pop2 = 88 (0x58) + 1, // dup = 89 (0x59) + 1, // dup_x1 = 90 (0x5a) + 1, // dup_x2 = 91 (0x5b) + 2, // dup2 = 92 (0x5c) + 2, // dup2_x1 = 93 (0x5d) + 2, // dup2_x2 = 94 (0x5e) + 0, // swap = 95 (0x5f) + -1, // iadd = 96 (0x60) + -2, // ladd = 97 (0x61) + -1, // fadd = 98 (0x62) + -2, // dadd = 99 (0x63) + -1, // isub = 100 (0x64) + -2, // lsub = 101 (0x65) + -1, // fsub = 102 (0x66) + -2, // dsub = 103 (0x67) + -1, // imul = 104 (0x68) + -2, // lmul = 105 (0x69) + -1, // fmul = 106 (0x6a) + -2, // dmul = 107 (0x6b) + -1, // idiv = 108 (0x6c) + -2, // ldiv = 109 (0x6d) + -1, // fdiv = 110 (0x6e) + -2, // ddiv = 111 (0x6f) + -1, // irem = 112 (0x70) + -2, // lrem = 113 (0x71) + -1, // frem = 114 (0x72) + -2, // drem = 115 (0x73) + 0, // ineg = 116 (0x74) + 0, // lneg = 117 (0x75) + 0, // fneg = 118 (0x76) + 0, // dneg = 119 (0x77) + -1, // ishl = 120 (0x78) + -1, // lshl = 121 (0x79) + -1, // ishr = 122 (0x7a) + -1, // lshr = 123 (0x7b) + -1, // iushr = 124 (0x7c) + -1, // lushr = 125 (0x7d) + -1, // iand = 126 (0x7e) + -2, // land = 127 (0x7f) + -1, // ior = 128 (0x80) + -2, // lor = 129 (0x81) + -1, // ixor = 130 (0x82) + -2, // lxor = 131 (0x83) + 0, // iinc = 132 (0x84) + 1, // i2l = 133 (0x85) + 0, // i2f = 134 (0x86) + 1, // i2d = 135 (0x87) + -1, // l2i = 136 (0x88) + -1, // l2f = 137 (0x89) + 0, // l2d = 138 (0x8a) + 0, // f2i = 139 (0x8b) + 1, // f2l = 140 (0x8c) + 1, // f2d = 141 (0x8d) + -1, // d2i = 142 (0x8e) + 0, // d2l = 143 (0x8f) + -1, // d2f = 144 (0x90) + 0, // i2b = 145 (0x91) + 0, // i2c = 146 (0x92) + 0, // i2s = 147 (0x93) + -3, // lcmp = 148 (0x94) + -1, // fcmpl = 149 (0x95) + -1, // fcmpg = 150 (0x96) + -3, // dcmpl = 151 (0x97) + -3, // dcmpg = 152 (0x98) + -1, // ifeq = 153 (0x99) + -1, // ifne = 154 (0x9a) + -1, // iflt = 155 (0x9b) + -1, // ifge = 156 (0x9c) + -1, // ifgt = 157 (0x9d) + -1, // ifle = 158 (0x9e) + -2, // if_icmpeq = 159 (0x9f) + -2, // if_icmpne = 160 (0xa0) + -2, // if_icmplt = 161 (0xa1) + -2, // if_icmpge = 162 (0xa2) + -2, // if_icmpgt = 163 (0xa3) + -2, // if_icmple = 164 (0xa4) + -2, // if_acmpeq = 165 (0xa5) + -2, // if_acmpne = 166 (0xa6) + 0, // goto = 167 (0xa7) + 1, // jsr = 168 (0xa8) + 0, // ret = 169 (0xa9) + -1, // tableswitch = 170 (0xaa) + -1, // lookupswitch = 171 (0xab) + -1, // ireturn = 172 (0xac) + -2, // lreturn = 173 (0xad) + -1, // freturn = 174 (0xae) + -2, // dreturn = 175 (0xaf) + -1, // areturn = 176 (0xb0) + 0, // return = 177 (0xb1) + NA, // getstatic = 178 (0xb2) + NA, // putstatic = 179 (0xb3) + NA, // getfield = 180 (0xb4) + NA, // putfield = 181 (0xb5) + NA, // invokevirtual = 182 (0xb6) + NA, // invokespecial = 183 (0xb7) + NA, // invokestatic = 184 (0xb8) + NA, // invokeinterface = 185 (0xb9) + NA, // invokedynamic = 186 (0xba) + 1, // new = 187 (0xbb) + 0, // newarray = 188 (0xbc) + 0, // anewarray = 189 (0xbd) + 0, // arraylength = 190 (0xbe) + NA, // athrow = 191 (0xbf) + 0, // checkcast = 192 (0xc0) + 0, // instanceof = 193 (0xc1) + -1, // monitorenter = 194 (0xc2) + -1, // monitorexit = 195 (0xc3) + NA, // wide = 196 (0xc4) + NA, // multianewarray = 197 (0xc5) + -1, // ifnull = 198 (0xc6) + -1, // ifnonnull = 199 (0xc7) + NA, // goto_w = 200 (0xc8) + NA // jsr_w = 201 (0xc9) + }; + + /** Where the constants used in this MethodWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the method_info structure, and those related to attributes are + // ordered as in Section 4.7 of the JVMS. + + /** + * The access_flags field of the method_info JVMS structure. This field can contain ASM specific + * access flags, such as {@link Opcodes#ACC_DEPRECATED}, which are removed when generating the + * ClassFile structure. + */ + private final int accessFlags; + + /** The name_index field of the method_info JVMS structure. */ + private final int nameIndex; + + /** The name of this method. */ + private final String name; + + /** The descriptor_index field of the method_info JVMS structure. */ + private final int descriptorIndex; + + /** The descriptor of this method. */ + private final String descriptor; + + // Code attribute fields and sub attributes: + + /** The max_stack field of the Code attribute. */ + private int maxStack; + + /** The max_locals field of the Code attribute. */ + private int maxLocals; + + /** The 'code' field of the Code attribute. */ + private final ByteVector code = new ByteVector(); + + /** + * The first element in the exception handler list (used to generate the exception_table of the + * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May + * be {@literal null}. + */ + private Handler firstHandler; + + /** + * The last element in the exception handler list (used to generate the exception_table of the + * Code attribute). The next ones can be accessed with the {@link Handler#nextHandler} field. May + * be {@literal null}. + */ + private Handler lastHandler; + + /** The line_number_table_length field of the LineNumberTable code attribute. */ + private int lineNumberTableLength; + + /** The line_number_table array of the LineNumberTable code attribute, or {@literal null}. */ + private ByteVector lineNumberTable; + + /** The local_variable_table_length field of the LocalVariableTable code attribute. */ + private int localVariableTableLength; + + /** + * The local_variable_table array of the LocalVariableTable code attribute, or {@literal null}. + */ + private ByteVector localVariableTable; + + /** The local_variable_type_table_length field of the LocalVariableTypeTable code attribute. */ + private int localVariableTypeTableLength; + + /** + * The local_variable_type_table array of the LocalVariableTypeTable code attribute, or {@literal + * null}. + */ + private ByteVector localVariableTypeTable; + + /** The number_of_entries field of the StackMapTable code attribute. */ + private int stackMapTableNumberOfEntries; + + /** The 'entries' array of the StackMapTable code attribute. */ + private ByteVector stackMapTableEntries; + + /** + * The last runtime visible type annotation of the Code attribute. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastCodeRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of the Code attribute. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastCodeRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of the Code attribute. The next ones can be accessed with the + * {@link Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstCodeAttribute; + + // Other method_info attributes: + + /** The number_of_exceptions field of the Exceptions attribute. */ + private final int numberOfExceptions; + + /** The exception_index_table array of the Exceptions attribute, or {@literal null}. */ + private final int[] exceptionIndexTable; + + /** The signature_index field of the Signature attribute. */ + private final int signatureIndex; + + /** + * The last runtime visible annotation of this method. The previous ones can be accessed with the + * {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this method. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** The number of method parameters that can have runtime visible annotations, or 0. */ + private int visibleAnnotableParameterCount; + + /** + * The runtime visible parameter annotations of this method. Each array element contains the last + * annotation of a parameter (which can be {@literal null} - the previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}. + */ + private AnnotationWriter[] lastRuntimeVisibleParameterAnnotations; + + /** The number of method parameters that can have runtime visible annotations, or 0. */ + private int invisibleAnnotableParameterCount; + + /** + * The runtime invisible parameter annotations of this method. Each array element contains the + * last annotation of a parameter (which can be {@literal null} - the previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field). May be {@literal null}. + */ + private AnnotationWriter[] lastRuntimeInvisibleParameterAnnotations; + + /** + * The last runtime visible type annotation of this method. The previous ones can be accessed with + * the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this method. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** The default_value field of the AnnotationDefault attribute, or {@literal null}. */ + private ByteVector defaultValue; + + /** The parameters_count field of the MethodParameters attribute. */ + private int parametersCount; + + /** The 'parameters' array of the MethodParameters attribute, or {@literal null}. */ + private ByteVector parameters; + + /** + * The first non standard attribute of this method. The next ones can be accessed with the {@link + * Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute}. The {@link + * #putMethodInfo} method writes the attributes in the order defined by this list, i.e. in the + * reverse order specified by the user. + */ + private Attribute firstAttribute; + + // ----------------------------------------------------------------------------------------------- + // Fields used to compute the maximum stack size and number of locals, and the stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Indicates what must be computed. Must be one of {@link #COMPUTE_ALL_FRAMES}, {@link + * #COMPUTE_INSERTED_FRAMES}, {@link COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link + * #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_NOTHING}. + */ + private final int compute; + + /** + * The first basic block of the method. The next ones (in bytecode offset order) can be accessed + * with the {@link Label#nextBasicBlock} field. + */ + private Label firstBasicBlock; + + /** + * The last basic block of the method (in bytecode offset order). This field is updated each time + * a basic block is encountered, and is used to append it at the end of the basic block list. + */ + private Label lastBasicBlock; + + /** + * The current basic block, i.e. the basic block of the last visited instruction. When {@link + * #compute} is equal to {@link #COMPUTE_MAX_STACK_AND_LOCAL} or {@link #COMPUTE_ALL_FRAMES}, this + * field is {@literal null} for unreachable code. When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES} or {@link #COMPUTE_INSERTED_FRAMES}, this field stays + * unchanged throughout the whole method (i.e. the whole code is seen as a single basic block; + * indeed, the existing frames are sufficient by hypothesis to compute any intermediate frame - + * and the maximum stack size as well - without using any control flow graph). + */ + private Label currentBasicBlock; + + /** + * The relative stack size after the last visited instruction. This size is relative to the + * beginning of {@link #currentBasicBlock}, i.e. the true stack size after the last visited + * instruction is equal to the {@link Label#inputStackSize} of the current basic block plus {@link + * #relativeStackSize}. When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of + * the method, so this relative size is also equal to the absolute stack size after the last + * visited instruction. + */ + private int relativeStackSize; + + /** + * The maximum relative stack size after the last visited instruction. This size is relative to + * the beginning of {@link #currentBasicBlock}, i.e. the true maximum stack size after the last + * visited instruction is equal to the {@link Label#inputStackSize} of the current basic block + * plus {@link #maxRelativeStackSize}.When {@link #compute} is equal to {@link + * #COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES}, {@link #currentBasicBlock} is always the start of + * the method, so this relative size is also equal to the absolute maximum stack size after the + * last visited instruction. + */ + private int maxRelativeStackSize; + + /** The number of local variables in the last visited stack map frame. */ + private int currentLocals; + + /** The bytecode offset of the last frame that was written in {@link #stackMapTableEntries}. */ + private int previousFrameOffset; + + /** + * The last frame that was written in {@link #stackMapTableEntries}. This field has the same + * format as {@link #currentFrame}. + */ + private int[] previousFrame; + + /** + * The current stack map frame. The first element contains the bytecode offset of the instruction + * to which the frame corresponds, the second element is the number of locals and the third one is + * the number of stack elements. The local variables start at index 3 and are followed by the + * operand stack elements. In summary frame[0] = offset, frame[1] = numLocal, frame[2] = numStack. + * Local variables and operand stack entries contain abstract types, as defined in {@link Frame}, + * but restricted to {@link Frame#CONSTANT_KIND}, {@link Frame#REFERENCE_KIND}, {@link + * Frame#UNINITIALIZED_KIND} or {@link Frame#FORWARD_UNINITIALIZED_KIND} abstract types. Long and + * double types use only one array entry. + */ + private int[] currentFrame; + + /** Whether this method contains subroutines. */ + private boolean hasSubroutines; + + // ----------------------------------------------------------------------------------------------- + // Other miscellaneous status fields + // ----------------------------------------------------------------------------------------------- + + /** Whether the bytecode of this method contains ASM specific instructions. */ + private boolean hasAsmInstructions; + + /** + * The start offset of the last visited instruction. Used to set the offset field of type + * annotations of type 'offset_target' (see JVMS + * 4.7.20.1). + */ + private int lastBytecodeOffset; + + /** + * The offset in bytes in {@link SymbolTable#getSource} from which the method_info for this method + * (excluding its first 6 bytes) must be copied, or 0. + */ + private int sourceOffset; + + /** + * The length in bytes in {@link SymbolTable#getSource} which must be copied to get the + * method_info for this method (excluding its first 6 bytes for access_flags, name_index and + * descriptor_index). + */ + private int sourceLength; + + // ----------------------------------------------------------------------------------------------- + // Constructor and accessors + // ----------------------------------------------------------------------------------------------- + + /** + * Constructs a new {@link MethodWriter}. + * + * @param symbolTable where the constants used in this AnnotationWriter must be stored. + * @param access the method's access flags (see {@link Opcodes}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type}). + * @param signature the method's signature. May be {@literal null}. + * @param exceptions the internal names of the method's exceptions. May be {@literal null}. + * @param compute indicates what must be computed (see #compute). + */ + MethodWriter( + final SymbolTable symbolTable, + final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptions, + final int compute) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.accessFlags = "".equals(name) ? access | Constants.ACC_CONSTRUCTOR : access; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.name = name; + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + this.descriptor = descriptor; + this.signatureIndex = signature == null ? 0 : symbolTable.addConstantUtf8(signature); + if (exceptions != null && exceptions.length > 0) { + numberOfExceptions = exceptions.length; + this.exceptionIndexTable = new int[numberOfExceptions]; + for (int i = 0; i < numberOfExceptions; ++i) { + this.exceptionIndexTable[i] = symbolTable.addConstantClass(exceptions[i]).index; + } + } else { + numberOfExceptions = 0; + this.exceptionIndexTable = null; + } + this.compute = compute; + if (compute != COMPUTE_NOTHING) { + // Update maxLocals and currentLocals. + int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + if ((access & Opcodes.ACC_STATIC) != 0) { + --argumentsSize; + } + maxLocals = argumentsSize; + currentLocals = argumentsSize; + // Create and visit the label for the first basic block. + firstBasicBlock = new Label(); + visitLabel(firstBasicBlock); + } + } + + boolean hasFrames() { + return stackMapTableNumberOfEntries > 0; + } + + boolean hasAsmInstructions() { + return hasAsmInstructions; + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the MethodVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public void visitParameter(final String name, final int access) { + if (parameters == null) { + parameters = new ByteVector(); + } + ++parametersCount; + parameters.putShort((name == null) ? 0 : symbolTable.addConstantUtf8(name)).putShort(access); + } + + @Override + public AnnotationVisitor visitAnnotationDefault() { + defaultValue = new ByteVector(); + return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, defaultValue, null); + } + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAnnotableParameterCount(final int parameterCount, final boolean visible) { + if (visible) { + visibleAnnotableParameterCount = parameterCount; + } else { + invisibleAnnotableParameterCount = parameterCount; + } + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + final int parameter, final String annotationDescriptor, final boolean visible) { + if (visible) { + if (lastRuntimeVisibleParameterAnnotations == null) { + lastRuntimeVisibleParameterAnnotations = + new AnnotationWriter[Type.getArgumentCount(descriptor)]; + } + return lastRuntimeVisibleParameterAnnotations[parameter] = + AnnotationWriter.create( + symbolTable, annotationDescriptor, lastRuntimeVisibleParameterAnnotations[parameter]); + } else { + if (lastRuntimeInvisibleParameterAnnotations == null) { + lastRuntimeInvisibleParameterAnnotations = + new AnnotationWriter[Type.getArgumentCount(descriptor)]; + } + return lastRuntimeInvisibleParameterAnnotations[parameter] = + AnnotationWriter.create( + symbolTable, + annotationDescriptor, + lastRuntimeInvisibleParameterAnnotations[parameter]); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + if (attribute.isCodeAttribute()) { + attribute.nextAttribute = firstCodeAttribute; + firstCodeAttribute = attribute; + } else { + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + } + + @Override + public void visitCode() { + // Nothing to do. + } + + @Override + public void visitFrame( + final int type, + final int numLocal, + final Object[] local, + final int numStack, + final Object[] stack) { + if (compute == COMPUTE_ALL_FRAMES) { + return; + } + + if (compute == COMPUTE_INSERTED_FRAMES) { + if (currentBasicBlock.frame == null) { + // This should happen only once, for the implicit first frame (which is explicitly visited + // in ClassReader if the EXPAND_ASM_INSNS option is used - and COMPUTE_INSERTED_FRAMES + // can't be set if EXPAND_ASM_INSNS is not used). + currentBasicBlock.frame = new CurrentFrame(currentBasicBlock); + currentBasicBlock.frame.setInputFrameFromDescriptor( + symbolTable, accessFlags, descriptor, numLocal); + currentBasicBlock.frame.accept(this); + } else { + if (type == Opcodes.F_NEW) { + currentBasicBlock.frame.setInputFrameFromApiFormat( + symbolTable, numLocal, local, numStack, stack); + } + // If type is not F_NEW then it is F_INSERT by hypothesis, and currentBlock.frame contains + // the stack map frame at the current instruction, computed from the last F_NEW frame and + // the bytecode instructions in between (via calls to CurrentFrame#execute). + currentBasicBlock.frame.accept(this); + } + } else if (type == Opcodes.F_NEW) { + if (previousFrame == null) { + int argumentsSize = Type.getArgumentsAndReturnSizes(descriptor) >> 2; + Frame implicitFirstFrame = new Frame(new Label()); + implicitFirstFrame.setInputFrameFromDescriptor( + symbolTable, accessFlags, descriptor, argumentsSize); + implicitFirstFrame.accept(this); + } + currentLocals = numLocal; + int frameIndex = visitFrameStart(code.length, numLocal, numStack); + for (int i = 0; i < numLocal; ++i) { + currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, local[i]); + } + for (int i = 0; i < numStack; ++i) { + currentFrame[frameIndex++] = Frame.getAbstractTypeFromApiFormat(symbolTable, stack[i]); + } + visitFrameEnd(); + } else { + if (symbolTable.getMajorVersion() < Opcodes.V1_6) { + throw new IllegalArgumentException("Class versions V1_5 or less must use F_NEW frames."); + } + int offsetDelta; + if (stackMapTableEntries == null) { + stackMapTableEntries = new ByteVector(); + offsetDelta = code.length; + } else { + offsetDelta = code.length - previousFrameOffset - 1; + if (offsetDelta < 0) { + if (type == Opcodes.F_SAME) { + return; + } else { + throw new IllegalStateException(); + } + } + } + + switch (type) { + case Opcodes.F_FULL: + currentLocals = numLocal; + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal); + for (int i = 0; i < numLocal; ++i) { + putFrameType(local[i]); + } + stackMapTableEntries.putShort(numStack); + for (int i = 0; i < numStack; ++i) { + putFrameType(stack[i]); + } + break; + case Opcodes.F_APPEND: + currentLocals += numLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED + numLocal).putShort(offsetDelta); + for (int i = 0; i < numLocal; ++i) { + putFrameType(local[i]); + } + break; + case Opcodes.F_CHOP: + currentLocals -= numLocal; + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED - numLocal).putShort(offsetDelta); + break; + case Opcodes.F_SAME: + if (offsetDelta < 64) { + stackMapTableEntries.putByte(offsetDelta); + } else { + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); + } + break; + case Opcodes.F_SAME1: + if (offsetDelta < 64) { + stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); + } else { + stackMapTableEntries + .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(offsetDelta); + } + putFrameType(stack[0]); + break; + default: + throw new IllegalArgumentException(); + } + + previousFrameOffset = code.length; + ++stackMapTableNumberOfEntries; + } + + if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + relativeStackSize = numStack; + for (int i = 0; i < numStack; ++i) { + if (stack[i] == Opcodes.LONG || stack[i] == Opcodes.DOUBLE) { + relativeStackSize++; + } + } + if (relativeStackSize > maxRelativeStackSize) { + maxRelativeStackSize = relativeStackSize; + } + } + + maxStack = Math.max(maxStack, numStack); + maxLocals = Math.max(maxLocals, currentLocals); + } + + @Override + public void visitInsn(final int opcode) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(opcode); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, null, null); + } else { + int size = relativeStackSize + STACK_SIZE_DELTA[opcode]; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) { + endCurrentBasicBlockWithNoSuccessor(); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if (opcode == Opcodes.SIPUSH) { + code.put12(opcode, operand); + } else { // BIPUSH or NEWARRAY + code.put11(opcode, operand); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, operand, null, null); + } else if (opcode != Opcodes.NEWARRAY) { + // The stack size delta is 1 for BIPUSH or SIPUSH, and 0 for NEWARRAY. + int size = relativeStackSize + 1; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitVarInsn(final int opcode, final int varIndex) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if (varIndex < 4 && opcode != Opcodes.RET) { + int optimizedOpcode; + if (opcode < Opcodes.ISTORE) { + optimizedOpcode = Constants.ILOAD_0 + ((opcode - Opcodes.ILOAD) << 2) + varIndex; + } else { + optimizedOpcode = Constants.ISTORE_0 + ((opcode - Opcodes.ISTORE) << 2) + varIndex; + } + code.putByte(optimizedOpcode); + } else if (varIndex >= 256) { + code.putByte(Constants.WIDE).put12(opcode, varIndex); + } else { + code.put11(opcode, varIndex); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, varIndex, null, null); + } else { + if (opcode == Opcodes.RET) { + // No stack size delta. + currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_END; + currentBasicBlock.outputStackSize = (short) relativeStackSize; + endCurrentBasicBlockWithNoSuccessor(); + } else { // xLOAD or xSTORE + int size = relativeStackSize + STACK_SIZE_DELTA[opcode]; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + if (compute != COMPUTE_NOTHING) { + int currentMaxLocals; + if (opcode == Opcodes.LLOAD + || opcode == Opcodes.DLOAD + || opcode == Opcodes.LSTORE + || opcode == Opcodes.DSTORE) { + currentMaxLocals = varIndex + 2; + } else { + currentMaxLocals = varIndex + 1; + } + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + if (opcode >= Opcodes.ISTORE && compute == COMPUTE_ALL_FRAMES && firstHandler != null) { + // If there are exception handler blocks, each instruction within a handler range is, in + // theory, a basic block (since execution can jump from this instruction to the exception + // handler). As a consequence, the local variable types at the beginning of the handler + // block should be the merge of the local variable types at all the instructions within the + // handler range. However, instead of creating a basic block for each instruction, we can + // get the same result in a more efficient way. Namely, by starting a new basic block after + // each xSTORE instruction, which is what we do here. + visitLabel(new Label()); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol typeSymbol = symbolTable.addConstantClass(type); + code.put12(opcode, typeSymbol.index); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, lastBytecodeOffset, typeSymbol, symbolTable); + } else if (opcode == Opcodes.NEW) { + // The stack size delta is 1 for NEW, and 0 for ANEWARRAY, CHECKCAST, or INSTANCEOF. + int size = relativeStackSize + 1; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol fieldrefSymbol = symbolTable.addConstantFieldref(owner, name, descriptor); + code.put12(opcode, fieldrefSymbol.index); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, fieldrefSymbol, symbolTable); + } else { + int size; + char firstDescChar = descriptor.charAt(0); + switch (opcode) { + case Opcodes.GETSTATIC: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 2 : 1); + break; + case Opcodes.PUTSTATIC: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -2 : -1); + break; + case Opcodes.GETFIELD: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? 1 : 0); + break; + case Opcodes.PUTFIELD: + default: + size = relativeStackSize + (firstDescChar == 'D' || firstDescChar == 'J' ? -3 : -2); + break; + } + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitMethodInsn( + final int opcode, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface); + if (opcode == Opcodes.INVOKEINTERFACE) { + code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index) + .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0); + } else { + code.put12(opcode, methodrefSymbol.index); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(opcode, 0, methodrefSymbol, symbolTable); + } else { + int argumentsAndReturnSize = methodrefSymbol.getArgumentsAndReturnSizes(); + int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2); + int size; + if (opcode == Opcodes.INVOKESTATIC) { + size = relativeStackSize + stackSizeDelta + 1; + } else { + size = relativeStackSize + stackSizeDelta; + } + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol invokeDynamicSymbol = + symbolTable.addConstantInvokeDynamic( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + code.put12(Opcodes.INVOKEDYNAMIC, invokeDynamicSymbol.index); + code.putShort(0); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.INVOKEDYNAMIC, 0, invokeDynamicSymbol, symbolTable); + } else { + int argumentsAndReturnSize = invokeDynamicSymbol.getArgumentsAndReturnSizes(); + int stackSizeDelta = (argumentsAndReturnSize & 3) - (argumentsAndReturnSize >> 2) + 1; + int size = relativeStackSize + stackSizeDelta; + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + // Compute the 'base' opcode, i.e. GOTO or JSR if opcode is GOTO_W or JSR_W, otherwise opcode. + int baseOpcode = + opcode >= Constants.GOTO_W ? opcode - Constants.WIDE_JUMP_OPCODE_DELTA : opcode; + boolean nextInsnIsJumpTarget = false; + if ((label.flags & Label.FLAG_RESOLVED) != 0 + && label.bytecodeOffset - code.length < Short.MIN_VALUE) { + // Case of a backward jump with an offset < -32768. In this case we automatically replace GOTO + // with GOTO_W, JSR with JSR_W and IFxxx with IFNOTxxx GOTO_W L:..., where + // IFNOTxxx is the "opposite" opcode of IFxxx (e.g. IFNE for IFEQ) and where designates + // the instruction just after the GOTO_W. + if (baseOpcode == Opcodes.GOTO) { + code.putByte(Constants.GOTO_W); + } else if (baseOpcode == Opcodes.JSR) { + code.putByte(Constants.JSR_W); + } else { + // Put the "opposite" opcode of baseOpcode. This can be done by flipping the least + // significant bit for IFNULL and IFNONNULL, and similarly for IFEQ ... IF_ACMPEQ (with a + // pre and post offset by 1). The jump offset is 8 bytes (3 for IFNOTxxx, 5 for GOTO_W). + code.putByte(baseOpcode >= Opcodes.IFNULL ? baseOpcode ^ 1 : ((baseOpcode + 1) ^ 1) - 1); + code.putShort(8); + // Here we could put a GOTO_W in theory, but if ASM specific instructions are used in this + // method or another one, and if the class has frames, we will need to insert a frame after + // this GOTO_W during the additional ClassReader -> ClassWriter round trip to remove the ASM + // specific instructions. To not miss this additional frame, we need to use an ASM_GOTO_W + // here, which has the unfortunate effect of forcing this additional round trip (which in + // some case would not have been really necessary, but we can't know this at this point). + code.putByte(Constants.ASM_GOTO_W); + hasAsmInstructions = true; + // The instruction after the GOTO_W becomes the target of the IFNOT instruction. + nextInsnIsJumpTarget = true; + } + label.put(code, code.length - 1, true); + } else if (baseOpcode != opcode) { + // Case of a GOTO_W or JSR_W specified by the user (normally ClassReader when used to remove + // ASM specific instructions). In this case we keep the original instruction. + code.putByte(opcode); + label.put(code, code.length - 1, true); + } else { + // Case of a jump with an offset >= -32768, or of a jump with an unknown offset. In these + // cases we store the offset in 2 bytes (which will be increased via a ClassReader -> + // ClassWriter round trip if it turns out that 2 bytes are not sufficient). + code.putByte(baseOpcode); + label.put(code, code.length - 1, false); + } + + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + Label nextBasicBlock = null; + if (compute == COMPUTE_ALL_FRAMES) { + currentBasicBlock.frame.execute(baseOpcode, 0, null, null); + // Record the fact that 'label' is the target of a jump instruction. + label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + // Add 'label' as a successor of the current basic block. + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + if (baseOpcode != Opcodes.GOTO) { + // The next instruction starts a new basic block (except for GOTO: by default the code + // following a goto is unreachable - unless there is an explicit label for it - and we + // should not compute stack frame types for its instructions). + nextBasicBlock = new Label(); + } + } else if (compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(baseOpcode, 0, null, null); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += STACK_SIZE_DELTA[baseOpcode]; + } else { + if (baseOpcode == Opcodes.JSR) { + // Record the fact that 'label' designates a subroutine, if not already done. + if ((label.flags & Label.FLAG_SUBROUTINE_START) == 0) { + label.flags |= Label.FLAG_SUBROUTINE_START; + hasSubroutines = true; + } + currentBasicBlock.flags |= Label.FLAG_SUBROUTINE_CALLER; + // Note that, by construction in this method, a block which calls a subroutine has at + // least two successors in the control flow graph: the first one (added below) leads to + // the instruction after the JSR, while the second one (added here) leads to the JSR + // target. Note that the first successor is virtual (it does not correspond to a possible + // execution path): it is only used to compute the successors of the basic blocks ending + // with a ret, in {@link Label#addSubroutineRetSuccessors}. + addSuccessorToCurrentBasicBlock(relativeStackSize + 1, label); + // The instruction after the JSR starts a new basic block. + nextBasicBlock = new Label(); + } else { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += STACK_SIZE_DELTA[baseOpcode]; + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + } + // If the next instruction starts a new basic block, call visitLabel to add the label of this + // instruction as a successor of the current block, and to start a new basic block. + if (nextBasicBlock != null) { + if (nextInsnIsJumpTarget) { + nextBasicBlock.flags |= Label.FLAG_JUMP_TARGET; + } + visitLabel(nextBasicBlock); + } + if (baseOpcode == Opcodes.GOTO) { + endCurrentBasicBlockWithNoSuccessor(); + } + } + } + + @Override + public void visitLabel(final Label label) { + // Resolve the forward references to this label, if any. + hasAsmInstructions |= label.resolve(code.data, stackMapTableEntries, code.length); + // visitLabel starts a new basic block (except for debug only labels), so we need to update the + // previous and current block references and list of successors. + if ((label.flags & Label.FLAG_DEBUG_ONLY) != 0) { + return; + } + if (compute == COMPUTE_ALL_FRAMES) { + if (currentBasicBlock != null) { + if (label.bytecodeOffset == currentBasicBlock.bytecodeOffset) { + // We use {@link Label#getCanonicalInstance} to store the state of a basic block in only + // one place, but this does not work for labels which have not been visited yet. + // Therefore, when we detect here two labels having the same bytecode offset, we need to + // - consolidate the state scattered in these two instances into the canonical instance: + currentBasicBlock.flags |= (short)(label.flags & Label.FLAG_JUMP_TARGET); + // - make sure the two instances share the same Frame instance (the implementation of + // {@link Label#getCanonicalInstance} relies on this property; here label.frame should be + // null): + label.frame = currentBasicBlock.frame; + // - and make sure to NOT assign 'label' into 'currentBasicBlock' or 'lastBasicBlock', so + // that they still refer to the canonical instance for this bytecode offset. + return; + } + // End the current basic block (with one new successor). + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + } + // Append 'label' at the end of the basic block list. + if (lastBasicBlock != null) { + if (label.bytecodeOffset == lastBasicBlock.bytecodeOffset) { + // Same comment as above. + lastBasicBlock.flags |= (short)(label.flags & Label.FLAG_JUMP_TARGET); + // Here label.frame should be null. + label.frame = lastBasicBlock.frame; + currentBasicBlock = lastBasicBlock; + return; + } + lastBasicBlock.nextBasicBlock = label; + } + lastBasicBlock = label; + // Make it the new current basic block. + currentBasicBlock = label; + // Here label.frame should be null. + label.frame = new Frame(label); + } else if (compute == COMPUTE_INSERTED_FRAMES) { + if (currentBasicBlock == null) { + // This case should happen only once, for the visitLabel call in the constructor. Indeed, if + // compute is equal to COMPUTE_INSERTED_FRAMES, currentBasicBlock stays unchanged. + currentBasicBlock = label; + } else { + // Update the frame owner so that a correct frame offset is computed in Frame.accept(). + currentBasicBlock.frame.owner = label; + } + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + if (currentBasicBlock != null) { + // End the current basic block (with one new successor). + currentBasicBlock.outputStackMax = (short) maxRelativeStackSize; + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + // Start a new current basic block, and reset the current and maximum relative stack sizes. + currentBasicBlock = label; + relativeStackSize = 0; + maxRelativeStackSize = 0; + // Append the new basic block at the end of the basic block list. + if (lastBasicBlock != null) { + lastBasicBlock.nextBasicBlock = label; + } + lastBasicBlock = label; + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES && currentBasicBlock == null) { + // This case should happen only once, for the visitLabel call in the constructor. Indeed, if + // compute is equal to COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES, currentBasicBlock stays + // unchanged. + currentBasicBlock = label; + } + } + + @Override + public void visitLdcInsn(final Object value) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol constantSymbol = symbolTable.addConstant(value); + int constantIndex = constantSymbol.index; + char firstDescriptorChar; + boolean isLongOrDouble = + constantSymbol.tag == Symbol.CONSTANT_LONG_TAG + || constantSymbol.tag == Symbol.CONSTANT_DOUBLE_TAG + || (constantSymbol.tag == Symbol.CONSTANT_DYNAMIC_TAG + && ((firstDescriptorChar = constantSymbol.value.charAt(0)) == 'J' + || firstDescriptorChar == 'D')); + if (isLongOrDouble) { + code.put12(Constants.LDC2_W, constantIndex); + } else if (constantIndex >= 256) { + code.put12(Constants.LDC_W, constantIndex); + } else { + code.put11(Opcodes.LDC, constantIndex); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.LDC, 0, constantSymbol, symbolTable); + } else { + int size = relativeStackSize + (isLongOrDouble ? 2 : 1); + if (size > maxRelativeStackSize) { + maxRelativeStackSize = size; + } + relativeStackSize = size; + } + } + } + + @Override + public void visitIincInsn(final int varIndex, final int increment) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + if ((varIndex > 255) || (increment > 127) || (increment < -128)) { + code.putByte(Constants.WIDE).put12(Opcodes.IINC, varIndex).putShort(increment); + } else { + code.putByte(Opcodes.IINC).put11(varIndex, increment); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null + && (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES)) { + currentBasicBlock.frame.execute(Opcodes.IINC, varIndex, null, null); + } + if (compute != COMPUTE_NOTHING) { + int currentMaxLocals = varIndex + 1; + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + } + + @Override + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(Opcodes.TABLESWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(code, lastBytecodeOffset, true); + code.putInt(min).putInt(max); + for (Label label : labels) { + label.put(code, lastBytecodeOffset, true); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + visitSwitchInsn(dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + code.putByte(Opcodes.LOOKUPSWITCH).putByteArray(null, 0, (4 - code.length % 4) % 4); + dflt.put(code, lastBytecodeOffset, true); + code.putInt(labels.length); + for (int i = 0; i < labels.length; ++i) { + code.putInt(keys[i]); + labels[i].put(code, lastBytecodeOffset, true); + } + // If needed, update the maximum stack size and number of locals, and stack map frames. + visitSwitchInsn(dflt, labels); + } + + private void visitSwitchInsn(final Label dflt, final Label[] labels) { + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES) { + currentBasicBlock.frame.execute(Opcodes.LOOKUPSWITCH, 0, null, null); + // Add all the labels as successors of the current basic block. + addSuccessorToCurrentBasicBlock(Edge.JUMP, dflt); + dflt.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + for (Label label : labels) { + addSuccessorToCurrentBasicBlock(Edge.JUMP, label); + label.getCanonicalInstance().flags |= Label.FLAG_JUMP_TARGET; + } + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + --relativeStackSize; + // Add all the labels as successors of the current basic block. + addSuccessorToCurrentBasicBlock(relativeStackSize, dflt); + for (Label label : labels) { + addSuccessorToCurrentBasicBlock(relativeStackSize, label); + } + } + // End the current basic block. + endCurrentBasicBlockWithNoSuccessor(); + } + } + + @Override + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + lastBytecodeOffset = code.length; + // Add the instruction to the bytecode of the method. + Symbol descSymbol = symbolTable.addConstantClass(descriptor); + code.put12(Opcodes.MULTIANEWARRAY, descSymbol.index).putByte(numDimensions); + // If needed, update the maximum stack size and number of locals, and stack map frames. + if (currentBasicBlock != null) { + if (compute == COMPUTE_ALL_FRAMES || compute == COMPUTE_INSERTED_FRAMES) { + currentBasicBlock.frame.execute( + Opcodes.MULTIANEWARRAY, numDimensions, descSymbol, symbolTable); + } else { + // No need to update maxRelativeStackSize (the stack size delta is always negative). + relativeStackSize += 1 - numDimensions; + } + } + } + + @Override + public AnnotationVisitor visitInsnAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, + (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), + typePath, + descriptor, + lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, + (typeRef & 0xFF0000FF) | (lastBytecodeOffset << 8), + typePath, + descriptor, + lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + Handler newHandler = + new Handler( + start, end, handler, type != null ? symbolTable.addConstantClass(type).index : 0, type); + if (firstHandler == null) { + firstHandler = newHandler; + } else { + lastHandler.nextHandler = newHandler; + } + lastHandler = newHandler; + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitLocalVariable( + final String name, + final String descriptor, + final String signature, + final Label start, + final Label end, + final int index) { + if (signature != null) { + if (localVariableTypeTable == null) { + localVariableTypeTable = new ByteVector(); + } + ++localVariableTypeTableLength; + localVariableTypeTable + .putShort(start.bytecodeOffset) + .putShort(end.bytecodeOffset - start.bytecodeOffset) + .putShort(symbolTable.addConstantUtf8(name)) + .putShort(symbolTable.addConstantUtf8(signature)) + .putShort(index); + } + if (localVariableTable == null) { + localVariableTable = new ByteVector(); + } + ++localVariableTableLength; + localVariableTable + .putShort(start.bytecodeOffset) + .putShort(end.bytecodeOffset - start.bytecodeOffset) + .putShort(symbolTable.addConstantUtf8(name)) + .putShort(symbolTable.addConstantUtf8(descriptor)) + .putShort(index); + if (compute != COMPUTE_NOTHING) { + char firstDescChar = descriptor.charAt(0); + int currentMaxLocals = index + (firstDescChar == 'J' || firstDescChar == 'D' ? 2 : 1); + if (currentMaxLocals > maxLocals) { + maxLocals = currentMaxLocals; + } + } + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation( + final int typeRef, + final TypePath typePath, + final Label[] start, + final Label[] end, + final int[] index, + final String descriptor, + final boolean visible) { + // Create a ByteVector to hold a 'type_annotation' JVMS structure. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20. + ByteVector typeAnnotation = new ByteVector(); + // Write target_type, target_info, and target_path. + typeAnnotation.putByte(typeRef >>> 24).putShort(start.length); + for (int i = 0; i < start.length; ++i) { + typeAnnotation + .putShort(start[i].bytecodeOffset) + .putShort(end[i].bytecodeOffset - start[i].bytecodeOffset) + .putShort(index[i]); + } + TypePath.put(typePath, typeAnnotation); + // Write type_index and reserve space for num_element_value_pairs. + typeAnnotation.putShort(symbolTable.addConstantUtf8(descriptor)).putShort(0); + if (visible) { + return lastCodeRuntimeVisibleTypeAnnotation = + new AnnotationWriter( + symbolTable, + /* useNamedValues = */ true, + typeAnnotation, + lastCodeRuntimeVisibleTypeAnnotation); + } else { + return lastCodeRuntimeInvisibleTypeAnnotation = + new AnnotationWriter( + symbolTable, + /* useNamedValues = */ true, + typeAnnotation, + lastCodeRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitLineNumber(final int line, final Label start) { + if (lineNumberTable == null) { + lineNumberTable = new ByteVector(); + } + ++lineNumberTableLength; + lineNumberTable.putShort(start.bytecodeOffset); + lineNumberTable.putShort(line); + } + + @Override + public void visitMaxs(final int maxStack, final int maxLocals) { + if (compute == COMPUTE_ALL_FRAMES) { + computeAllFrames(); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + computeMaxStackAndLocal(); + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { + this.maxStack = maxRelativeStackSize; + } else { + this.maxStack = maxStack; + this.maxLocals = maxLocals; + } + } + + /** Computes all the stack map frames of the method, from scratch. */ + private void computeAllFrames() { + // Complete the control flow graph with exception handler blocks. + Handler handler = firstHandler; + while (handler != null) { + String catchTypeDescriptor = + handler.catchTypeDescriptor == null ? "java/lang/Throwable" : handler.catchTypeDescriptor; + int catchType = Frame.getAbstractTypeFromInternalName(symbolTable, catchTypeDescriptor); + // Mark handlerBlock as an exception handler. + Label handlerBlock = handler.handlerPc.getCanonicalInstance(); + handlerBlock.flags |= Label.FLAG_JUMP_TARGET; + // Add handlerBlock as a successor of all the basic blocks in the exception handler range. + Label handlerRangeBlock = handler.startPc.getCanonicalInstance(); + Label handlerRangeEnd = handler.endPc.getCanonicalInstance(); + while (handlerRangeBlock != handlerRangeEnd) { + handlerRangeBlock.outgoingEdges = + new Edge(catchType, handlerBlock, handlerRangeBlock.outgoingEdges); + handlerRangeBlock = handlerRangeBlock.nextBasicBlock; + } + handler = handler.nextHandler; + } + + // Create and visit the first (implicit) frame. + Frame firstFrame = firstBasicBlock.frame; + firstFrame.setInputFrameFromDescriptor(symbolTable, accessFlags, descriptor, this.maxLocals); + firstFrame.accept(this); + + // Fix point algorithm: add the first basic block to a list of blocks to process (i.e. blocks + // whose stack map frame has changed) and, while there are blocks to process, remove one from + // the list and update the stack map frames of its successor blocks in the control flow graph + // (which might change them, in which case these blocks must be processed too, and are thus + // added to the list of blocks to process). Also compute the maximum stack size of the method, + // as a by-product. + Label listOfBlocksToProcess = firstBasicBlock; + listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST; + int maxStackSize = 0; + while (listOfBlocksToProcess != Label.EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + basicBlock.nextListElement = null; + // By definition, basicBlock is reachable. + basicBlock.flags |= Label.FLAG_REACHABLE; + // Update the (absolute) maximum stack size. + int maxBlockStackSize = basicBlock.frame.getInputStackSize() + basicBlock.outputStackMax; + if (maxBlockStackSize > maxStackSize) { + maxStackSize = maxBlockStackSize; + } + // Update the successor blocks of basicBlock in the control flow graph. + Edge outgoingEdge = basicBlock.outgoingEdges; + while (outgoingEdge != null) { + Label successorBlock = outgoingEdge.successor.getCanonicalInstance(); + boolean successorBlockChanged = + basicBlock.frame.merge(symbolTable, successorBlock.frame, outgoingEdge.info); + if (successorBlockChanged && successorBlock.nextListElement == null) { + // If successorBlock has changed it must be processed. Thus, if it is not already in the + // list of blocks to process, add it to this list. + successorBlock.nextListElement = listOfBlocksToProcess; + listOfBlocksToProcess = successorBlock; + } + outgoingEdge = outgoingEdge.nextEdge; + } + } + + // Loop over all the basic blocks and visit the stack map frames that must be stored in the + // StackMapTable attribute. Also replace unreachable code with NOP* ATHROW, and remove it from + // exception handler ranges. + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) + == (Label.FLAG_JUMP_TARGET | Label.FLAG_REACHABLE)) { + basicBlock.frame.accept(this); + } + if ((basicBlock.flags & Label.FLAG_REACHABLE) == 0) { + // Find the start and end bytecode offsets of this unreachable block. + Label nextBasicBlock = basicBlock.nextBasicBlock; + int startOffset = basicBlock.bytecodeOffset; + int endOffset = (nextBasicBlock == null ? code.length : nextBasicBlock.bytecodeOffset) - 1; + if (endOffset >= startOffset) { + // Replace its instructions with NOP ... NOP ATHROW. + for (int i = startOffset; i < endOffset; ++i) { + code.data[i] = Opcodes.NOP; + } + code.data[endOffset] = (byte) Opcodes.ATHROW; + // Emit a frame for this unreachable block, with no local and a Throwable on the stack + // (so that the ATHROW could consume this Throwable if it were reachable). + int frameIndex = visitFrameStart(startOffset, /* numLocal = */ 0, /* numStack = */ 1); + currentFrame[frameIndex] = + Frame.getAbstractTypeFromInternalName(symbolTable, "java/lang/Throwable"); + visitFrameEnd(); + // Remove this unreachable basic block from the exception handler ranges. + firstHandler = Handler.removeRange(firstHandler, basicBlock, nextBasicBlock); + // The maximum stack size is now at least one, because of the Throwable declared above. + maxStackSize = Math.max(maxStackSize, 1); + } + } + basicBlock = basicBlock.nextBasicBlock; + } + + this.maxStack = maxStackSize; + } + + /** Computes the maximum stack size of the method. */ + private void computeMaxStackAndLocal() { + // Complete the control flow graph with exception handler blocks. + Handler handler = firstHandler; + while (handler != null) { + Label handlerBlock = handler.handlerPc; + Label handlerRangeBlock = handler.startPc; + Label handlerRangeEnd = handler.endPc; + // Add handlerBlock as a successor of all the basic blocks in the exception handler range. + while (handlerRangeBlock != handlerRangeEnd) { + if ((handlerRangeBlock.flags & Label.FLAG_SUBROUTINE_CALLER) == 0) { + handlerRangeBlock.outgoingEdges = + new Edge(Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges); + } else { + // If handlerRangeBlock is a JSR block, add handlerBlock after the first two outgoing + // edges to preserve the hypothesis about JSR block successors order (see + // {@link #visitJumpInsn}). + handlerRangeBlock.outgoingEdges.nextEdge.nextEdge = + new Edge( + Edge.EXCEPTION, handlerBlock, handlerRangeBlock.outgoingEdges.nextEdge.nextEdge); + } + handlerRangeBlock = handlerRangeBlock.nextBasicBlock; + } + handler = handler.nextHandler; + } + + // Complete the control flow graph with the successor blocks of subroutines, if needed. + if (hasSubroutines) { + // First step: find the subroutines. This step determines, for each basic block, to which + // subroutine(s) it belongs. Start with the main "subroutine": + short numSubroutines = 1; + firstBasicBlock.markSubroutine(numSubroutines); + // Then, mark the subroutines called by the main subroutine, then the subroutines called by + // those called by the main subroutine, etc. + for (short currentSubroutine = 1; currentSubroutine <= numSubroutines; ++currentSubroutine) { + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0 + && basicBlock.subroutineId == currentSubroutine) { + Label jsrTarget = basicBlock.outgoingEdges.nextEdge.successor; + if (jsrTarget.subroutineId == 0) { + // If this subroutine has not been marked yet, find its basic blocks. + jsrTarget.markSubroutine(++numSubroutines); + } + } + basicBlock = basicBlock.nextBasicBlock; + } + } + // Second step: find the successors in the control flow graph of each subroutine basic block + // 'r' ending with a RET instruction. These successors are the virtual successors of the basic + // blocks ending with JSR instructions (see {@link #visitJumpInsn)} that can reach 'r'. + Label basicBlock = firstBasicBlock; + while (basicBlock != null) { + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) { + // By construction, jsr targets are stored in the second outgoing edge of basic blocks + // that ends with a jsr instruction (see {@link #FLAG_SUBROUTINE_CALLER}). + Label subroutine = basicBlock.outgoingEdges.nextEdge.successor; + subroutine.addSubroutineRetSuccessors(basicBlock); + } + basicBlock = basicBlock.nextBasicBlock; + } + } + + // Data flow algorithm: put the first basic block in a list of blocks to process (i.e. blocks + // whose input stack size has changed) and, while there are blocks to process, remove one + // from the list, update the input stack size of its successor blocks in the control flow + // graph, and add these blocks to the list of blocks to process (if not already done). + Label listOfBlocksToProcess = firstBasicBlock; + listOfBlocksToProcess.nextListElement = Label.EMPTY_LIST; + int maxStackSize = maxStack; + while (listOfBlocksToProcess != Label.EMPTY_LIST) { + // Remove a basic block from the list of blocks to process. Note that we don't reset + // basicBlock.nextListElement to null on purpose, to make sure we don't reprocess already + // processed basic blocks. + Label basicBlock = listOfBlocksToProcess; + listOfBlocksToProcess = listOfBlocksToProcess.nextListElement; + // Compute the (absolute) input stack size and maximum stack size of this block. + int inputStackTop = basicBlock.inputStackSize; + int maxBlockStackSize = inputStackTop + basicBlock.outputStackMax; + // Update the absolute maximum stack size of the method. + if (maxBlockStackSize > maxStackSize) { + maxStackSize = maxBlockStackSize; + } + // Update the input stack size of the successor blocks of basicBlock in the control flow + // graph, and add these blocks to the list of blocks to process, if not already done. + Edge outgoingEdge = basicBlock.outgoingEdges; + if ((basicBlock.flags & Label.FLAG_SUBROUTINE_CALLER) != 0) { + // Ignore the first outgoing edge of the basic blocks ending with a jsr: these are virtual + // edges which lead to the instruction just after the jsr, and do not correspond to a + // possible execution path (see {@link #visitJumpInsn} and + // {@link Label#FLAG_SUBROUTINE_CALLER}). + outgoingEdge = outgoingEdge.nextEdge; + } + while (outgoingEdge != null) { + Label successorBlock = outgoingEdge.successor; + if (successorBlock.nextListElement == null) { + successorBlock.inputStackSize = + (short) (outgoingEdge.info == Edge.EXCEPTION ? 1 : inputStackTop + outgoingEdge.info); + successorBlock.nextListElement = listOfBlocksToProcess; + listOfBlocksToProcess = successorBlock; + } + outgoingEdge = outgoingEdge.nextEdge; + } + } + this.maxStack = maxStackSize; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: control flow analysis algorithm + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a successor to {@link #currentBasicBlock} in the control flow graph. + * + * @param info information about the control flow edge to be added. + * @param successor the successor block to be added to the current basic block. + */ + private void addSuccessorToCurrentBasicBlock(final int info, final Label successor) { + currentBasicBlock.outgoingEdges = new Edge(info, successor, currentBasicBlock.outgoingEdges); + } + + /** + * Ends the current basic block. This method must be used in the case where the current basic + * block does not have any successor. + * + *

WARNING: this method must be called after the currently visited instruction has been put in + * {@link #code} (if frames are computed, this method inserts a new Label to start a new basic + * block after the current instruction). + */ + private void endCurrentBasicBlockWithNoSuccessor() { + if (compute == COMPUTE_ALL_FRAMES) { + Label nextBasicBlock = new Label(); + nextBasicBlock.frame = new Frame(nextBasicBlock); + nextBasicBlock.resolve(code.data, stackMapTableEntries, code.length); + lastBasicBlock.nextBasicBlock = nextBasicBlock; + lastBasicBlock = nextBasicBlock; + currentBasicBlock = null; + } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { + currentBasicBlock.outputStackMax = (short) maxRelativeStackSize; + currentBasicBlock = null; + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods: stack map frames + // ----------------------------------------------------------------------------------------------- + + /** + * Starts the visit of a new stack map frame, stored in {@link #currentFrame}. + * + * @param offset the bytecode offset of the instruction to which the frame corresponds. + * @param numLocal the number of local variables in the frame. + * @param numStack the number of stack elements in the frame. + * @return the index of the next element to be written in this frame. + */ + int visitFrameStart(final int offset, final int numLocal, final int numStack) { + int frameLength = 3 + numLocal + numStack; + if (currentFrame == null || currentFrame.length < frameLength) { + currentFrame = new int[frameLength]; + } + currentFrame[0] = offset; + currentFrame[1] = numLocal; + currentFrame[2] = numStack; + return 3; + } + + /** + * Sets an abstract type in {@link #currentFrame}. + * + * @param frameIndex the index of the element to be set in {@link #currentFrame}. + * @param abstractType an abstract type. + */ + void visitAbstractType(final int frameIndex, final int abstractType) { + currentFrame[frameIndex] = abstractType; + } + + /** + * Ends the visit of {@link #currentFrame} by writing it in the StackMapTable entries and by + * updating the StackMapTable number_of_entries (except if the current frame is the first one, + * which is implicit in StackMapTable). Then resets {@link #currentFrame} to {@literal null}. + */ + void visitFrameEnd() { + if (previousFrame != null) { + if (stackMapTableEntries == null) { + stackMapTableEntries = new ByteVector(); + } + putFrame(); + ++stackMapTableNumberOfEntries; + } + previousFrame = currentFrame; + currentFrame = null; + } + + /** Compresses and writes {@link #currentFrame} in a new StackMapTable entry. */ + private void putFrame() { + final int numLocal = currentFrame[1]; + final int numStack = currentFrame[2]; + if (symbolTable.getMajorVersion() < Opcodes.V1_6) { + // Generate a StackMap attribute entry, which are always uncompressed. + stackMapTableEntries.putShort(currentFrame[0]).putShort(numLocal); + putAbstractTypes(3, 3 + numLocal); + stackMapTableEntries.putShort(numStack); + putAbstractTypes(3 + numLocal, 3 + numLocal + numStack); + return; + } + final int offsetDelta = + stackMapTableNumberOfEntries == 0 + ? currentFrame[0] + : currentFrame[0] - previousFrame[0] - 1; + final int previousNumlocal = previousFrame[1]; + final int numLocalDelta = numLocal - previousNumlocal; + int type = Frame.FULL_FRAME; + if (numStack == 0) { + switch (numLocalDelta) { + case -3: + case -2: + case -1: + type = Frame.CHOP_FRAME; + break; + case 0: + type = offsetDelta < 64 ? Frame.SAME_FRAME : Frame.SAME_FRAME_EXTENDED; + break; + case 1: + case 2: + case 3: + type = Frame.APPEND_FRAME; + break; + default: + // Keep the FULL_FRAME type. + break; + } + } else if (numLocalDelta == 0 && numStack == 1) { + type = + offsetDelta < 63 + ? Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + : Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED; + } + if (type != Frame.FULL_FRAME) { + // Verify if locals are the same as in the previous frame. + int frameIndex = 3; + for (int i = 0; i < previousNumlocal && i < numLocal; i++) { + if (currentFrame[frameIndex] != previousFrame[frameIndex]) { + type = Frame.FULL_FRAME; + break; + } + frameIndex++; + } + } + switch (type) { + case Frame.SAME_FRAME: + stackMapTableEntries.putByte(offsetDelta); + break; + case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME: + stackMapTableEntries.putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME + offsetDelta); + putAbstractTypes(3 + numLocal, 4 + numLocal); + break; + case Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED: + stackMapTableEntries + .putByte(Frame.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) + .putShort(offsetDelta); + putAbstractTypes(3 + numLocal, 4 + numLocal); + break; + case Frame.SAME_FRAME_EXTENDED: + stackMapTableEntries.putByte(Frame.SAME_FRAME_EXTENDED).putShort(offsetDelta); + break; + case Frame.CHOP_FRAME: + stackMapTableEntries + .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta) + .putShort(offsetDelta); + break; + case Frame.APPEND_FRAME: + stackMapTableEntries + .putByte(Frame.SAME_FRAME_EXTENDED + numLocalDelta) + .putShort(offsetDelta); + putAbstractTypes(3 + previousNumlocal, 3 + numLocal); + break; + case Frame.FULL_FRAME: + default: + stackMapTableEntries.putByte(Frame.FULL_FRAME).putShort(offsetDelta).putShort(numLocal); + putAbstractTypes(3, 3 + numLocal); + stackMapTableEntries.putShort(numStack); + putAbstractTypes(3 + numLocal, 3 + numLocal + numStack); + break; + } + } + + /** + * Puts some abstract types of {@link #currentFrame} in {@link #stackMapTableEntries} , using the + * JVMS verification_type_info format used in StackMapTable attributes. + * + * @param start index of the first type in {@link #currentFrame} to write. + * @param end index of last type in {@link #currentFrame} to write (exclusive). + */ + private void putAbstractTypes(final int start, final int end) { + for (int i = start; i < end; ++i) { + Frame.putAbstractType(symbolTable, currentFrame[i], stackMapTableEntries); + } + } + + /** + * Puts the given public API frame element type in {@link #stackMapTableEntries} , using the JVMS + * verification_type_info format used in StackMapTable attributes. + * + * @param type a frame element type described using the same format as in {@link + * MethodVisitor#visitFrame}, i.e. either {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link + * Opcodes#FLOAT}, {@link Opcodes#LONG}, {@link Opcodes#DOUBLE}, {@link Opcodes#NULL}, or + * {@link Opcodes#UNINITIALIZED_THIS}, or the internal name of a class, or a Label designating + * a NEW instruction (for uninitialized types). + */ + private void putFrameType(final Object type) { + if (type instanceof Integer) { + stackMapTableEntries.putByte(((Integer) type).intValue()); + } else if (type instanceof String) { + stackMapTableEntries + .putByte(Frame.ITEM_OBJECT) + .putShort(symbolTable.addConstantClass((String) type).index); + } else { + stackMapTableEntries.putByte(Frame.ITEM_UNINITIALIZED); + ((Label) type).put(stackMapTableEntries); + } + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns whether the attributes of this method can be copied from the attributes of the given + * method (assuming there is no method visitor between the given ClassReader and this + * MethodWriter). This method should only be called just after this MethodWriter has been created, + * and before any content is visited. It returns true if the attributes corresponding to the + * constructor arguments (at most a Signature, an Exception, a Deprecated and a Synthetic + * attribute) are the same as the corresponding attributes in the given method. + * + * @param source the source ClassReader from which the attributes of this method might be copied. + * @param hasSyntheticAttribute whether the method_info JVMS structure from which the attributes + * of this method might be copied contains a Synthetic attribute. + * @param hasDeprecatedAttribute whether the method_info JVMS structure from which the attributes + * of this method might be copied contains a Deprecated attribute. + * @param descriptorIndex the descriptor_index field of the method_info JVMS structure from which + * the attributes of this method might be copied. + * @param signatureIndex the constant pool index contained in the Signature attribute of the + * method_info JVMS structure from which the attributes of this method might be copied, or 0. + * @param exceptionsOffset the offset in 'source.b' of the Exceptions attribute of the method_info + * JVMS structure from which the attributes of this method might be copied, or 0. + * @return whether the attributes of this method can be copied from the attributes of the + * method_info JVMS structure in 'source.b', between 'methodInfoOffset' and 'methodInfoOffset' + * + 'methodInfoLength'. + */ + boolean canCopyMethodAttributes( + final ClassReader source, + final boolean hasSyntheticAttribute, + final boolean hasDeprecatedAttribute, + final int descriptorIndex, + final int signatureIndex, + final int exceptionsOffset) { + // If the method descriptor has changed, with more locals than the max_locals field of the + // original Code attribute, if any, then the original method attributes can't be copied. A + // conservative check on the descriptor changes alone ensures this (being more precise is not + // worth the additional complexity, because these cases should be rare -- if a transform changes + // a method descriptor, most of the time it needs to change the method's code too). + if (source != symbolTable.getSource() + || descriptorIndex != this.descriptorIndex + || signatureIndex != this.signatureIndex + || hasDeprecatedAttribute != ((accessFlags & Opcodes.ACC_DEPRECATED) != 0)) { + return false; + } + boolean needSyntheticAttribute = + symbolTable.getMajorVersion() < Opcodes.V1_5 && (accessFlags & Opcodes.ACC_SYNTHETIC) != 0; + if (hasSyntheticAttribute != needSyntheticAttribute) { + return false; + } + if (exceptionsOffset == 0) { + if (numberOfExceptions != 0) { + return false; + } + } else if (source.readUnsignedShort(exceptionsOffset) == numberOfExceptions) { + int currentExceptionOffset = exceptionsOffset + 2; + for (int i = 0; i < numberOfExceptions; ++i) { + if (source.readUnsignedShort(currentExceptionOffset) != exceptionIndexTable[i]) { + return false; + } + currentExceptionOffset += 2; + } + } + return true; + } + + /** + * Sets the source from which the attributes of this method will be copied. + * + * @param methodInfoOffset the offset in 'symbolTable.getSource()' of the method_info JVMS + * structure from which the attributes of this method will be copied. + * @param methodInfoLength the length in 'symbolTable.getSource()' of the method_info JVMS + * structure from which the attributes of this method will be copied. + */ + void setMethodAttributesSource(final int methodInfoOffset, final int methodInfoLength) { + // Don't copy the attributes yet, instead store their location in the source class reader so + // they can be copied later, in {@link #putMethodInfo}. Note that we skip the 6 header bytes + // of the method_info JVMS structure. + this.sourceOffset = methodInfoOffset + 6; + this.sourceLength = methodInfoLength - 6; + } + + /** + * Returns the size of the method_info JVMS structure generated by this MethodWriter. Also add the + * names of the attributes of this method in the constant pool. + * + * @return the size in bytes of the method_info JVMS structure. + */ + int computeMethodInfoSize() { + // If this method_info must be copied from an existing one, the size computation is trivial. + if (sourceOffset != 0) { + // sourceLength excludes the first 6 bytes for access_flags, name_index and descriptor_index. + return 6 + sourceLength; + } + // 2 bytes each for access_flags, name_index, descriptor_index and attributes_count. + int size = 8; + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + if (code.length > 0) { + if (code.length > 65535) { + throw new MethodTooLargeException( + symbolTable.getClassName(), name, descriptor, code.length); + } + symbolTable.addConstantUtf8(Constants.CODE); + // The Code attribute has 6 header bytes, plus 2, 2, 4 and 2 bytes respectively for max_stack, + // max_locals, code_length and attributes_count, plus the bytecode and the exception table. + size += 16 + code.length + Handler.getExceptionTableSize(firstHandler); + if (stackMapTableEntries != null) { + boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; + symbolTable.addConstantUtf8(useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap"); + // 6 header bytes and 2 bytes for number_of_entries. + size += 8 + stackMapTableEntries.length; + } + if (lineNumberTable != null) { + symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE); + // 6 header bytes and 2 bytes for line_number_table_length. + size += 8 + lineNumberTable.length; + } + if (localVariableTable != null) { + symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE); + // 6 header bytes and 2 bytes for local_variable_table_length. + size += 8 + localVariableTable.length; + } + if (localVariableTypeTable != null) { + symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE); + // 6 header bytes and 2 bytes for local_variable_type_table_length. + size += 8 + localVariableTypeTable.length; + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + size += + lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + size += + lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + } + if (firstCodeAttribute != null) { + size += + firstCodeAttribute.computeAttributesSize( + symbolTable, code.data, code.length, maxStack, maxLocals); + } + } + if (numberOfExceptions > 0) { + symbolTable.addConstantUtf8(Constants.EXCEPTIONS); + size += 8 + 2 * numberOfExceptions; + } + size += Attribute.computeAttributesSize(symbolTable, accessFlags, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (lastRuntimeVisibleParameterAnnotations != null) { + size += + AnnotationWriter.computeParameterAnnotationsSize( + Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + lastRuntimeVisibleParameterAnnotations, + visibleAnnotableParameterCount == 0 + ? lastRuntimeVisibleParameterAnnotations.length + : visibleAnnotableParameterCount); + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + size += + AnnotationWriter.computeParameterAnnotationsSize( + Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, + lastRuntimeInvisibleParameterAnnotations, + invisibleAnnotableParameterCount == 0 + ? lastRuntimeInvisibleParameterAnnotations.length + : invisibleAnnotableParameterCount); + } + if (defaultValue != null) { + symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT); + size += 6 + defaultValue.length; + } + if (parameters != null) { + symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS); + // 6 header bytes and 1 byte for parameters_count. + size += 7 + parameters.length; + } + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the method_info JVMS structure generated by this MethodWriter into the + * given ByteVector. + * + * @param output where the method_info structure must be put. + */ + void putMethodInfo(final ByteVector output) { + boolean useSyntheticAttribute = symbolTable.getMajorVersion() < Opcodes.V1_5; + int mask = useSyntheticAttribute ? Opcodes.ACC_SYNTHETIC : 0; + output.putShort(accessFlags & ~mask).putShort(nameIndex).putShort(descriptorIndex); + // If this method_info must be copied from an existing one, copy it now and return early. + if (sourceOffset != 0) { + output.putByteArray(symbolTable.getSource().classFileBuffer, sourceOffset, sourceLength); + return; + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributeCount = 0; + if (code.length > 0) { + ++attributeCount; + } + if (numberOfExceptions > 0) { + ++attributeCount; + } + if ((accessFlags & Opcodes.ACC_SYNTHETIC) != 0 && useSyntheticAttribute) { + ++attributeCount; + } + if (signatureIndex != 0) { + ++attributeCount; + } + if ((accessFlags & Opcodes.ACC_DEPRECATED) != 0) { + ++attributeCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeVisibleParameterAnnotations != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + ++attributeCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributeCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributeCount; + } + if (defaultValue != null) { + ++attributeCount; + } + if (parameters != null) { + ++attributeCount; + } + if (firstAttribute != null) { + attributeCount += firstAttribute.getAttributeCount(); + } + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + output.putShort(attributeCount); + if (code.length > 0) { + // 2, 2, 4 and 2 bytes respectively for max_stack, max_locals, code_length and + // attributes_count, plus the bytecode and the exception table. + int size = 10 + code.length + Handler.getExceptionTableSize(firstHandler); + int codeAttributeCount = 0; + if (stackMapTableEntries != null) { + // 6 header bytes and 2 bytes for number_of_entries. + size += 8 + stackMapTableEntries.length; + ++codeAttributeCount; + } + if (lineNumberTable != null) { + // 6 header bytes and 2 bytes for line_number_table_length. + size += 8 + lineNumberTable.length; + ++codeAttributeCount; + } + if (localVariableTable != null) { + // 6 header bytes and 2 bytes for local_variable_table_length. + size += 8 + localVariableTable.length; + ++codeAttributeCount; + } + if (localVariableTypeTable != null) { + // 6 header bytes and 2 bytes for local_variable_type_table_length. + size += 8 + localVariableTypeTable.length; + ++codeAttributeCount; + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + size += + lastCodeRuntimeVisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS); + ++codeAttributeCount; + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + size += + lastCodeRuntimeInvisibleTypeAnnotation.computeAnnotationsSize( + Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); + ++codeAttributeCount; + } + if (firstCodeAttribute != null) { + size += + firstCodeAttribute.computeAttributesSize( + symbolTable, code.data, code.length, maxStack, maxLocals); + codeAttributeCount += firstCodeAttribute.getAttributeCount(); + } + output + .putShort(symbolTable.addConstantUtf8(Constants.CODE)) + .putInt(size) + .putShort(maxStack) + .putShort(maxLocals) + .putInt(code.length) + .putByteArray(code.data, 0, code.length); + Handler.putExceptionTable(firstHandler, output); + output.putShort(codeAttributeCount); + if (stackMapTableEntries != null) { + boolean useStackMapTable = symbolTable.getMajorVersion() >= Opcodes.V1_6; + output + .putShort( + symbolTable.addConstantUtf8( + useStackMapTable ? Constants.STACK_MAP_TABLE : "StackMap")) + .putInt(2 + stackMapTableEntries.length) + .putShort(stackMapTableNumberOfEntries) + .putByteArray(stackMapTableEntries.data, 0, stackMapTableEntries.length); + } + if (lineNumberTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LINE_NUMBER_TABLE)) + .putInt(2 + lineNumberTable.length) + .putShort(lineNumberTableLength) + .putByteArray(lineNumberTable.data, 0, lineNumberTable.length); + } + if (localVariableTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TABLE)) + .putInt(2 + localVariableTable.length) + .putShort(localVariableTableLength) + .putByteArray(localVariableTable.data, 0, localVariableTable.length); + } + if (localVariableTypeTable != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.LOCAL_VARIABLE_TYPE_TABLE)) + .putInt(2 + localVariableTypeTable.length) + .putShort(localVariableTypeTableLength) + .putByteArray(localVariableTypeTable.data, 0, localVariableTypeTable.length); + } + if (lastCodeRuntimeVisibleTypeAnnotation != null) { + lastCodeRuntimeVisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_TYPE_ANNOTATIONS), output); + } + if (lastCodeRuntimeInvisibleTypeAnnotation != null) { + lastCodeRuntimeInvisibleTypeAnnotation.putAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS), output); + } + if (firstCodeAttribute != null) { + firstCodeAttribute.putAttributes( + symbolTable, code.data, code.length, maxStack, maxLocals, output); + } + } + if (numberOfExceptions > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.EXCEPTIONS)) + .putInt(2 + 2 * numberOfExceptions) + .putShort(numberOfExceptions); + for (int exceptionIndex : exceptionIndexTable) { + output.putShort(exceptionIndex); + } + } + Attribute.putAttributes(symbolTable, accessFlags, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (lastRuntimeVisibleParameterAnnotations != null) { + AnnotationWriter.putParameterAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS), + lastRuntimeVisibleParameterAnnotations, + visibleAnnotableParameterCount == 0 + ? lastRuntimeVisibleParameterAnnotations.length + : visibleAnnotableParameterCount, + output); + } + if (lastRuntimeInvisibleParameterAnnotations != null) { + AnnotationWriter.putParameterAnnotations( + symbolTable.addConstantUtf8(Constants.RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS), + lastRuntimeInvisibleParameterAnnotations, + invisibleAnnotableParameterCount == 0 + ? lastRuntimeInvisibleParameterAnnotations.length + : invisibleAnnotableParameterCount, + output); + } + if (defaultValue != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.ANNOTATION_DEFAULT)) + .putInt(defaultValue.length) + .putByteArray(defaultValue.data, 0, defaultValue.length); + } + if (parameters != null) { + output + .putShort(symbolTable.addConstantUtf8(Constants.METHOD_PARAMETERS)) + .putInt(1 + parameters.length) + .putByte(parametersCount) + .putByteArray(parameters.data, 0, parameters.length); + } + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this method into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + attributePrototypes.addAttributes(firstCodeAttribute); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleVisitor.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleVisitor.class new file mode 100644 index 00000000..2870e128 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleVisitor.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleVisitor.java new file mode 100644 index 00000000..0e8f4843 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleVisitor.java @@ -0,0 +1,224 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A visitor to visit a Java module. The methods of this class must be called in the following + * order: ( {@code visitMainClass} | ( {@code visitPackage} | {@code visitRequire} | {@code + * visitExport} | {@code visitOpen} | {@code visitUse} | {@code visitProvide} )* ) {@code visitEnd}. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public abstract class ModuleVisitor { + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM6} or {@link Opcodes#ASM7}. + */ + protected final int api; + + /** + * The module visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + protected ModuleVisitor mv; + + /** + * Constructs a new {@link ModuleVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} + * or {@link Opcodes#ASM7}. + */ + protected ModuleVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link ModuleVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM6} + * or {@link Opcodes#ASM7}. + * @param moduleVisitor the module visitor to which this visitor must delegate method calls. May + * be null. + */ + protected ModuleVisitor(final int api, final ModuleVisitor moduleVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + this.mv = moduleVisitor; + } + + /** + * The module visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the module visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public ModuleVisitor getDelegate() { + return mv; + } + + /** + * Visit the main class of the current module. + * + * @param mainClass the internal name of the main class of the current module (see {@link + * Type#getInternalName()}). + */ + public void visitMainClass(final String mainClass) { + if (mv != null) { + mv.visitMainClass(mainClass); + } + } + + /** + * Visit a package of the current module. + * + * @param packaze the internal name of a package (see {@link Type#getInternalName()}). + */ + public void visitPackage(final String packaze) { + if (mv != null) { + mv.visitPackage(packaze); + } + } + + /** + * Visits a dependence of the current module. + * + * @param module the fully qualified name (using dots) of the dependence. + * @param access the access flag of the dependence among {@code ACC_TRANSITIVE}, {@code + * ACC_STATIC_PHASE}, {@code ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param version the module version at compile time, or {@literal null}. + */ + public void visitRequire(final String module, final int access, final String version) { + if (mv != null) { + mv.visitRequire(module, access, version); + } + } + + /** + * Visit an exported package of the current module. + * + * @param packaze the internal name of the exported package (see {@link Type#getInternalName()}). + * @param access the access flag of the exported package, valid values are among {@code + * ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param modules the fully qualified names (using dots) of the modules that can access the public + * classes of the exported package, or {@literal null}. + */ + public void visitExport(final String packaze, final int access, final String... modules) { + if (mv != null) { + mv.visitExport(packaze, access, modules); + } + } + + /** + * Visit an open package of the current module. + * + * @param packaze the internal name of the opened package (see {@link Type#getInternalName()}). + * @param access the access flag of the opened package, valid values are among {@code + * ACC_SYNTHETIC} and {@code ACC_MANDATED}. + * @param modules the fully qualified names (using dots) of the modules that can use deep + * reflection to the classes of the open package, or {@literal null}. + */ + public void visitOpen(final String packaze, final int access, final String... modules) { + if (mv != null) { + mv.visitOpen(packaze, access, modules); + } + } + + /** + * Visit a service used by the current module. The name must be the internal name of an interface + * or a class. + * + * @param service the internal name of the service (see {@link Type#getInternalName()}). + */ + public void visitUse(final String service) { + if (mv != null) { + mv.visitUse(service); + } + } + + /** + * Visit an implementation of a service. + * + * @param service the internal name of the service (see {@link Type#getInternalName()}). + * @param providers the internal names (see {@link Type#getInternalName()}) of the implementations + * of the service (there is at least one provider). + */ + public void visitProvide(final String service, final String... providers) { + if (mv != null) { + mv.visitProvide(service, providers); + } + } + + /** + * Visits the end of the module. This method, which is the last one to be called, is used to + * inform the visitor that everything have been visited. + */ + public void visitEnd() { + if (mv != null) { + mv.visitEnd(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleWriter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleWriter.class new file mode 100644 index 00000000..8257f4c8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleWriter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleWriter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleWriter.java new file mode 100644 index 00000000..18b1a22f --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/ModuleWriter.java @@ -0,0 +1,285 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A {@link ModuleVisitor} that generates the corresponding Module, ModulePackages and + * ModuleMainClass attributes, as defined in the Java Virtual Machine Specification (JVMS). + * + * @see JVMS + * 4.7.25 + * @see JVMS + * 4.7.26 + * @see JVMS + * 4.7.27 + * @author Remi Forax + * @author Eric Bruneton + */ +final class ModuleWriter extends ModuleVisitor { + + /** Where the constants used in this AnnotationWriter must be stored. */ + private final SymbolTable symbolTable; + + /** The module_name_index field of the JVMS Module attribute. */ + private final int moduleNameIndex; + + /** The module_flags field of the JVMS Module attribute. */ + private final int moduleFlags; + + /** The module_version_index field of the JVMS Module attribute. */ + private final int moduleVersionIndex; + + /** The requires_count field of the JVMS Module attribute. */ + private int requiresCount; + + /** The binary content of the 'requires' array of the JVMS Module attribute. */ + private final ByteVector requires; + + /** The exports_count field of the JVMS Module attribute. */ + private int exportsCount; + + /** The binary content of the 'exports' array of the JVMS Module attribute. */ + private final ByteVector exports; + + /** The opens_count field of the JVMS Module attribute. */ + private int opensCount; + + /** The binary content of the 'opens' array of the JVMS Module attribute. */ + private final ByteVector opens; + + /** The uses_count field of the JVMS Module attribute. */ + private int usesCount; + + /** The binary content of the 'uses_index' array of the JVMS Module attribute. */ + private final ByteVector usesIndex; + + /** The provides_count field of the JVMS Module attribute. */ + private int providesCount; + + /** The binary content of the 'provides' array of the JVMS Module attribute. */ + private final ByteVector provides; + + /** The provides_count field of the JVMS ModulePackages attribute. */ + private int packageCount; + + /** The binary content of the 'package_index' array of the JVMS ModulePackages attribute. */ + private final ByteVector packageIndex; + + /** The main_class_index field of the JVMS ModuleMainClass attribute, or 0. */ + private int mainClassIndex; + + ModuleWriter(final SymbolTable symbolTable, final int name, final int access, final int version) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.moduleNameIndex = name; + this.moduleFlags = access; + this.moduleVersionIndex = version; + this.requires = new ByteVector(); + this.exports = new ByteVector(); + this.opens = new ByteVector(); + this.usesIndex = new ByteVector(); + this.provides = new ByteVector(); + this.packageIndex = new ByteVector(); + } + + @Override + public void visitMainClass(final String mainClass) { + this.mainClassIndex = symbolTable.addConstantClass(mainClass).index; + } + + @Override + public void visitPackage(final String packaze) { + packageIndex.putShort(symbolTable.addConstantPackage(packaze).index); + packageCount++; + } + + @Override + public void visitRequire(final String module, final int access, final String version) { + requires + .putShort(symbolTable.addConstantModule(module).index) + .putShort(access) + .putShort(version == null ? 0 : symbolTable.addConstantUtf8(version)); + requiresCount++; + } + + @Override + public void visitExport(final String packaze, final int access, final String... modules) { + exports.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access); + if (modules == null) { + exports.putShort(0); + } else { + exports.putShort(modules.length); + for (String module : modules) { + exports.putShort(symbolTable.addConstantModule(module).index); + } + } + exportsCount++; + } + + @Override + public void visitOpen(final String packaze, final int access, final String... modules) { + opens.putShort(symbolTable.addConstantPackage(packaze).index).putShort(access); + if (modules == null) { + opens.putShort(0); + } else { + opens.putShort(modules.length); + for (String module : modules) { + opens.putShort(symbolTable.addConstantModule(module).index); + } + } + opensCount++; + } + + @Override + public void visitUse(final String service) { + usesIndex.putShort(symbolTable.addConstantClass(service).index); + usesCount++; + } + + @Override + public void visitProvide(final String service, final String... providers) { + provides.putShort(symbolTable.addConstantClass(service).index); + provides.putShort(providers.length); + for (String provider : providers) { + provides.putShort(symbolTable.addConstantClass(provider).index); + } + providesCount++; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + /** + * Returns the number of Module, ModulePackages and ModuleMainClass attributes generated by this + * ModuleWriter. + * + * @return the number of Module, ModulePackages and ModuleMainClass attributes (between 1 and 3). + */ + int getAttributeCount() { + return 1 + (packageCount > 0 ? 1 : 0) + (mainClassIndex > 0 ? 1 : 0); + } + + /** + * Returns the size of the Module, ModulePackages and ModuleMainClass attributes generated by this + * ModuleWriter. Also add the names of these attributes in the constant pool. + * + * @return the size in bytes of the Module, ModulePackages and ModuleMainClass attributes. + */ + int computeAttributesSize() { + symbolTable.addConstantUtf8(Constants.MODULE); + // 6 attribute header bytes, 6 bytes for name, flags and version, and 5 * 2 bytes for counts. + int size = + 22 + requires.length + exports.length + opens.length + usesIndex.length + provides.length; + if (packageCount > 0) { + symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES); + // 6 attribute header bytes, and 2 bytes for package_count. + size += 8 + packageIndex.length; + } + if (mainClassIndex > 0) { + symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS); + // 6 attribute header bytes, and 2 bytes for main_class_index. + size += 8; + } + return size; + } + + /** + * Puts the Module, ModulePackages and ModuleMainClass attributes generated by this ModuleWriter + * in the given ByteVector. + * + * @param output where the attributes must be put. + */ + void putAttributes(final ByteVector output) { + // 6 bytes for name, flags and version, and 5 * 2 bytes for counts. + int moduleAttributeLength = + 16 + requires.length + exports.length + opens.length + usesIndex.length + provides.length; + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE)) + .putInt(moduleAttributeLength) + .putShort(moduleNameIndex) + .putShort(moduleFlags) + .putShort(moduleVersionIndex) + .putShort(requiresCount) + .putByteArray(requires.data, 0, requires.length) + .putShort(exportsCount) + .putByteArray(exports.data, 0, exports.length) + .putShort(opensCount) + .putByteArray(opens.data, 0, opens.length) + .putShort(usesCount) + .putByteArray(usesIndex.data, 0, usesIndex.length) + .putShort(providesCount) + .putByteArray(provides.data, 0, provides.length); + if (packageCount > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE_PACKAGES)) + .putInt(2 + packageIndex.length) + .putShort(packageCount) + .putByteArray(packageIndex.data, 0, packageIndex.length); + } + if (mainClassIndex > 0) { + output + .putShort(symbolTable.addConstantUtf8(Constants.MODULE_MAIN_CLASS)) + .putInt(2) + .putShort(mainClassIndex); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Opcodes.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Opcodes.class new file mode 100644 index 00000000..b652c805 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Opcodes.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Opcodes.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Opcodes.java new file mode 100644 index 00000000..8353b787 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Opcodes.java @@ -0,0 +1,590 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * The JVM opcodes, access flags and array type codes. This interface does not define all the JVM + * opcodes because some opcodes are automatically handled. For example, the xLOAD and xSTORE opcodes + * are automatically replaced by xLOAD_n and xSTORE_n opcodes when possible. The xLOAD_n and + * xSTORE_n opcodes are therefore not defined in this interface. Likewise for LDC, automatically + * replaced by LDC_W or LDC2_W when necessary, WIDE, GOTO_W and JSR_W. + * + * @see JVMS 6 + * @author Eric Bruneton + * @author Eugene Kuleshov + */ +// DontCheck(InterfaceIsType): can't be fixed (for backward binary compatibility). +public interface Opcodes { + + // ASM API versions. + + int ASM4 = 4 << 16 | 0 << 8; + int ASM5 = 5 << 16 | 0 << 8; + int ASM6 = 6 << 16 | 0 << 8; + int ASM7 = 7 << 16 | 0 << 8; + int ASM8 = 8 << 16 | 0 << 8; + int ASM9 = 9 << 16 | 0 << 8; + + /* + * Internal flags used to redirect calls to deprecated methods. For instance, if a visitOldStuff + * method in API_OLD is deprecated and replaced with visitNewStuff in API_NEW, then the + * redirection should be done as follows: + * + *

+      * public class StuffVisitor {
+      *   ...
+      *
+      *   @Deprecated public void visitOldStuff(int arg, ...) {
+      *     // SOURCE_DEPRECATED means "a call from a deprecated method using the old 'api' value".
+      *     visitNewStuf(arg | (api < API_NEW ? SOURCE_DEPRECATED : 0), ...);
+      *   }
+      *
+      *   public void visitNewStuff(int argAndSource, ...) {
+      *     if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+      *       visitOldStuff(argAndSource, ...);
+      *     } else {
+      *       int arg = argAndSource & ~SOURCE_MASK;
+      *       [ do stuff ]
+      *     }
+      *   }
+      * }
+      * 
+ * + *

If 'api' is equal to API_NEW, there are two cases: + * + *

    + *
  • call visitNewStuff: the redirection test is skipped and 'do stuff' is executed directly. + *
  • call visitOldSuff: the source is not set to SOURCE_DEPRECATED before calling + * visitNewStuff, but the redirection test is skipped anyway in visitNewStuff, which + * directly executes 'do stuff'. + *
+ * + *

If 'api' is equal to API_OLD, there are two cases: + * + *

    + *
  • call visitOldSuff: the source is set to SOURCE_DEPRECATED before calling visitNewStuff. + * Because of this visitNewStuff does not redirect back to visitOldStuff, and instead + * executes 'do stuff'. + *
  • call visitNewStuff: the call is redirected to visitOldStuff because the source is 0. + * visitOldStuff now sets the source to SOURCE_DEPRECATED and calls visitNewStuff back. This + * time visitNewStuff does not redirect the call, and instead executes 'do stuff'. + *
+ * + *

User subclasses

+ * + *

If a user subclass overrides one of these methods, there are only two cases: either 'api' is + * API_OLD and visitOldStuff is overridden (and visitNewStuff is not), or 'api' is API_NEW or + * more, and visitNewStuff is overridden (and visitOldStuff is not). Any other case is a user + * programming error. + * + *

If 'api' is equal to API_NEW, the class hierarchy is equivalent to + * + *

+      * public class StuffVisitor {
+      *   @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+      *   public void visitNewStuff(int arg, ...) { [ do stuff ] }
+      * }
+      * class UserStuffVisitor extends StuffVisitor {
+      *   @Override public void visitNewStuff(int arg, ...) {
+      *     super.visitNewStuff(int arg, ...); // optional
+      *     [ do user stuff ]
+      *   }
+      * }
+      * 
+ * + *

It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff' and 'do + * user stuff' will be executed, in this order. + * + *

If 'api' is equal to API_OLD, the class hierarchy is equivalent to + * + *

+      * public class StuffVisitor {
+      *   @Deprecated public void visitOldStuff(int arg, ...) {
+      *     visitNewStuff(arg | SOURCE_DEPRECATED, ...);
+      *   }
+      *   public void visitNewStuff(int argAndSource...) {
+      *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+      *       visitOldStuff(argAndSource, ...);
+      *     } else {
+      *       int arg = argAndSource & ~SOURCE_MASK;
+      *       [ do stuff ]
+      *     }
+      *   }
+      * }
+      * class UserStuffVisitor extends StuffVisitor {
+      *   @Override public void visitOldStuff(int arg, ...) {
+      *     super.visitOldStuff(int arg, ...); // optional
+      *     [ do user stuff ]
+      *   }
+      * }
+      * 
+ * + *

and there are two cases: + * + *

    + *
  • call visitOldStuff: in the call to super.visitOldStuff, the source is set to + * SOURCE_DEPRECATED and visitNewStuff is called. Here 'do stuff' is run because the source + * was previously set to SOURCE_DEPRECATED, and execution eventually returns to + * UserStuffVisitor.visitOldStuff, where 'do user stuff' is run. + *
  • call visitNewStuff: the call is redirected to UserStuffVisitor.visitOldStuff because the + * source is 0. Execution continues as in the previous case, resulting in 'do stuff' and 'do + * user stuff' being executed, in this order. + *
+ * + *

ASM subclasses

+ * + *

In ASM packages, subclasses of StuffVisitor can typically be sub classed again by the user, + * and can be used with API_OLD or API_NEW. Because of this, if such a subclass must override + * visitNewStuff, it must do so in the following way (and must not override visitOldStuff): + * + *

+      * public class AsmStuffVisitor extends StuffVisitor {
+      *   @Override public void visitNewStuff(int argAndSource, ...) {
+      *     if (api < API_NEW && (argAndSource & SOURCE_DEPRECATED) == 0) {
+      *       super.visitNewStuff(argAndSource, ...);
+      *       return;
+      *     }
+      *     super.visitNewStuff(argAndSource, ...); // optional
+      *     int arg = argAndSource & ~SOURCE_MASK;
+      *     [ do other stuff ]
+      *   }
+      * }
+      * 
+ * + *

If a user class extends this with 'api' equal to API_NEW, the class hierarchy is equivalent + * to + * + *

+      * public class StuffVisitor {
+      *   @Deprecated public void visitOldStuff(int arg, ...) { visitNewStuf(arg, ...); }
+      *   public void visitNewStuff(int arg, ...) { [ do stuff ] }
+      * }
+      * public class AsmStuffVisitor extends StuffVisitor {
+      *   @Override public void visitNewStuff(int arg, ...) {
+      *     super.visitNewStuff(arg, ...);
+      *     [ do other stuff ]
+      *   }
+      * }
+      * class UserStuffVisitor extends StuffVisitor {
+      *   @Override public void visitNewStuff(int arg, ...) {
+      *     super.visitNewStuff(int arg, ...);
+      *     [ do user stuff ]
+      *   }
+      * }
+      * 
+ * + *

It is then obvious that whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do + * other stuff' and 'do user stuff' will be executed, in this order. If, on the other hand, a user + * class extends AsmStuffVisitor with 'api' equal to API_OLD, the class hierarchy is equivalent to + * + *

+      * public class StuffVisitor {
+      *   @Deprecated public void visitOldStuff(int arg, ...) {
+      *     visitNewStuf(arg | SOURCE_DEPRECATED, ...);
+      *   }
+      *   public void visitNewStuff(int argAndSource, ...) {
+      *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+      *       visitOldStuff(argAndSource, ...);
+      *     } else {
+      *       int arg = argAndSource & ~SOURCE_MASK;
+      *       [ do stuff ]
+      *     }
+      *   }
+      * }
+      * public class AsmStuffVisitor extends StuffVisitor {
+      *   @Override public void visitNewStuff(int argAndSource, ...) {
+      *     if ((argAndSource & SOURCE_DEPRECATED) == 0) {
+      *       super.visitNewStuff(argAndSource, ...);
+      *       return;
+      *     }
+      *     super.visitNewStuff(argAndSource, ...); // optional
+      *     int arg = argAndSource & ~SOURCE_MASK;
+      *     [ do other stuff ]
+      *   }
+      * }
+      * class UserStuffVisitor extends StuffVisitor {
+      *   @Override public void visitOldStuff(int arg, ...) {
+      *     super.visitOldStuff(arg, ...);
+      *     [ do user stuff ]
+      *   }
+      * }
+      * 
+ * + *

and, here again, whether visitNewStuff or visitOldStuff is called, 'do stuff', 'do other + * stuff' and 'do user stuff' will be executed, in this order (exercise left to the reader). + * + *

Notes

+ * + *
    + *
  • the SOURCE_DEPRECATED flag is set only if 'api' is API_OLD, just before calling + * visitNewStuff. By hypothesis, this method is not overridden by the user. Therefore, user + * classes can never see this flag. Only ASM subclasses must take care of extracting the + * actual argument value by clearing the source flags. + *
  • because the SOURCE_DEPRECATED flag is immediately cleared in the caller, the caller can + * call visitOldStuff or visitNewStuff (in 'do stuff' and 'do user stuff') on a delegate + * visitor without any risks (breaking the redirection logic, "leaking" the flag, etc). + *
  • all the scenarios discussed above are unit tested in MethodVisitorTest. + *
+ */ + + int SOURCE_DEPRECATED = 0x100; + int SOURCE_MASK = SOURCE_DEPRECATED; + + // Java ClassFile versions (the minor version is stored in the 16 most significant bits, and the + // major version in the 16 least significant bits). + + int V1_1 = 3 << 16 | 45; + int V1_2 = 0 << 16 | 46; + int V1_3 = 0 << 16 | 47; + int V1_4 = 0 << 16 | 48; + int V1_5 = 0 << 16 | 49; + int V1_6 = 0 << 16 | 50; + int V1_7 = 0 << 16 | 51; + int V1_8 = 0 << 16 | 52; + int V9 = 0 << 16 | 53; + int V10 = 0 << 16 | 54; + int V11 = 0 << 16 | 55; + int V12 = 0 << 16 | 56; + int V13 = 0 << 16 | 57; + int V14 = 0 << 16 | 58; + int V15 = 0 << 16 | 59; + int V16 = 0 << 16 | 60; + int V17 = 0 << 16 | 61; + int V18 = 0 << 16 | 62; + int V19 = 0 << 16 | 63; + int V20 = 0 << 16 | 64; + int V21 = 0 << 16 | 65; + int V22 = 0 << 16 | 66; + int V23 = 0 << 16 | 67; + + /** + * Version flag indicating that the class is using 'preview' features. + * + *

{@code version & V_PREVIEW == V_PREVIEW} tests if a version is flagged with {@code + * V_PREVIEW}. + */ + int V_PREVIEW = 0xFFFF0000; + + // Access flags values, defined in + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.5-200-A.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.6-200-A.1 + // - https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.25 + + int ACC_PUBLIC = 0x0001; // class, field, method + int ACC_PRIVATE = 0x0002; // class, field, method + int ACC_PROTECTED = 0x0004; // class, field, method + int ACC_STATIC = 0x0008; // field, method + int ACC_FINAL = 0x0010; // class, field, method, parameter + int ACC_SUPER = 0x0020; // class + int ACC_SYNCHRONIZED = 0x0020; // method + int ACC_OPEN = 0x0020; // module + int ACC_TRANSITIVE = 0x0020; // module requires + int ACC_VOLATILE = 0x0040; // field + int ACC_BRIDGE = 0x0040; // method + int ACC_STATIC_PHASE = 0x0040; // module requires + int ACC_VARARGS = 0x0080; // method + int ACC_TRANSIENT = 0x0080; // field + int ACC_NATIVE = 0x0100; // method + int ACC_INTERFACE = 0x0200; // class + int ACC_ABSTRACT = 0x0400; // class, method + int ACC_STRICT = 0x0800; // method + int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module * + int ACC_ANNOTATION = 0x2000; // class + int ACC_ENUM = 0x4000; // class(?) field inner + int ACC_MANDATED = 0x8000; // field, method, parameter, module, module * + int ACC_MODULE = 0x8000; // class + + // ASM specific access flags. + // WARNING: the 16 least significant bits must NOT be used, to avoid conflicts with standard + // access flags, and also to make sure that these flags are automatically filtered out when + // written in class files (because access flags are stored using 16 bits only). + + int ACC_RECORD = 0x10000; // class + int ACC_DEPRECATED = 0x20000; // class, field, method + + // Possible values for the type operand of the NEWARRAY instruction. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.newarray. + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + // Possible values for the reference_kind field of CONSTANT_MethodHandle_info structures. + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4.8. + + int H_GETFIELD = 1; + int H_GETSTATIC = 2; + int H_PUTFIELD = 3; + int H_PUTSTATIC = 4; + int H_INVOKEVIRTUAL = 5; + int H_INVOKESTATIC = 6; + int H_INVOKESPECIAL = 7; + int H_NEWINVOKESPECIAL = 8; + int H_INVOKEINTERFACE = 9; + + // ASM specific stack map frame types, used in {@link ClassVisitor#visitFrame}. + + /** An expanded frame. See {@link ClassReader#EXPAND_FRAMES}. */ + int F_NEW = -1; + + /** A compressed frame with complete frame data. */ + int F_FULL = 0; + + /** + * A compressed frame where locals are the same as the locals in the previous frame, except that + * additional 1-3 locals are defined, and with an empty stack. + */ + int F_APPEND = 1; + + /** + * A compressed frame where locals are the same as the locals in the previous frame, except that + * the last 1-3 locals are absent and with an empty stack. + */ + int F_CHOP = 2; + + /** + * A compressed frame with exactly the same locals as the previous frame and with an empty stack. + */ + int F_SAME = 3; + + /** + * A compressed frame with exactly the same locals as the previous frame and with a single value + * on the stack. + */ + int F_SAME1 = 4; + + // Standard stack map frame element types, used in {@link ClassVisitor#visitFrame}. + + Integer TOP = Frame.ITEM_TOP; + Integer INTEGER = Frame.ITEM_INTEGER; + Integer FLOAT = Frame.ITEM_FLOAT; + Integer DOUBLE = Frame.ITEM_DOUBLE; + Integer LONG = Frame.ITEM_LONG; + Integer NULL = Frame.ITEM_NULL; + Integer UNINITIALIZED_THIS = Frame.ITEM_UNINITIALIZED_THIS; + + // The JVM opcode values (with the MethodVisitor method name used to visit them in comment, and + // where '-' means 'same method name as on the previous line'). + // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html. + + int NOP = 0; // visitInsn + int ACONST_NULL = 1; // - + int ICONST_M1 = 2; // - + int ICONST_0 = 3; // - + int ICONST_1 = 4; // - + int ICONST_2 = 5; // - + int ICONST_3 = 6; // - + int ICONST_4 = 7; // - + int ICONST_5 = 8; // - + int LCONST_0 = 9; // - + int LCONST_1 = 10; // - + int FCONST_0 = 11; // - + int FCONST_1 = 12; // - + int FCONST_2 = 13; // - + int DCONST_0 = 14; // - + int DCONST_1 = 15; // - + int BIPUSH = 16; // visitIntInsn + int SIPUSH = 17; // - + int LDC = 18; // visitLdcInsn + int ILOAD = 21; // visitVarInsn + int LLOAD = 22; // - + int FLOAD = 23; // - + int DLOAD = 24; // - + int ALOAD = 25; // - + int IALOAD = 46; // visitInsn + int LALOAD = 47; // - + int FALOAD = 48; // - + int DALOAD = 49; // - + int AALOAD = 50; // - + int BALOAD = 51; // - + int CALOAD = 52; // - + int SALOAD = 53; // - + int ISTORE = 54; // visitVarInsn + int LSTORE = 55; // - + int FSTORE = 56; // - + int DSTORE = 57; // - + int ASTORE = 58; // - + int IASTORE = 79; // visitInsn + int LASTORE = 80; // - + int FASTORE = 81; // - + int DASTORE = 82; // - + int AASTORE = 83; // - + int BASTORE = 84; // - + int CASTORE = 85; // - + int SASTORE = 86; // - + int POP = 87; // - + int POP2 = 88; // - + int DUP = 89; // - + int DUP_X1 = 90; // - + int DUP_X2 = 91; // - + int DUP2 = 92; // - + int DUP2_X1 = 93; // - + int DUP2_X2 = 94; // - + int SWAP = 95; // - + int IADD = 96; // - + int LADD = 97; // - + int FADD = 98; // - + int DADD = 99; // - + int ISUB = 100; // - + int LSUB = 101; // - + int FSUB = 102; // - + int DSUB = 103; // - + int IMUL = 104; // - + int LMUL = 105; // - + int FMUL = 106; // - + int DMUL = 107; // - + int IDIV = 108; // - + int LDIV = 109; // - + int FDIV = 110; // - + int DDIV = 111; // - + int IREM = 112; // - + int LREM = 113; // - + int FREM = 114; // - + int DREM = 115; // - + int INEG = 116; // - + int LNEG = 117; // - + int FNEG = 118; // - + int DNEG = 119; // - + int ISHL = 120; // - + int LSHL = 121; // - + int ISHR = 122; // - + int LSHR = 123; // - + int IUSHR = 124; // - + int LUSHR = 125; // - + int IAND = 126; // - + int LAND = 127; // - + int IOR = 128; // - + int LOR = 129; // - + int IXOR = 130; // - + int LXOR = 131; // - + int IINC = 132; // visitIincInsn + int I2L = 133; // visitInsn + int I2F = 134; // - + int I2D = 135; // - + int L2I = 136; // - + int L2F = 137; // - + int L2D = 138; // - + int F2I = 139; // - + int F2L = 140; // - + int F2D = 141; // - + int D2I = 142; // - + int D2L = 143; // - + int D2F = 144; // - + int I2B = 145; // - + int I2C = 146; // - + int I2S = 147; // - + int LCMP = 148; // - + int FCMPL = 149; // - + int FCMPG = 150; // - + int DCMPL = 151; // - + int DCMPG = 152; // - + int IFEQ = 153; // visitJumpInsn + int IFNE = 154; // - + int IFLT = 155; // - + int IFGE = 156; // - + int IFGT = 157; // - + int IFLE = 158; // - + int IF_ICMPEQ = 159; // - + int IF_ICMPNE = 160; // - + int IF_ICMPLT = 161; // - + int IF_ICMPGE = 162; // - + int IF_ICMPGT = 163; // - + int IF_ICMPLE = 164; // - + int IF_ACMPEQ = 165; // - + int IF_ACMPNE = 166; // - + int GOTO = 167; // - + int JSR = 168; // - + int RET = 169; // visitVarInsn + int TABLESWITCH = 170; // visiTableSwitchInsn + int LOOKUPSWITCH = 171; // visitLookupSwitch + int IRETURN = 172; // visitInsn + int LRETURN = 173; // - + int FRETURN = 174; // - + int DRETURN = 175; // - + int ARETURN = 176; // - + int RETURN = 177; // - + int GETSTATIC = 178; // visitFieldInsn + int PUTSTATIC = 179; // - + int GETFIELD = 180; // - + int PUTFIELD = 181; // - + int INVOKEVIRTUAL = 182; // visitMethodInsn + int INVOKESPECIAL = 183; // - + int INVOKESTATIC = 184; // - + int INVOKEINTERFACE = 185; // - + int INVOKEDYNAMIC = 186; // visitInvokeDynamicInsn + int NEW = 187; // visitTypeInsn + int NEWARRAY = 188; // visitIntInsn + int ANEWARRAY = 189; // visitTypeInsn + int ARRAYLENGTH = 190; // visitInsn + int ATHROW = 191; // - + int CHECKCAST = 192; // visitTypeInsn + int INSTANCEOF = 193; // - + int MONITORENTER = 194; // visitInsn + int MONITOREXIT = 195; // - + int MULTIANEWARRAY = 197; // visitMultiANewArrayInsn + int IFNULL = 198; // visitJumpInsn + int IFNONNULL = 199; // - +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentVisitor.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentVisitor.class new file mode 100644 index 00000000..000edf7c Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentVisitor.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentVisitor.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentVisitor.java new file mode 100644 index 00000000..ad0aad6b --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentVisitor.java @@ -0,0 +1,181 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A visitor to visit a record component. The methods of this class must be called in the following + * order: ( {@code visitAnnotation} | {@code visitTypeAnnotation} | {@code visitAttribute} )* {@code + * visitEnd}. + * + * @author Remi Forax + * @author Eric Bruneton + */ +public abstract class RecordComponentVisitor { + /** + * The ASM API version implemented by this visitor. The value of this field must be one of {@link + * Opcodes#ASM8} or {@link Opcodes#ASM9}. + */ + protected final int api; + + /** + * The record visitor to which this visitor must delegate method calls. May be {@literal null}. + */ + protected RecordComponentVisitor delegate; + + /** + * Constructs a new {@link RecordComponentVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be one of {@link Opcodes#ASM8} + * or {@link Opcodes#ASM9}. + */ + protected RecordComponentVisitor(final int api) { + this(api, null); + } + + /** + * Constructs a new {@link RecordComponentVisitor}. + * + * @param api the ASM API version implemented by this visitor. Must be {@link Opcodes#ASM8}. + * @param recordComponentVisitor the record component visitor to which this visitor must delegate + * method calls. May be null. + */ + protected RecordComponentVisitor( + final int api, final RecordComponentVisitor recordComponentVisitor) { + if (api != Opcodes.ASM9 + && api != Opcodes.ASM8 + && api != Opcodes.ASM7 + && api != Opcodes.ASM6 + && api != Opcodes.ASM5 + && api != Opcodes.ASM4) { + throw new IllegalArgumentException("Unsupported api " + api); + } + this.api = api; + this.delegate = recordComponentVisitor; + } + + /** + * The record visitor to which this visitor must delegate method calls. May be {@literal null}. + * + * @return the record visitor to which this visitor must delegate method calls, or {@literal + * null}. + */ + public RecordComponentVisitor getDelegate() { + return delegate; + } + + /** + * Visits an annotation of the record component. + * + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (delegate != null) { + return delegate.visitAnnotation(descriptor, visible); + } + return null; + } + + /** + * Visits an annotation on a type in the record component signature. + * + * @param typeRef a reference to the annotated type. The sort of this type reference must be + * {@link TypeReference#CLASS_TYPE_PARAMETER}, {@link + * TypeReference#CLASS_TYPE_PARAMETER_BOUND} or {@link TypeReference#CLASS_EXTENDS}. See + * {@link TypeReference}. + * @param typePath the path to the annotated type argument, wildcard bound, array element type, or + * static inner type within 'typeRef'. May be {@literal null} if the annotation targets + * 'typeRef' as a whole. + * @param descriptor the class descriptor of the annotation class. + * @param visible {@literal true} if the annotation is visible at runtime. + * @return a visitor to visit the annotation values, or {@literal null} if this visitor is not + * interested in visiting this annotation. + */ + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (delegate != null) { + return delegate.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + return null; + } + + /** + * Visits a non standard attribute of the record component. + * + * @param attribute an attribute. + */ + public void visitAttribute(final Attribute attribute) { + if (delegate != null) { + delegate.visitAttribute(attribute); + } + } + + /** + * Visits the end of the record component. This method, which is the last one to be called, is + * used to inform the visitor that everything have been visited. + */ + public void visitEnd() { + if (delegate != null) { + delegate.visitEnd(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentWriter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentWriter.class new file mode 100644 index 00000000..d16bd064 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentWriter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentWriter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentWriter.java new file mode 100644 index 00000000..3098cf70 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/RecordComponentWriter.java @@ -0,0 +1,257 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +final class RecordComponentWriter extends RecordComponentVisitor { + /** Where the constants used in this RecordComponentWriter must be stored. */ + private final SymbolTable symbolTable; + + // Note: fields are ordered as in the record_component_info structure, and those related to + // attributes are ordered as in Section 4.7 of the JVMS. + + /** The name_index field of the Record attribute. */ + private final int nameIndex; + + /** The descriptor_index field of the Record attribute. */ + private final int descriptorIndex; + + /** + * The signature_index field of the Signature attribute of this record component, or 0 if there is + * no Signature attribute. + */ + private int signatureIndex; + + /** + * The last runtime visible annotation of this record component. The previous ones can be accessed + * with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleAnnotation; + + /** + * The last runtime invisible annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleAnnotation; + + /** + * The last runtime visible type annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeVisibleTypeAnnotation; + + /** + * The last runtime invisible type annotation of this record component. The previous ones can be + * accessed with the {@link AnnotationWriter#previousAnnotation} field. May be {@literal null}. + */ + private AnnotationWriter lastRuntimeInvisibleTypeAnnotation; + + /** + * The first non standard attribute of this record component. The next ones can be accessed with + * the {@link Attribute#nextAttribute} field. May be {@literal null}. + * + *

WARNING: this list stores the attributes in the reverse order of their visit. + * firstAttribute is actually the last attribute visited in {@link #visitAttribute(Attribute)}. + * The {@link #putRecordComponentInfo(ByteVector)} method writes the attributes in the order + * defined by this list, i.e. in the reverse order specified by the user. + */ + private Attribute firstAttribute; + + /** + * Constructs a new {@link RecordComponentWriter}. + * + * @param symbolTable where the constants used in this RecordComponentWriter must be stored. + * @param name the record component name. + * @param descriptor the record component descriptor (see {@link Type}). + * @param signature the record component signature. May be {@literal null}. + */ + RecordComponentWriter( + final SymbolTable symbolTable, + final String name, + final String descriptor, + final String signature) { + super(/* latest api = */ Opcodes.ASM9); + this.symbolTable = symbolTable; + this.nameIndex = symbolTable.addConstantUtf8(name); + this.descriptorIndex = symbolTable.addConstantUtf8(descriptor); + if (signature != null) { + this.signatureIndex = symbolTable.addConstantUtf8(signature); + } + } + + // ----------------------------------------------------------------------------------------------- + // Implementation of the FieldVisitor abstract class + // ----------------------------------------------------------------------------------------------- + + @Override + public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeVisibleAnnotation); + } else { + return lastRuntimeInvisibleAnnotation = + AnnotationWriter.create(symbolTable, descriptor, lastRuntimeInvisibleAnnotation); + } + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) { + if (visible) { + return lastRuntimeVisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeVisibleTypeAnnotation); + } else { + return lastRuntimeInvisibleTypeAnnotation = + AnnotationWriter.create( + symbolTable, typeRef, typePath, descriptor, lastRuntimeInvisibleTypeAnnotation); + } + } + + @Override + public void visitAttribute(final Attribute attribute) { + // Store the attributes in the reverse order of their visit by this method. + attribute.nextAttribute = firstAttribute; + firstAttribute = attribute; + } + + @Override + public void visitEnd() { + // Nothing to do. + } + + // ----------------------------------------------------------------------------------------------- + // Utility methods + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the size of the record component JVMS structure generated by this + * RecordComponentWriter. Also adds the names of the attributes of this record component in the + * constant pool. + * + * @return the size in bytes of the record_component_info of the Record attribute. + */ + int computeRecordComponentInfoSize() { + // name_index, descriptor_index and attributes_count fields use 6 bytes. + int size = 6; + size += Attribute.computeAttributesSize(symbolTable, 0, signatureIndex); + size += + AnnotationWriter.computeAnnotationsSize( + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation); + if (firstAttribute != null) { + size += firstAttribute.computeAttributesSize(symbolTable); + } + return size; + } + + /** + * Puts the content of the record component generated by this RecordComponentWriter into the given + * ByteVector. + * + * @param output where the record_component_info structure must be put. + */ + void putRecordComponentInfo(final ByteVector output) { + output.putShort(nameIndex).putShort(descriptorIndex); + // Compute and put the attributes_count field. + // For ease of reference, we use here the same attribute order as in Section 4.7 of the JVMS. + int attributesCount = 0; + if (signatureIndex != 0) { + ++attributesCount; + } + if (lastRuntimeVisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeVisibleTypeAnnotation != null) { + ++attributesCount; + } + if (lastRuntimeInvisibleTypeAnnotation != null) { + ++attributesCount; + } + if (firstAttribute != null) { + attributesCount += firstAttribute.getAttributeCount(); + } + output.putShort(attributesCount); + Attribute.putAttributes(symbolTable, 0, signatureIndex, output); + AnnotationWriter.putAnnotations( + symbolTable, + lastRuntimeVisibleAnnotation, + lastRuntimeInvisibleAnnotation, + lastRuntimeVisibleTypeAnnotation, + lastRuntimeInvisibleTypeAnnotation, + output); + if (firstAttribute != null) { + firstAttribute.putAttributes(symbolTable, output); + } + } + + /** + * Collects the attributes of this record component into the given set of attribute prototypes. + * + * @param attributePrototypes a set of attribute prototypes. + */ + final void collectAttributePrototypes(final Attribute.Set attributePrototypes) { + attributePrototypes.addAttributes(firstAttribute); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Symbol.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Symbol.class new file mode 100644 index 00000000..6d1c4f22 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Symbol.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Symbol.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Symbol.java new file mode 100644 index 00000000..a45f75a0 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Symbol.java @@ -0,0 +1,291 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * An entry of the constant pool, of the BootstrapMethods attribute, or of the (ASM specific) type + * table of a class. + * + * @see JVMS + * 4.4 + * @see JVMS + * 4.7.23 + * @author Eric Bruneton + */ +abstract class Symbol { + + // Tag values for the constant pool entries (using the same order as in the JVMS). + + /** The tag value of CONSTANT_Class_info JVMS structures. */ + static final int CONSTANT_CLASS_TAG = 7; + + /** The tag value of CONSTANT_Fieldref_info JVMS structures. */ + static final int CONSTANT_FIELDREF_TAG = 9; + + /** The tag value of CONSTANT_Methodref_info JVMS structures. */ + static final int CONSTANT_METHODREF_TAG = 10; + + /** The tag value of CONSTANT_InterfaceMethodref_info JVMS structures. */ + static final int CONSTANT_INTERFACE_METHODREF_TAG = 11; + + /** The tag value of CONSTANT_String_info JVMS structures. */ + static final int CONSTANT_STRING_TAG = 8; + + /** The tag value of CONSTANT_Integer_info JVMS structures. */ + static final int CONSTANT_INTEGER_TAG = 3; + + /** The tag value of CONSTANT_Float_info JVMS structures. */ + static final int CONSTANT_FLOAT_TAG = 4; + + /** The tag value of CONSTANT_Long_info JVMS structures. */ + static final int CONSTANT_LONG_TAG = 5; + + /** The tag value of CONSTANT_Double_info JVMS structures. */ + static final int CONSTANT_DOUBLE_TAG = 6; + + /** The tag value of CONSTANT_NameAndType_info JVMS structures. */ + static final int CONSTANT_NAME_AND_TYPE_TAG = 12; + + /** The tag value of CONSTANT_Utf8_info JVMS structures. */ + static final int CONSTANT_UTF8_TAG = 1; + + /** The tag value of CONSTANT_MethodHandle_info JVMS structures. */ + static final int CONSTANT_METHOD_HANDLE_TAG = 15; + + /** The tag value of CONSTANT_MethodType_info JVMS structures. */ + static final int CONSTANT_METHOD_TYPE_TAG = 16; + + /** The tag value of CONSTANT_Dynamic_info JVMS structures. */ + static final int CONSTANT_DYNAMIC_TAG = 17; + + /** The tag value of CONSTANT_InvokeDynamic_info JVMS structures. */ + static final int CONSTANT_INVOKE_DYNAMIC_TAG = 18; + + /** The tag value of CONSTANT_Module_info JVMS structures. */ + static final int CONSTANT_MODULE_TAG = 19; + + /** The tag value of CONSTANT_Package_info JVMS structures. */ + static final int CONSTANT_PACKAGE_TAG = 20; + + // Tag values for the BootstrapMethods attribute entries (ASM specific tag). + + /** The tag value of the BootstrapMethods attribute entries. */ + static final int BOOTSTRAP_METHOD_TAG = 64; + + // Tag values for the type table entries (ASM specific tags). + + /** The tag value of a normal type entry in the (ASM specific) type table of a class. */ + static final int TYPE_TAG = 128; + + /** + * The tag value of an uninitialized type entry in the type table of a class. This type is used + * for the normal case where the NEW instruction is before the <init> constructor call (in + * bytecode offset order), i.e. when the label of the NEW instruction is resolved when the + * constructor call is visited. If the NEW instruction is after the constructor call, use the + * {@link #FORWARD_UNINITIALIZED_TYPE_TAG} tag value instead. + */ + static final int UNINITIALIZED_TYPE_TAG = 129; + + /** + * The tag value of an uninitialized type entry in the type table of a class. This type is used + * for the unusual case where the NEW instruction is after the <init> constructor call (in + * bytecode offset order), i.e. when the label of the NEW instruction is not resolved when the + * constructor call is visited. If the NEW instruction is before the constructor call, use the + * {@link #UNINITIALIZED_TYPE_TAG} tag value instead. + */ + static final int FORWARD_UNINITIALIZED_TYPE_TAG = 130; + + /** The tag value of a merged type entry in the (ASM specific) type table of a class. */ + static final int MERGED_TYPE_TAG = 131; + + // Instance fields. + + /** + * The index of this symbol in the constant pool, in the BootstrapMethods attribute, or in the + * (ASM specific) type table of a class (depending on the {@link #tag} value). + */ + final int index; + + /** + * A tag indicating the type of this symbol. Must be one of the static tag values defined in this + * class. + */ + final int tag; + + /** + * The internal name of the owner class of this symbol. Only used for {@link + * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link + * #CONSTANT_INTERFACE_METHODREF_TAG}, and {@link #CONSTANT_METHOD_HANDLE_TAG} symbols. + */ + final String owner; + + /** + * The name of the class field or method corresponding to this symbol. Only used for {@link + * #CONSTANT_FIELDREF_TAG}, {@link #CONSTANT_METHODREF_TAG}, {@link + * #CONSTANT_INTERFACE_METHODREF_TAG}, {@link #CONSTANT_NAME_AND_TYPE_TAG}, {@link + * #CONSTANT_METHOD_HANDLE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols. + */ + final String name; + + /** + * The string value of this symbol. This is: + * + *

    + *
  • a field or method descriptor for {@link #CONSTANT_FIELDREF_TAG}, {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG}, {@link + * #CONSTANT_NAME_AND_TYPE_TAG}, {@link #CONSTANT_METHOD_HANDLE_TAG}, {@link + * #CONSTANT_METHOD_TYPE_TAG}, {@link #CONSTANT_DYNAMIC_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • an arbitrary string for {@link #CONSTANT_UTF8_TAG} and {@link #CONSTANT_STRING_TAG} + * symbols, + *
  • an internal class name for {@link #CONSTANT_CLASS_TAG}, {@link #TYPE_TAG}, {@link + * #UNINITIALIZED_TYPE_TAG} and {@link #FORWARD_UNINITIALIZED_TYPE_TAG} symbols, + *
  • {@literal null} for the other types of symbol. + *
+ */ + final String value; + + /** + * The numeric value of this symbol. This is: + * + *
    + *
  • the symbol's value for {@link #CONSTANT_INTEGER_TAG},{@link #CONSTANT_FLOAT_TAG}, {@link + * #CONSTANT_LONG_TAG}, {@link #CONSTANT_DOUBLE_TAG}, + *
  • the CONSTANT_MethodHandle_info reference_kind field value for {@link + * #CONSTANT_METHOD_HANDLE_TAG} symbols, + *
  • the CONSTANT_InvokeDynamic_info bootstrap_method_attr_index field value for {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • the offset of a bootstrap method in the BootstrapMethods boostrap_methods array, for + * {@link #CONSTANT_DYNAMIC_TAG} or {@link #BOOTSTRAP_METHOD_TAG} symbols, + *
  • the bytecode offset of the NEW instruction that created an {@link + * Frame#ITEM_UNINITIALIZED} type for {@link #UNINITIALIZED_TYPE_TAG} symbols, + *
  • the index of the {@link Label} (in the {@link SymbolTable#labelTable} table) of the NEW + * instruction that created an {@link Frame#ITEM_UNINITIALIZED} type for {@link + * #FORWARD_UNINITIALIZED_TYPE_TAG} symbols, + *
  • the indices (in the class' type table) of two {@link #TYPE_TAG} source types for {@link + * #MERGED_TYPE_TAG} symbols, + *
  • 0 for the other types of symbol. + *
+ */ + final long data; + + /** + * Additional information about this symbol, generally computed lazily. Warning: the value of + * this field is ignored when comparing Symbol instances (to avoid duplicate entries in a + * SymbolTable). Therefore, this field should only contain data that can be computed from the + * other fields of this class. It contains: + * + *
    + *
  • the {@link Type#getArgumentsAndReturnSizes} of the symbol's method descriptor for {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols, + *
  • the index in the InnerClasses_attribute 'classes' array (plus one) corresponding to this + * class, for {@link #CONSTANT_CLASS_TAG} symbols, + *
  • the index (in the class' type table) of the merged type of the two source types for + * {@link #MERGED_TYPE_TAG} symbols, + *
  • 0 for the other types of symbol, or if this field has not been computed yet. + *
+ */ + int info; + + /** + * Constructs a new Symbol. This constructor can't be used directly because the Symbol class is + * abstract. Instead, use the factory methods of the {@link SymbolTable} class. + * + * @param index the symbol index in the constant pool, in the BootstrapMethods attribute, or in + * the (ASM specific) type table of a class (depending on 'tag'). + * @param tag the symbol type. Must be one of the static tag values defined in this class. + * @param owner The internal name of the symbol's owner class. Maybe {@literal null}. + * @param name The name of the symbol's corresponding class field or method. Maybe {@literal + * null}. + * @param value The string value of this symbol. Maybe {@literal null}. + * @param data The numeric value of this symbol. + */ + Symbol( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data) { + this.index = index; + this.tag = tag; + this.owner = owner; + this.name = name; + this.value = value; + this.data = data; + } + + /** + * Returns the result {@link Type#getArgumentsAndReturnSizes} on {@link #value}. + * + * @return the result {@link Type#getArgumentsAndReturnSizes} on {@link #value} (memoized in + * {@link #info} for efficiency). This should only be used for {@link + * #CONSTANT_METHODREF_TAG}, {@link #CONSTANT_INTERFACE_METHODREF_TAG} and {@link + * #CONSTANT_INVOKE_DYNAMIC_TAG} symbols. + */ + int getArgumentsAndReturnSizes() { + if (info == 0) { + info = Type.getArgumentsAndReturnSizes(value); + } + return info; + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable$Entry.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable$Entry.class new file mode 100644 index 00000000..2f321bb8 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable$Entry.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable$LabelEntry.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable$LabelEntry.class new file mode 100644 index 00000000..f3008898 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable$LabelEntry.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable.class new file mode 100644 index 00000000..1c9afebc Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable.java new file mode 100644 index 00000000..5fc629d6 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/SymbolTable.java @@ -0,0 +1,1495 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type + * table entries of a class. + * + * @author Eric Bruneton + * @see JVMS + * 4.4 + * @see JVMS + * 4.7.23 + */ +final class SymbolTable { + + /** + * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link + * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link + * Attribute#write}. + */ + final ClassWriter classWriter; + + /** + * The ClassReader from which this SymbolTable was constructed, or {@literal null} if it was + * constructed from scratch. + */ + private final ClassReader sourceClassReader; + + /** The major version number of the class to which this symbol table belongs. */ + private int majorVersion; + + /** The internal name of the class to which this symbol table belongs. */ + private String className; + + /** + * The total number of {@link Entry} instances in {@link #entries}. This includes entries that are + * accessible (recursively) via {@link Entry#next}. + */ + private int entryCount; + + /** + * A hash set of all the entries in this SymbolTable (this includes the constant pool entries, the + * bootstrap method entries and the type table entries). Each {@link Entry} instance is stored at + * the array index given by its hash code modulo the array size. If several entries must be stored + * at the same array index, they are linked together via their {@link Entry#next} field. The + * factory methods of this class make sure that this table does not contain duplicated entries. + */ + private Entry[] entries; + + /** + * The number of constant pool items in {@link #constantPool}, plus 1. The first constant pool + * item has index 1, and long and double items count for two items. + */ + private int constantPoolCount; + + /** + * The content of the ClassFile's constant_pool JVMS structure corresponding to this SymbolTable. + * The ClassFile's constant_pool_count field is not included. + */ + private ByteVector constantPool; + + /** + * The number of bootstrap methods in {@link #bootstrapMethods}. Corresponds to the + * BootstrapMethods_attribute's num_bootstrap_methods field value. + */ + private int bootstrapMethodCount; + + /** + * The content of the BootstrapMethods attribute 'bootstrap_methods' array corresponding to this + * SymbolTable. Note that the first 6 bytes of the BootstrapMethods_attribute, and its + * num_bootstrap_methods field, are not included. + */ + private ByteVector bootstrapMethods; + + /** + * The actual number of elements in {@link #typeTable}. These elements are stored from index 0 to + * typeCount (excluded). The other array entries are empty. + */ + private int typeCount; + + /** + * An ASM specific type table used to temporarily store internal names that will not necessarily + * be stored in the constant pool. This type table is used by the control flow and data flow + * analysis algorithm used to compute stack map frames from scratch. This array stores {@link + * Symbol#TYPE_TAG}, {@link Symbol#UNINITIALIZED_TYPE_TAG},{@link + * Symbol#FORWARD_UNINITIALIZED_TYPE_TAG} and {@link Symbol#MERGED_TYPE_TAG} entries. The type + * symbol at index {@code i} has its {@link Symbol#index} equal to {@code i} (and vice versa). + */ + private Entry[] typeTable; + + /** + * The actual number of {@link LabelEntry} in {@link #labelTable}. These elements are stored from + * index 0 to labelCount (excluded). The other array entries are empty. These label entries are + * also stored in the {@link #labelEntries} hash set. + */ + private int labelCount; + + /** + * The labels corresponding to the "forward uninitialized" types in the ASM specific {@link + * typeTable} (see {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}). The label entry at index {@code + * i} has its {@link LabelEntry#index} equal to {@code i} (and vice versa). + */ + private LabelEntry[] labelTable; + + /** + * A hash set of all the {@link LabelEntry} elements in the {@link #labelTable}. Each {@link + * LabelEntry} instance is stored at the array index given by its hash code modulo the array size. + * If several entries must be stored at the same array index, they are linked together via their + * {@link LabelEntry#next} field. The {@link #getOrAddLabelEntry(Label)} method ensures that this + * table does not contain duplicated entries. + */ + private LabelEntry[] labelEntries; + + /** + * Constructs a new, empty SymbolTable for the given ClassWriter. + * + * @param classWriter a ClassWriter. + */ + SymbolTable(final ClassWriter classWriter) { + this.classWriter = classWriter; + this.sourceClassReader = null; + this.entries = new Entry[256]; + this.constantPoolCount = 1; + this.constantPool = new ByteVector(); + } + + /** + * Constructs a new SymbolTable for the given ClassWriter, initialized with the constant pool and + * bootstrap methods of the given ClassReader. + * + * @param classWriter a ClassWriter. + * @param classReader the ClassReader whose constant pool and bootstrap methods must be copied to + * initialize the SymbolTable. + */ + SymbolTable(final ClassWriter classWriter, final ClassReader classReader) { + this.classWriter = classWriter; + this.sourceClassReader = classReader; + + // Copy the constant pool binary content. + byte[] inputBytes = classReader.classFileBuffer; + int constantPoolOffset = classReader.getItem(1) - 1; + int constantPoolLength = classReader.header - constantPoolOffset; + constantPoolCount = classReader.getItemCount(); + constantPool = new ByteVector(constantPoolLength); + constantPool.putByteArray(inputBytes, constantPoolOffset, constantPoolLength); + + // Add the constant pool items in the symbol table entries. Reserve enough space in 'entries' to + // avoid too many hash set collisions (entries is not dynamically resized by the addConstant* + // method calls below), and to account for bootstrap method entries. + entries = new Entry[constantPoolCount * 2]; + char[] charBuffer = new char[classReader.getMaxStringLength()]; + boolean hasBootstrapMethods = false; + int itemIndex = 1; + while (itemIndex < constantPoolCount) { + int itemOffset = classReader.getItem(itemIndex); + int itemTag = inputBytes[itemOffset - 1]; + int nameAndTypeItemOffset; + switch (itemTag) { + case Symbol.CONSTANT_FIELDREF_TAG: + case Symbol.CONSTANT_METHODREF_TAG: + case Symbol.CONSTANT_INTERFACE_METHODREF_TAG: + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); + addConstantMemberReference( + itemIndex, + itemTag, + classReader.readClass(itemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_INTEGER_TAG: + case Symbol.CONSTANT_FLOAT_TAG: + addConstantIntegerOrFloat(itemIndex, itemTag, classReader.readInt(itemOffset)); + break; + case Symbol.CONSTANT_NAME_AND_TYPE_TAG: + addConstantNameAndType( + itemIndex, + classReader.readUTF8(itemOffset, charBuffer), + classReader.readUTF8(itemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_LONG_TAG: + case Symbol.CONSTANT_DOUBLE_TAG: + addConstantLongOrDouble(itemIndex, itemTag, classReader.readLong(itemOffset)); + break; + case Symbol.CONSTANT_UTF8_TAG: + addConstantUtf8(itemIndex, classReader.readUtf(itemIndex, charBuffer)); + break; + case Symbol.CONSTANT_METHOD_HANDLE_TAG: + int memberRefItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 1)); + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2)); + addConstantMethodHandle( + itemIndex, + classReader.readByte(itemOffset), + classReader.readClass(memberRefItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer)); + break; + case Symbol.CONSTANT_DYNAMIC_TAG: + case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG: + hasBootstrapMethods = true; + nameAndTypeItemOffset = + classReader.getItem(classReader.readUnsignedShort(itemOffset + 2)); + addConstantDynamicOrInvokeDynamicReference( + itemTag, + itemIndex, + classReader.readUTF8(nameAndTypeItemOffset, charBuffer), + classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer), + classReader.readUnsignedShort(itemOffset)); + break; + case Symbol.CONSTANT_STRING_TAG: + case Symbol.CONSTANT_CLASS_TAG: + case Symbol.CONSTANT_METHOD_TYPE_TAG: + case Symbol.CONSTANT_MODULE_TAG: + case Symbol.CONSTANT_PACKAGE_TAG: + addConstantUtf8Reference( + itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer)); + break; + default: + throw new IllegalArgumentException(); + } + itemIndex += + (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1; + } + + // Copy the BootstrapMethods, if any. + if (hasBootstrapMethods) { + copyBootstrapMethods(classReader, charBuffer); + } + } + + /** + * Read the BootstrapMethods 'bootstrap_methods' array binary content and add them as entries of + * the SymbolTable. + * + * @param classReader the ClassReader whose bootstrap methods must be copied to initialize the + * SymbolTable. + * @param charBuffer a buffer used to read strings in the constant pool. + */ + private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) { + // Find attributOffset of the 'bootstrap_methods' array. + byte[] inputBytes = classReader.classFileBuffer; + int currentAttributeOffset = classReader.getFirstAttributeOffset(); + for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) { + String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer); + if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) { + bootstrapMethodCount = classReader.readUnsignedShort(currentAttributeOffset + 6); + break; + } + currentAttributeOffset += 6 + classReader.readInt(currentAttributeOffset + 2); + } + if (bootstrapMethodCount > 0) { + // Compute the offset and the length of the BootstrapMethods 'bootstrap_methods' array. + int bootstrapMethodsOffset = currentAttributeOffset + 8; + int bootstrapMethodsLength = classReader.readInt(currentAttributeOffset + 2) - 2; + bootstrapMethods = new ByteVector(bootstrapMethodsLength); + bootstrapMethods.putByteArray(inputBytes, bootstrapMethodsOffset, bootstrapMethodsLength); + + // Add each bootstrap method in the symbol table entries. + int currentOffset = bootstrapMethodsOffset; + for (int i = 0; i < bootstrapMethodCount; i++) { + int offset = currentOffset - bootstrapMethodsOffset; + int bootstrapMethodRef = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + int numBootstrapArguments = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + int hashCode = classReader.readConst(bootstrapMethodRef, charBuffer).hashCode(); + while (numBootstrapArguments-- > 0) { + int bootstrapArgument = classReader.readUnsignedShort(currentOffset); + currentOffset += 2; + hashCode ^= classReader.readConst(bootstrapArgument, charBuffer).hashCode(); + } + add(new Entry(i, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode & 0x7FFFFFFF)); + } + } + } + + /** + * Returns the ClassReader from which this SymbolTable was constructed. + * + * @return the ClassReader from which this SymbolTable was constructed, or {@literal null} if it + * was constructed from scratch. + */ + ClassReader getSource() { + return sourceClassReader; + } + + /** + * Returns the major version of the class to which this symbol table belongs. + * + * @return the major version of the class to which this symbol table belongs. + */ + int getMajorVersion() { + return majorVersion; + } + + /** + * Returns the internal name of the class to which this symbol table belongs. + * + * @return the internal name of the class to which this symbol table belongs. + */ + String getClassName() { + return className; + } + + /** + * Sets the major version and the name of the class to which this symbol table belongs. Also adds + * the class name to the constant pool. + * + * @param majorVersion a major ClassFile version number. + * @param className an internal class name. + * @return the constant pool index of a new or already existing Symbol with the given class name. + */ + int setMajorVersionAndClassName(final int majorVersion, final String className) { + this.majorVersion = majorVersion; + this.className = className; + return addConstantClass(className).index; + } + + /** + * Returns the number of items in this symbol table's constant_pool array (plus 1). + * + * @return the number of items in this symbol table's constant_pool array (plus 1). + */ + int getConstantPoolCount() { + return constantPoolCount; + } + + /** + * Returns the length in bytes of this symbol table's constant_pool array. + * + * @return the length in bytes of this symbol table's constant_pool array. + */ + int getConstantPoolLength() { + return constantPool.length; + } + + /** + * Puts this symbol table's constant_pool array in the given ByteVector, preceded by the + * constant_pool_count value. + * + * @param output where the JVMS ClassFile's constant_pool array must be put. + */ + void putConstantPool(final ByteVector output) { + output.putShort(constantPoolCount).putByteArray(constantPool.data, 0, constantPool.length); + } + + /** + * Returns the size in bytes of this symbol table's BootstrapMethods attribute. Also adds the + * attribute name in the constant pool. + * + * @return the size in bytes of this symbol table's BootstrapMethods attribute. + */ + int computeBootstrapMethodsSize() { + if (bootstrapMethods != null) { + addConstantUtf8(Constants.BOOTSTRAP_METHODS); + return 8 + bootstrapMethods.length; + } else { + return 0; + } + } + + /** + * Puts this symbol table's BootstrapMethods attribute in the given ByteVector. This includes the + * 6 attribute header bytes and the num_bootstrap_methods value. + * + * @param output where the JVMS BootstrapMethods attribute must be put. + */ + void putBootstrapMethods(final ByteVector output) { + if (bootstrapMethods != null) { + output + .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS)) + .putInt(bootstrapMethods.length + 2) + .putShort(bootstrapMethodCount) + .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length); + } + } + + // ----------------------------------------------------------------------------------------------- + // Generic symbol table entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the list of entries which can potentially have the given hash code. + * + * @param hashCode a {@link Entry#hashCode} value. + * @return the list of entries which can potentially have the given hash code. The list is stored + * via the {@link Entry#next} field. + */ + private Entry get(final int hashCode) { + return entries[hashCode % entries.length]; + } + + /** + * Puts the given entry in the {@link #entries} hash set. This method does not check + * whether {@link #entries} already contains a similar entry or not. {@link #entries} is resized + * if necessary to avoid hash collisions (multiple entries needing to be stored at the same {@link + * #entries} array index) as much as possible, with reasonable memory usage. + * + * @param entry an Entry (which must not already be contained in {@link #entries}). + * @return the given entry + */ + private Entry put(final Entry entry) { + if (entryCount > (entries.length * 3) / 4) { + int currentCapacity = entries.length; + int newCapacity = currentCapacity * 2 + 1; + Entry[] newEntries = new Entry[newCapacity]; + for (int i = currentCapacity - 1; i >= 0; --i) { + Entry currentEntry = entries[i]; + while (currentEntry != null) { + int newCurrentEntryIndex = currentEntry.hashCode % newCapacity; + Entry nextEntry = currentEntry.next; + currentEntry.next = newEntries[newCurrentEntryIndex]; + newEntries[newCurrentEntryIndex] = currentEntry; + currentEntry = nextEntry; + } + } + entries = newEntries; + } + entryCount++; + int index = entry.hashCode % entries.length; + entry.next = entries[index]; + return entries[index] = entry; + } + + /** + * Adds the given entry in the {@link #entries} hash set. This method does not check + * whether {@link #entries} already contains a similar entry or not, and does not resize + * {@link #entries} if necessary. + * + * @param entry an Entry (which must not already be contained in {@link #entries}). + */ + private void add(final Entry entry) { + entryCount++; + int index = entry.hashCode % entries.length; + entry.next = entries[index]; + entries[index] = entry; + } + + // ----------------------------------------------------------------------------------------------- + // Constant pool entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a number or string constant to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value the value of the constant to be added to the constant pool. This parameter must be + * an {@link Integer}, {@link Byte}, {@link Character}, {@link Short}, {@link Boolean}, {@link + * Float}, {@link Long}, {@link Double}, {@link String}, {@link Type} or {@link Handle}. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstant(final Object value) { + if (value instanceof Integer) { + return addConstantInteger(((Integer) value).intValue()); + } else if (value instanceof Byte) { + return addConstantInteger(((Byte) value).intValue()); + } else if (value instanceof Character) { + return addConstantInteger(((Character) value).charValue()); + } else if (value instanceof Short) { + return addConstantInteger(((Short) value).intValue()); + } else if (value instanceof Boolean) { + return addConstantInteger(((Boolean) value).booleanValue() ? 1 : 0); + } else if (value instanceof Float) { + return addConstantFloat(((Float) value).floatValue()); + } else if (value instanceof Long) { + return addConstantLong(((Long) value).longValue()); + } else if (value instanceof Double) { + return addConstantDouble(((Double) value).doubleValue()); + } else if (value instanceof String) { + return addConstantString((String) value); + } else if (value instanceof Type) { + Type type = (Type) value; + int typeSort = type.getSort(); + if (typeSort == Type.OBJECT) { + return addConstantClass(type.getInternalName()); + } else if (typeSort == Type.METHOD) { + return addConstantMethodType(type.getDescriptor()); + } else { // type is a primitive or array type. + return addConstantClass(type.getDescriptor()); + } + } else if (value instanceof Handle) { + Handle handle = (Handle) value; + return addConstantMethodHandle( + handle.getTag(), + handle.getOwner(), + handle.getName(), + handle.getDesc(), + handle.isInterface()); + } else if (value instanceof ConstantDynamic) { + ConstantDynamic constantDynamic = (ConstantDynamic) value; + return addConstantDynamic( + constantDynamic.getName(), + constantDynamic.getDescriptor(), + constantDynamic.getBootstrapMethod(), + constantDynamic.getBootstrapMethodArgumentsUnsafe()); + } else { + throw new IllegalArgumentException("value " + value); + } + } + + /** + * Adds a CONSTANT_Class_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value the internal name of a class. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantClass(final String value) { + return addConstantUtf8Reference(Symbol.CONSTANT_CLASS_TAG, value); + } + + /** + * Adds a CONSTANT_Fieldref_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param owner the internal name of a class. + * @param name a field name. + * @param descriptor a field descriptor. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantFieldref(final String owner, final String name, final String descriptor) { + return addConstantMemberReference(Symbol.CONSTANT_FIELDREF_TAG, owner, name, descriptor); + } + + /** + * Adds a CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to the constant pool of this + * symbol table. Does nothing if the constant pool already contains a similar item. + * + * @param owner the internal name of a class. + * @param name a method name. + * @param descriptor a method descriptor. + * @param isInterface whether owner is an interface or not. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodref( + final String owner, final String name, final String descriptor, final boolean isInterface) { + int tag = isInterface ? Symbol.CONSTANT_INTERFACE_METHODREF_TAG : Symbol.CONSTANT_METHODREF_TAG; + return addConstantMemberReference(tag, owner, name, descriptor); + } + + /** + * Adds a CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to + * the constant pool of this symbol table. Does nothing if the constant pool already contains a + * similar item. + * + * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG} + * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}. + * @param owner the internal name of a class. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @return a new or already existing Symbol with the given value. + */ + private Entry addConstantMemberReference( + final int tag, final String owner, final String name, final String descriptor) { + int hashCode = hash(tag, owner, name, descriptor); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.owner.equals(owner) + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + constantPool.put122( + tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor)); + return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode)); + } + + /** + * Adds a new CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info + * to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG} + * or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}. + * @param owner the internal name of a class. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantMemberReference( + final int index, + final int tag, + final String owner, + final String name, + final String descriptor) { + add(new Entry(index, tag, owner, name, descriptor, 0, hash(tag, owner, name, descriptor))); + } + + /** + * Adds a CONSTANT_String_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a string. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantString(final String value) { + return addConstantUtf8Reference(Symbol.CONSTANT_STRING_TAG, value); + } + + /** + * Adds a CONSTANT_Integer_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value an int. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantInteger(final int value) { + return addConstantIntegerOrFloat(Symbol.CONSTANT_INTEGER_TAG, value); + } + + /** + * Adds a CONSTANT_Float_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a float. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantFloat(final float value) { + return addConstantIntegerOrFloat(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value)); + } + + /** + * Adds a CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol table. + * Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. + * @param value an int or float. + * @return a constant pool constant with the given tag and primitive values. + */ + private Symbol addConstantIntegerOrFloat(final int tag, final int value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) { + return entry; + } + entry = entry.next; + } + constantPool.putByte(tag).putInt(value); + return put(new Entry(constantPoolCount++, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol + * table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}. + * @param value an int or float. + */ + private void addConstantIntegerOrFloat(final int index, final int tag, final int value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + /** + * Adds a CONSTANT_Long_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a long. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantLong(final long value) { + return addConstantLongOrDouble(Symbol.CONSTANT_LONG_TAG, value); + } + + /** + * Adds a CONSTANT_Double_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a double. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantDouble(final double value) { + return addConstantLongOrDouble(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value)); + } + + /** + * Adds a CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol table. + * Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. + * @param value a long or double. + * @return a constant pool constant with the given tag and primitive values. + */ + private Symbol addConstantLongOrDouble(final int tag, final long value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) { + return entry; + } + entry = entry.next; + } + int index = constantPoolCount; + constantPool.putByte(tag).putLong(value); + constantPoolCount += 2; + return put(new Entry(index, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol + * table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}. + * @param value a long or double. + */ + private void addConstantLongOrDouble(final int index, final int tag, final long value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + /** + * Adds a CONSTANT_NameAndType_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @return a new or already existing Symbol with the given value. + */ + int addConstantNameAndType(final String name, final String descriptor) { + final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG; + int hashCode = hash(tag, name, descriptor); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry.index; + } + entry = entry.next; + } + constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor)); + return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index; + } + + /** + * Adds a new CONSTANT_NameAndType_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantNameAndType(final int index, final String name, final String descriptor) { + final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG; + add(new Entry(index, tag, name, descriptor, hash(tag, name, descriptor))); + } + + /** + * Adds a CONSTANT_Utf8_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param value a string. + * @return a new or already existing Symbol with the given value. + */ + int addConstantUtf8(final String value) { + int hashCode = hash(Symbol.CONSTANT_UTF8_TAG, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.CONSTANT_UTF8_TAG + && entry.hashCode == hashCode + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + constantPool.putByte(Symbol.CONSTANT_UTF8_TAG).putUTF8(value); + return put(new Entry(constantPoolCount++, Symbol.CONSTANT_UTF8_TAG, value, hashCode)).index; + } + + /** + * Adds a new CONSTANT_String_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param value a string. + */ + private void addConstantUtf8(final int index, final String value) { + add(new Entry(index, Symbol.CONSTANT_UTF8_TAG, value, hash(Symbol.CONSTANT_UTF8_TAG, value))); + } + + /** + * Adds a CONSTANT_MethodHandle_info to the constant pool of this symbol table. Does nothing if + * the constant pool already contains a similar item. + * + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of a class of interface. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + * @param isInterface whether owner is an interface or not. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodHandle( + final int referenceKind, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + // Note that we don't need to include isInterface in the hash computation, because it is + // redundant with owner (we can't have the same owner with different isInterface values). + int hashCode = hash(tag, owner, name, descriptor, referenceKind); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.data == referenceKind + && entry.owner.equals(owner) + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + if (referenceKind <= Opcodes.H_PUTSTATIC) { + constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index); + } else { + constantPool.put112( + tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index); + } + return put( + new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode)); + } + + /** + * Adds a new CONSTANT_MethodHandle_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link + * Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link + * Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link + * Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}. + * @param owner the internal name of a class of interface. + * @param name a field or method name. + * @param descriptor a field or method descriptor. + */ + private void addConstantMethodHandle( + final int index, + final int referenceKind, + final String owner, + final String name, + final String descriptor) { + final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG; + int hashCode = hash(tag, owner, name, descriptor, referenceKind); + add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode)); + } + + /** + * Adds a CONSTANT_MethodType_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param methodDescriptor a method descriptor. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantMethodType(final String methodDescriptor) { + return addConstantUtf8Reference(Symbol.CONSTANT_METHOD_TYPE_TAG, methodDescriptor); + } + + /** + * Adds a CONSTANT_Dynamic_info to the constant pool of this symbol table. Also adds the related + * bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the constant + * pool already contains a similar item. + * + * @param name a method name. + * @param descriptor a field descriptor. + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments); + return addConstantDynamicOrInvokeDynamicReference( + Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index); + } + + /** + * Adds a CONSTANT_InvokeDynamic_info to the constant pool of this symbol table. Also adds the + * related bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param name a method name. + * @param descriptor a method descriptor. + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantInvokeDynamic( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments); + return addConstantDynamicOrInvokeDynamicReference( + Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index); + } + + /** + * Adds a CONSTANT_Dynamic or a CONSTANT_InvokeDynamic_info to the constant pool of this symbol + * table. Does nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link + * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}. + * @param name a method name. + * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG) or a method descriptor for + * CONSTANT_INVOKE_DYNAMIC_TAG. + * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addConstantDynamicOrInvokeDynamicReference( + final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag + && entry.hashCode == hashCode + && entry.data == bootstrapMethodIndex + && entry.name.equals(name) + && entry.value.equals(descriptor)) { + return entry; + } + entry = entry.next; + } + constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor)); + return put( + new Entry( + constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode)); + } + + /** + * Adds a new CONSTANT_Dynamic_info or CONSTANT_InvokeDynamic_info to the constant pool of this + * symbol table. + * + * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link + * Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}. + * @param index the constant pool index of the new Symbol. + * @param name a method name. + * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG or a method descriptor for + * CONSTANT_INVOKE_DYNAMIC_TAG. + * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute. + */ + private void addConstantDynamicOrInvokeDynamicReference( + final int tag, + final int index, + final String name, + final String descriptor, + final int bootstrapMethodIndex) { + int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex); + add(new Entry(index, tag, null, name, descriptor, bootstrapMethodIndex, hashCode)); + } + + /** + * Adds a CONSTANT_Module_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param moduleName a fully qualified name (using dots) of a module. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantModule(final String moduleName) { + return addConstantUtf8Reference(Symbol.CONSTANT_MODULE_TAG, moduleName); + } + + /** + * Adds a CONSTANT_Package_info to the constant pool of this symbol table. Does nothing if the + * constant pool already contains a similar item. + * + * @param packageName the internal name of a package. + * @return a new or already existing Symbol with the given value. + */ + Symbol addConstantPackage(final String packageName) { + return addConstantUtf8Reference(Symbol.CONSTANT_PACKAGE_TAG, packageName); + } + + /** + * Adds a CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info, + * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. Does + * nothing if the constant pool already contains a similar item. + * + * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link + * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link + * Symbol#CONSTANT_PACKAGE_TAG}. + * @param value an internal class name, an arbitrary string, a method descriptor, a module or a + * package name, depending on tag. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addConstantUtf8Reference(final int tag, final String value) { + int hashCode = hash(tag, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == tag && entry.hashCode == hashCode && entry.value.equals(value)) { + return entry; + } + entry = entry.next; + } + constantPool.put12(tag, addConstantUtf8(value)); + return put(new Entry(constantPoolCount++, tag, value, hashCode)); + } + + /** + * Adds a new CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info, + * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. + * + * @param index the constant pool index of the new Symbol. + * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link + * Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link + * Symbol#CONSTANT_PACKAGE_TAG}. + * @param value an internal class name, an arbitrary string, a method descriptor, a module or a + * package name, depending on tag. + */ + private void addConstantUtf8Reference(final int index, final int tag, final String value) { + add(new Entry(index, tag, value, hash(tag, value))); + } + + // ----------------------------------------------------------------------------------------------- + // Bootstrap method entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if + * the BootstrapMethods already contains a similar bootstrap method. + * + * @param bootstrapMethodHandle a bootstrap method handle. + * @param bootstrapMethodArguments the bootstrap method arguments. + * @return a new or already existing Symbol with the given value. + */ + Symbol addBootstrapMethod( + final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) { + ByteVector bootstrapMethodsAttribute = bootstrapMethods; + if (bootstrapMethodsAttribute == null) { + bootstrapMethodsAttribute = bootstrapMethods = new ByteVector(); + } + + // The bootstrap method arguments can be Constant_Dynamic values, which reference other + // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool + // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified + // while adding the given bootstrap method to it, in the rest of this method. + int numBootstrapArguments = bootstrapMethodArguments.length; + int[] bootstrapMethodArgumentIndexes = new int[numBootstrapArguments]; + for (int i = 0; i < numBootstrapArguments; i++) { + bootstrapMethodArgumentIndexes[i] = addConstant(bootstrapMethodArguments[i]).index; + } + + // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to + // compare it with existing ones, and will be reverted below if there is already a similar + // bootstrap method. + int bootstrapMethodOffset = bootstrapMethodsAttribute.length; + bootstrapMethodsAttribute.putShort( + addConstantMethodHandle( + bootstrapMethodHandle.getTag(), + bootstrapMethodHandle.getOwner(), + bootstrapMethodHandle.getName(), + bootstrapMethodHandle.getDesc(), + bootstrapMethodHandle.isInterface()) + .index); + + bootstrapMethodsAttribute.putShort(numBootstrapArguments); + for (int i = 0; i < numBootstrapArguments; i++) { + bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]); + } + + // Compute the length and the hash code of the bootstrap method. + int bootstrapMethodlength = bootstrapMethodsAttribute.length - bootstrapMethodOffset; + int hashCode = bootstrapMethodHandle.hashCode(); + for (Object bootstrapMethodArgument : bootstrapMethodArguments) { + hashCode ^= bootstrapMethodArgument.hashCode(); + } + hashCode &= 0x7FFFFFFF; + + // Add the bootstrap method to the symbol table or revert the above changes. + return addBootstrapMethod(bootstrapMethodOffset, bootstrapMethodlength, hashCode); + } + + /** + * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if + * the BootstrapMethods already contains a similar bootstrap method (more precisely, reverts the + * content of {@link #bootstrapMethods} to remove the last, duplicate bootstrap method). + * + * @param offset the offset of the last bootstrap method in {@link #bootstrapMethods}, in bytes. + * @param length the length of this bootstrap method in {@link #bootstrapMethods}, in bytes. + * @param hashCode the hash code of this bootstrap method. + * @return a new or already existing Symbol with the given value. + */ + private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) { + final byte[] bootstrapMethodsData = bootstrapMethods.data; + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) { + int otherOffset = (int) entry.data; + boolean isSameBootstrapMethod = true; + for (int i = 0; i < length; ++i) { + if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) { + isSameBootstrapMethod = false; + break; + } + } + if (isSameBootstrapMethod) { + bootstrapMethods.length = offset; // Revert to old position. + return entry; + } + } + entry = entry.next; + } + return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode)); + } + + // ----------------------------------------------------------------------------------------------- + // Type table entries management. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the type table element whose index is given. + * + * @param typeIndex a type table index. + * @return the type table element whose index is given. + */ + Symbol getType(final int typeIndex) { + return typeTable[typeIndex]; + } + + /** + * Returns the label corresponding to the "forward uninitialized" type table element whose index + * is given. + * + * @param typeIndex the type table index of a "forward uninitialized" type table element. + * @return the label corresponding of the NEW instruction which created this "forward + * uninitialized" type. + */ + Label getForwardUninitializedLabel(final int typeIndex) { + return labelTable[(int) typeTable[typeIndex].data].label; + } + + /** + * Adds a type in the type table of this symbol table. Does nothing if the type table already + * contains a similar type. + * + * @param value an internal class name. + * @return the index of a new or already existing type Symbol with the given value. + */ + int addType(final String value) { + int hashCode = hash(Symbol.TYPE_TAG, value); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.TYPE_TAG && entry.hashCode == hashCode && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addTypeInternal(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode)); + } + + /** + * Adds an uninitialized type in the type table of this symbol table. Does nothing if the type + * table already contains a similar type. + * + * @param value an internal class name. + * @param bytecodeOffset the bytecode offset of the NEW instruction that created this + * uninitialized type value. + * @return the index of a new or already existing type #@link Symbol} with the given value. + */ + int addUninitializedType(final String value, final int bytecodeOffset) { + int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.UNINITIALIZED_TYPE_TAG + && entry.hashCode == hashCode + && entry.data == bytecodeOffset + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addTypeInternal( + new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode)); + } + + /** + * Adds a "forward uninitialized" type in the type table of this symbol table. Does nothing if the + * type table already contains a similar type. + * + * @param value an internal class name. + * @param label the label of the NEW instruction that created this uninitialized type value. If + * the label is resolved, use the {@link #addUninitializedType} method instead. + * @return the index of a new or already existing type {@link Symbol} with the given value. + */ + int addForwardUninitializedType(final String value, final Label label) { + int labelIndex = getOrAddLabelEntry(label).index; + int hashCode = hash(Symbol.FORWARD_UNINITIALIZED_TYPE_TAG, value, labelIndex); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.FORWARD_UNINITIALIZED_TYPE_TAG + && entry.hashCode == hashCode + && entry.data == labelIndex + && entry.value.equals(value)) { + return entry.index; + } + entry = entry.next; + } + return addTypeInternal( + new Entry(typeCount, Symbol.FORWARD_UNINITIALIZED_TYPE_TAG, value, labelIndex, hashCode)); + } + + /** + * Adds a merged type in the type table of this symbol table. Does nothing if the type table + * already contains a similar type. + * + * @param typeTableIndex1 a {@link Symbol#TYPE_TAG} type, specified by its index in the type + * table. + * @param typeTableIndex2 another {@link Symbol#TYPE_TAG} type, specified by its index in the type + * table. + * @return the index of a new or already existing {@link Symbol#TYPE_TAG} type Symbol, + * corresponding to the common super class of the given types. + */ + int addMergedType(final int typeTableIndex1, final int typeTableIndex2) { + long data = + typeTableIndex1 < typeTableIndex2 + ? typeTableIndex1 | (((long) typeTableIndex2) << 32) + : typeTableIndex2 | (((long) typeTableIndex1) << 32); + int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2); + Entry entry = get(hashCode); + while (entry != null) { + if (entry.tag == Symbol.MERGED_TYPE_TAG && entry.hashCode == hashCode && entry.data == data) { + return entry.info; + } + entry = entry.next; + } + String type1 = typeTable[typeTableIndex1].value; + String type2 = typeTable[typeTableIndex2].value; + int commonSuperTypeIndex = addType(classWriter.getCommonSuperClass(type1, type2)); + put(new Entry(typeCount, Symbol.MERGED_TYPE_TAG, data, hashCode)).info = commonSuperTypeIndex; + return commonSuperTypeIndex; + } + + /** + * Adds the given type Symbol to {@link #typeTable}. + * + * @param entry a {@link Symbol#TYPE_TAG} or {@link Symbol#UNINITIALIZED_TYPE_TAG} type symbol. + * The index of this Symbol must be equal to the current value of {@link #typeCount}. + * @return the index in {@link #typeTable} where the given type was added, which is also equal to + * entry's index by hypothesis. + */ + private int addTypeInternal(final Entry entry) { + if (typeTable == null) { + typeTable = new Entry[16]; + } + if (typeCount == typeTable.length) { + Entry[] newTypeTable = new Entry[2 * typeTable.length]; + System.arraycopy(typeTable, 0, newTypeTable, 0, typeTable.length); + typeTable = newTypeTable; + } + typeTable[typeCount++] = entry; + return put(entry).index; + } + + /** + * Returns the {@link LabelEntry} corresponding to the given label. Creates a new one if there is + * no such entry. + * + * @param label the {@link Label} of a NEW instruction which created an uninitialized type, in the + * case where this NEW instruction is after the <init> constructor call (in bytecode + * offset order). See {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}. + * @return the {@link LabelEntry} corresponding to {@code label}. + */ + private LabelEntry getOrAddLabelEntry(final Label label) { + if (labelEntries == null) { + labelEntries = new LabelEntry[16]; + labelTable = new LabelEntry[16]; + } + int hashCode = System.identityHashCode(label); + LabelEntry labelEntry = labelEntries[hashCode % labelEntries.length]; + while (labelEntry != null && labelEntry.label != label) { + labelEntry = labelEntry.next; + } + if (labelEntry != null) { + return labelEntry; + } + + if (labelCount > (labelEntries.length * 3) / 4) { + int currentCapacity = labelEntries.length; + int newCapacity = currentCapacity * 2 + 1; + LabelEntry[] newLabelEntries = new LabelEntry[newCapacity]; + for (int i = currentCapacity - 1; i >= 0; --i) { + LabelEntry currentEntry = labelEntries[i]; + while (currentEntry != null) { + int newCurrentEntryIndex = System.identityHashCode(currentEntry.label) % newCapacity; + LabelEntry nextEntry = currentEntry.next; + currentEntry.next = newLabelEntries[newCurrentEntryIndex]; + newLabelEntries[newCurrentEntryIndex] = currentEntry; + currentEntry = nextEntry; + } + } + labelEntries = newLabelEntries; + } + if (labelCount == labelTable.length) { + LabelEntry[] newLabelTable = new LabelEntry[2 * labelTable.length]; + System.arraycopy(labelTable, 0, newLabelTable, 0, labelTable.length); + labelTable = newLabelTable; + } + + labelEntry = new LabelEntry(labelCount, label); + int index = hashCode % labelEntries.length; + labelEntry.next = labelEntries[index]; + labelEntries[index] = labelEntry; + labelTable[labelCount++] = labelEntry; + return labelEntry; + } + + // ----------------------------------------------------------------------------------------------- + // Static helper methods to compute hash codes. + // ----------------------------------------------------------------------------------------------- + + private static int hash(final int tag, final int value) { + return 0x7FFFFFFF & (tag + value); + } + + private static int hash(final int tag, final long value) { + return 0x7FFFFFFF & (tag + (int) value + (int) (value >>> 32)); + } + + private static int hash(final int tag, final String value) { + return 0x7FFFFFFF & (tag + value.hashCode()); + } + + private static int hash(final int tag, final String value1, final int value2) { + return 0x7FFFFFFF & (tag + value1.hashCode() + value2); + } + + private static int hash(final int tag, final String value1, final String value2) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode()); + } + + private static int hash( + final int tag, final String value1, final String value2, final int value3) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * (value3 + 1)); + } + + private static int hash( + final int tag, final String value1, final String value2, final String value3) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode()); + } + + private static int hash( + final int tag, + final String value1, + final String value2, + final String value3, + final int value4) { + return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4); + } + + /** + * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields + * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid + * duplicate symbols). See {@link #entries}. + * + * @author Eric Bruneton + */ + private static final class Entry extends Symbol { + + /** The hash code of this entry. */ + final int hashCode; + + /** + * Another entry (and so on recursively) having the same hash code (modulo the size of {@link + * #entries}) as this one. + */ + Entry next; + + Entry( + final int index, + final int tag, + final String owner, + final String name, + final String value, + final long data, + final int hashCode) { + super(index, tag, owner, name, value, data); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final String value, final long data, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, value, data); + this.hashCode = hashCode; + } + + Entry( + final int index, final int tag, final String name, final String value, final int hashCode) { + super(index, tag, /* owner = */ null, name, value, /* data = */ 0); + this.hashCode = hashCode; + } + + Entry(final int index, final int tag, final long data, final int hashCode) { + super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data); + this.hashCode = hashCode; + } + } + + /** + * A label corresponding to a "forward uninitialized" type in the ASM specific {@link + * SymbolTable#typeTable} (see {@link Symbol#FORWARD_UNINITIALIZED_TYPE_TAG}). + * + * @author Eric Bruneton + */ + private static final class LabelEntry { + + /** The index of this label entry in the {@link SymbolTable#labelTable} array. */ + final int index; + + /** The value of this label entry. */ + final Label label; + + /** + * Another entry (and so on recursively) having the same hash code (modulo the size of {@link + * SymbolTable#labelEntries}}) as this one. + */ + LabelEntry next; + + LabelEntry(final int index, final Label label) { + this.index = index; + this.label = label; + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Type.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/Type.class new file mode 100644 index 00000000..cbc62d02 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/Type.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/Type.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/Type.java new file mode 100644 index 00000000..739b9444 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/Type.java @@ -0,0 +1,952 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * A Java field or method type. This class can be used to make it easier to manipulate type and + * method descriptors. + * + * @author Eric Bruneton + * @author Chris Nokleberg + */ +public final class Type { + + /** The sort of the {@code void} type. See {@link #getSort}. */ + public static final int VOID = 0; + + /** The sort of the {@code boolean} type. See {@link #getSort}. */ + public static final int BOOLEAN = 1; + + /** The sort of the {@code char} type. See {@link #getSort}. */ + public static final int CHAR = 2; + + /** The sort of the {@code byte} type. See {@link #getSort}. */ + public static final int BYTE = 3; + + /** The sort of the {@code short} type. See {@link #getSort}. */ + public static final int SHORT = 4; + + /** The sort of the {@code int} type. See {@link #getSort}. */ + public static final int INT = 5; + + /** The sort of the {@code float} type. See {@link #getSort}. */ + public static final int FLOAT = 6; + + /** The sort of the {@code long} type. See {@link #getSort}. */ + public static final int LONG = 7; + + /** The sort of the {@code double} type. See {@link #getSort}. */ + public static final int DOUBLE = 8; + + /** The sort of array reference types. See {@link #getSort}. */ + public static final int ARRAY = 9; + + /** The sort of object reference types. See {@link #getSort}. */ + public static final int OBJECT = 10; + + /** The sort of method types. See {@link #getSort}. */ + public static final int METHOD = 11; + + /** The (private) sort of object reference types represented with an internal name. */ + private static final int INTERNAL = 12; + + /** The descriptors of the primitive types. */ + private static final String PRIMITIVE_DESCRIPTORS = "VZCBSIFJD"; + + /** The {@code void} type. */ + public static final Type VOID_TYPE = new Type(VOID, PRIMITIVE_DESCRIPTORS, VOID, VOID + 1); + + /** The {@code boolean} type. */ + public static final Type BOOLEAN_TYPE = + new Type(BOOLEAN, PRIMITIVE_DESCRIPTORS, BOOLEAN, BOOLEAN + 1); + + /** The {@code char} type. */ + public static final Type CHAR_TYPE = new Type(CHAR, PRIMITIVE_DESCRIPTORS, CHAR, CHAR + 1); + + /** The {@code byte} type. */ + public static final Type BYTE_TYPE = new Type(BYTE, PRIMITIVE_DESCRIPTORS, BYTE, BYTE + 1); + + /** The {@code short} type. */ + public static final Type SHORT_TYPE = new Type(SHORT, PRIMITIVE_DESCRIPTORS, SHORT, SHORT + 1); + + /** The {@code int} type. */ + public static final Type INT_TYPE = new Type(INT, PRIMITIVE_DESCRIPTORS, INT, INT + 1); + + /** The {@code float} type. */ + public static final Type FLOAT_TYPE = new Type(FLOAT, PRIMITIVE_DESCRIPTORS, FLOAT, FLOAT + 1); + + /** The {@code long} type. */ + public static final Type LONG_TYPE = new Type(LONG, PRIMITIVE_DESCRIPTORS, LONG, LONG + 1); + + /** The {@code double} type. */ + public static final Type DOUBLE_TYPE = + new Type(DOUBLE, PRIMITIVE_DESCRIPTORS, DOUBLE, DOUBLE + 1); + + // ----------------------------------------------------------------------------------------------- + // Fields + // ----------------------------------------------------------------------------------------------- + + /** + * The sort of this type. Either {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, + * {@link #SHORT}, {@link #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, + * {@link #OBJECT}, {@link #METHOD} or {@link #INTERNAL}. + */ + private final int sort; + + /** + * A buffer containing the value of this field or method type. This value is an internal name for + * {@link #OBJECT} and {@link #INTERNAL} types, and a field or method descriptor in the other + * cases. + * + *

For {@link #OBJECT} types, this field also contains the descriptor: the characters in + * [{@link #valueBegin},{@link #valueEnd}) contain the internal name, and those in [{@link + * #valueBegin} - 1, {@link #valueEnd} + 1) contain the descriptor. + */ + private final String valueBuffer; + + /** + * The beginning index, inclusive, of the value of this Java field or method type in {@link + * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types, + * and a field or method descriptor in the other cases. + */ + private final int valueBegin; + + /** + * The end index, exclusive, of the value of this Java field or method type in {@link + * #valueBuffer}. This value is an internal name for {@link #OBJECT} and {@link #INTERNAL} types, + * and a field or method descriptor in the other cases. + */ + private final int valueEnd; + + /** + * Constructs a reference type. + * + * @param sort the sort of this type, see {@link #sort}. + * @param valueBuffer a buffer containing the value of this field or method type. + * @param valueBegin the beginning index, inclusive, of the value of this field or method type in + * valueBuffer. + * @param valueEnd the end index, exclusive, of the value of this field or method type in + * valueBuffer. + */ + private Type(final int sort, final String valueBuffer, final int valueBegin, final int valueEnd) { + this.sort = sort; + this.valueBuffer = valueBuffer; + this.valueBegin = valueBegin; + this.valueEnd = valueEnd; + } + + // ----------------------------------------------------------------------------------------------- + // Methods to get Type(s) from a descriptor, a reflected Method or Constructor, other types, etc. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the {@link Type} corresponding to the given type descriptor. + * + * @param typeDescriptor a field or method type descriptor. + * @return the {@link Type} corresponding to the given type descriptor. + */ + public static Type getType(final String typeDescriptor) { + return getTypeInternal(typeDescriptor, 0, typeDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the given class. + * + * @param clazz a class. + * @return the {@link Type} corresponding to the given class. + */ + public static Type getType(final Class clazz) { + if (clazz.isPrimitive()) { + if (clazz == Integer.TYPE) { + return INT_TYPE; + } else if (clazz == Void.TYPE) { + return VOID_TYPE; + } else if (clazz == Boolean.TYPE) { + return BOOLEAN_TYPE; + } else if (clazz == Byte.TYPE) { + return BYTE_TYPE; + } else if (clazz == Character.TYPE) { + return CHAR_TYPE; + } else if (clazz == Short.TYPE) { + return SHORT_TYPE; + } else if (clazz == Double.TYPE) { + return DOUBLE_TYPE; + } else if (clazz == Float.TYPE) { + return FLOAT_TYPE; + } else if (clazz == Long.TYPE) { + return LONG_TYPE; + } else { + throw new AssertionError(); + } + } else { + return getType(getDescriptor(clazz)); + } + } + + /** + * Returns the method {@link Type} corresponding to the given constructor. + * + * @param constructor a {@link Constructor} object. + * @return the method {@link Type} corresponding to the given constructor. + */ + public static Type getType(final Constructor constructor) { + return getType(getConstructorDescriptor(constructor)); + } + + /** + * Returns the method {@link Type} corresponding to the given method. + * + * @param method a {@link Method} object. + * @return the method {@link Type} corresponding to the given method. + */ + public static Type getType(final Method method) { + return getType(getMethodDescriptor(method)); + } + + /** + * Returns the type of the elements of this array type. This method should only be used for an + * array type. + * + * @return Returns the type of the elements of this array type. + */ + public Type getElementType() { + final int numDimensions = getDimensions(); + return getTypeInternal(valueBuffer, valueBegin + numDimensions, valueEnd); + } + + /** + * Returns the {@link Type} corresponding to the given internal name. + * + * @param internalName an internal name (see {@link Type#getInternalName()}). + * @return the {@link Type} corresponding to the given internal name. + */ + public static Type getObjectType(final String internalName) { + return new Type( + internalName.charAt(0) == '[' ? ARRAY : INTERNAL, internalName, 0, internalName.length()); + } + + /** + * Returns the {@link Type} corresponding to the given method descriptor. Equivalent to + * Type.getType(methodDescriptor). + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the given method descriptor. + */ + public static Type getMethodType(final String methodDescriptor) { + return new Type(METHOD, methodDescriptor, 0, methodDescriptor.length()); + } + + /** + * Returns the method {@link Type} corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the method {@link Type} corresponding to the given argument and return types. + */ + public static Type getMethodType(final Type returnType, final Type... argumentTypes) { + return getType(getMethodDescriptor(returnType, argumentTypes)); + } + + /** + * Returns the argument types of methods of this type. This method should only be used for method + * types. + * + * @return the argument types of methods of this type. + */ + public Type[] getArgumentTypes() { + return getArgumentTypes(getDescriptor()); + } + + /** + * Returns the {@link Type} values corresponding to the argument types of the given method + * descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} values corresponding to the argument types of the given method + * descriptor. + */ + public static Type[] getArgumentTypes(final String methodDescriptor) { + // First step: compute the number of argument types in methodDescriptor. + int numArgumentTypes = getArgumentCount(methodDescriptor); + + // Second step: create a Type instance for each argument type. + Type[] argumentTypes = new Type[numArgumentTypes]; + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Parse and create the argument types, one at each loop iteration. + int currentArgumentTypeIndex = 0; + while (methodDescriptor.charAt(currentOffset) != ')') { + final int currentArgumentTypeOffset = currentOffset; + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + argumentTypes[currentArgumentTypeIndex++] = + getTypeInternal(methodDescriptor, currentArgumentTypeOffset, currentOffset); + } + return argumentTypes; + } + + /** + * Returns the {@link Type} values corresponding to the argument types of the given method. + * + * @param method a method. + * @return the {@link Type} values corresponding to the argument types of the given method. + */ + public static Type[] getArgumentTypes(final Method method) { + Class[] classes = method.getParameterTypes(); + Type[] types = new Type[classes.length]; + for (int i = classes.length - 1; i >= 0; --i) { + types[i] = getType(classes[i]); + } + return types; + } + + /** + * Returns the return type of methods of this type. This method should only be used for method + * types. + * + * @return the return type of methods of this type. + */ + public Type getReturnType() { + return getReturnType(getDescriptor()); + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the {@link Type} corresponding to the return type of the given method descriptor. + */ + public static Type getReturnType(final String methodDescriptor) { + return getTypeInternal( + methodDescriptor, getReturnTypeOffset(methodDescriptor), methodDescriptor.length()); + } + + /** + * Returns the {@link Type} corresponding to the return type of the given method. + * + * @param method a method. + * @return the {@link Type} corresponding to the return type of the given method. + */ + public static Type getReturnType(final Method method) { + return getType(method.getReturnType()); + } + + /** + * Returns the start index of the return type of the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the start index of the return type of the given method descriptor. + */ + static int getReturnTypeOffset(final String methodDescriptor) { + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Skip the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + } + return currentOffset + 1; + } + + /** + * Returns the {@link Type} corresponding to the given field or method descriptor. + * + * @param descriptorBuffer a buffer containing the field or method descriptor. + * @param descriptorBegin the beginning index, inclusive, of the field or method descriptor in + * descriptorBuffer. + * @param descriptorEnd the end index, exclusive, of the field or method descriptor in + * descriptorBuffer. + * @return the {@link Type} corresponding to the given type descriptor. + */ + private static Type getTypeInternal( + final String descriptorBuffer, final int descriptorBegin, final int descriptorEnd) { + switch (descriptorBuffer.charAt(descriptorBegin)) { + case 'V': + return VOID_TYPE; + case 'Z': + return BOOLEAN_TYPE; + case 'C': + return CHAR_TYPE; + case 'B': + return BYTE_TYPE; + case 'S': + return SHORT_TYPE; + case 'I': + return INT_TYPE; + case 'F': + return FLOAT_TYPE; + case 'J': + return LONG_TYPE; + case 'D': + return DOUBLE_TYPE; + case '[': + return new Type(ARRAY, descriptorBuffer, descriptorBegin, descriptorEnd); + case 'L': + return new Type(OBJECT, descriptorBuffer, descriptorBegin + 1, descriptorEnd - 1); + case '(': + return new Type(METHOD, descriptorBuffer, descriptorBegin, descriptorEnd); + default: + throw new IllegalArgumentException("Invalid descriptor: " + descriptorBuffer); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to get class names, internal names or descriptors. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the binary name of the class corresponding to this type. This method must not be used + * on method types. + * + * @return the binary name of the class corresponding to this type. + */ + public String getClassName() { + switch (sort) { + case VOID: + return "void"; + case BOOLEAN: + return "boolean"; + case CHAR: + return "char"; + case BYTE: + return "byte"; + case SHORT: + return "short"; + case INT: + return "int"; + case FLOAT: + return "float"; + case LONG: + return "long"; + case DOUBLE: + return "double"; + case ARRAY: + StringBuilder stringBuilder = new StringBuilder(getElementType().getClassName()); + for (int i = getDimensions(); i > 0; --i) { + stringBuilder.append("[]"); + } + return stringBuilder.toString(); + case OBJECT: + case INTERNAL: + return valueBuffer.substring(valueBegin, valueEnd).replace('/', '.'); + default: + throw new AssertionError(); + } + } + + /** + * Returns the internal name of the class corresponding to this object or array type. The internal + * name of a class is its fully qualified name (as returned by Class.getName(), where '.' are + * replaced by '/'). This method should only be used for an object or array type. + * + * @return the internal name of the class corresponding to this object type. + */ + public String getInternalName() { + return valueBuffer.substring(valueBegin, valueEnd); + } + + /** + * Returns the internal name of the given class. The internal name of a class is its fully + * qualified name, as returned by Class.getName(), where '.' are replaced by '/'. + * + * @param clazz an object or array class. + * @return the internal name of the given class. + */ + public static String getInternalName(final Class clazz) { + return clazz.getName().replace('.', '/'); + } + + /** + * Returns the descriptor corresponding to this type. + * + * @return the descriptor corresponding to this type. + */ + public String getDescriptor() { + if (sort == OBJECT) { + return valueBuffer.substring(valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + return 'L' + valueBuffer.substring(valueBegin, valueEnd) + ';'; + } else { + return valueBuffer.substring(valueBegin, valueEnd); + } + } + + /** + * Returns the descriptor corresponding to the given class. + * + * @param clazz an object class, a primitive class or an array class. + * @return the descriptor corresponding to the given class. + */ + public static String getDescriptor(final Class clazz) { + StringBuilder stringBuilder = new StringBuilder(); + appendDescriptor(clazz, stringBuilder); + return stringBuilder.toString(); + } + + /** + * Returns the descriptor corresponding to the given constructor. + * + * @param constructor a {@link Constructor} object. + * @return the descriptor of the given constructor. + */ + public static String getConstructorDescriptor(final Constructor constructor) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + Class[] parameters = constructor.getParameterTypes(); + for (Class parameter : parameters) { + appendDescriptor(parameter, stringBuilder); + } + return stringBuilder.append(")V").toString(); + } + + /** + * Returns the descriptor corresponding to the given argument and return types. + * + * @param returnType the return type of the method. + * @param argumentTypes the argument types of the method. + * @return the descriptor corresponding to the given argument and return types. + */ + public static String getMethodDescriptor(final Type returnType, final Type... argumentTypes) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + for (Type argumentType : argumentTypes) { + argumentType.appendDescriptor(stringBuilder); + } + stringBuilder.append(')'); + returnType.appendDescriptor(stringBuilder); + return stringBuilder.toString(); + } + + /** + * Returns the descriptor corresponding to the given method. + * + * @param method a {@link Method} object. + * @return the descriptor of the given method. + */ + public static String getMethodDescriptor(final Method method) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append('('); + Class[] parameters = method.getParameterTypes(); + for (Class parameter : parameters) { + appendDescriptor(parameter, stringBuilder); + } + stringBuilder.append(')'); + appendDescriptor(method.getReturnType(), stringBuilder); + return stringBuilder.toString(); + } + + /** + * Appends the descriptor corresponding to this type to the given string buffer. + * + * @param stringBuilder the string builder to which the descriptor must be appended. + */ + private void appendDescriptor(final StringBuilder stringBuilder) { + if (sort == OBJECT) { + stringBuilder.append(valueBuffer, valueBegin - 1, valueEnd + 1); + } else if (sort == INTERNAL) { + stringBuilder.append('L').append(valueBuffer, valueBegin, valueEnd).append(';'); + } else { + stringBuilder.append(valueBuffer, valueBegin, valueEnd); + } + } + + /** + * Appends the descriptor of the given class to the given string builder. + * + * @param clazz the class whose descriptor must be computed. + * @param stringBuilder the string builder to which the descriptor must be appended. + */ + private static void appendDescriptor(final Class clazz, final StringBuilder stringBuilder) { + Class currentClass = clazz; + while (currentClass.isArray()) { + stringBuilder.append('['); + currentClass = currentClass.getComponentType(); + } + if (currentClass.isPrimitive()) { + char descriptor; + if (currentClass == Integer.TYPE) { + descriptor = 'I'; + } else if (currentClass == Void.TYPE) { + descriptor = 'V'; + } else if (currentClass == Boolean.TYPE) { + descriptor = 'Z'; + } else if (currentClass == Byte.TYPE) { + descriptor = 'B'; + } else if (currentClass == Character.TYPE) { + descriptor = 'C'; + } else if (currentClass == Short.TYPE) { + descriptor = 'S'; + } else if (currentClass == Double.TYPE) { + descriptor = 'D'; + } else if (currentClass == Float.TYPE) { + descriptor = 'F'; + } else if (currentClass == Long.TYPE) { + descriptor = 'J'; + } else { + throw new AssertionError(); + } + stringBuilder.append(descriptor); + } else { + stringBuilder.append('L').append(getInternalName(currentClass)).append(';'); + } + } + + // ----------------------------------------------------------------------------------------------- + // Methods to get the sort, dimension, size, and opcodes corresponding to a Type or descriptor. + // ----------------------------------------------------------------------------------------------- + + /** + * Returns the sort of this type. + * + * @return {@link #VOID}, {@link #BOOLEAN}, {@link #CHAR}, {@link #BYTE}, {@link #SHORT}, {@link + * #INT}, {@link #FLOAT}, {@link #LONG}, {@link #DOUBLE}, {@link #ARRAY}, {@link #OBJECT} or + * {@link #METHOD}. + */ + public int getSort() { + return sort == INTERNAL ? OBJECT : sort; + } + + /** + * Returns the number of dimensions of this array type. This method should only be used for an + * array type. + * + * @return the number of dimensions of this array type. + */ + public int getDimensions() { + int numDimensions = 1; + while (valueBuffer.charAt(valueBegin + numDimensions) == '[') { + numDimensions++; + } + return numDimensions; + } + + /** + * Returns the size of values of this type. This method must not be used for method types. + * + * @return the size of values of this type, i.e., 2 for {@code long} and {@code double}, 0 for + * {@code void} and 1 otherwise. + */ + public int getSize() { + switch (sort) { + case VOID: + return 0; + case BOOLEAN: + case CHAR: + case BYTE: + case SHORT: + case INT: + case FLOAT: + case ARRAY: + case OBJECT: + case INTERNAL: + return 1; + case LONG: + case DOUBLE: + return 2; + default: + throw new AssertionError(); + } + } + + /** + * Returns the number of arguments of this method type. This method should only be used for method + * types. + * + * @return the number of arguments of this method type. Each argument counts for 1, even long and + * double ones. The implicit @literal{this} argument is not counted. + */ + public int getArgumentCount() { + return getArgumentCount(getDescriptor()); + } + + /** + * Returns the number of arguments in the given method descriptor. + * + * @param methodDescriptor a method descriptor. + * @return the number of arguments in the given method descriptor. Each argument counts for 1, + * even long and double ones. The implicit @literal{this} argument is not counted. + */ + public static int getArgumentCount(final String methodDescriptor) { + int argumentCount = 0; + // Skip the first character, which is always a '('. + int currentOffset = 1; + // Parse the argument types, one at a each loop iteration. + while (methodDescriptor.charAt(currentOffset) != ')') { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + ++argumentCount; + } + return argumentCount; + } + + /** + * Returns the size of the arguments and of the return value of methods of this type. This method + * should only be used for method types. + * + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code + * i >> 2}, and returnSize to {@code i & 0x03}). Long and double values have size 2, + * the others have size 1. + */ + public int getArgumentsAndReturnSizes() { + return getArgumentsAndReturnSizes(getDescriptor()); + } + + /** + * Computes the size of the arguments and of the return value of a method. + * + * @param methodDescriptor a method descriptor. + * @return the size of the arguments of the method (plus one for the implicit this argument), + * argumentsSize, and the size of its return value, returnSize, packed into a single int i = + * {@code (argumentsSize << 2) | returnSize} (argumentsSize is therefore equal to {@code + * i >> 2}, and returnSize to {@code i & 0x03}). Long and double values have size 2, + * the others have size 1. + */ + public static int getArgumentsAndReturnSizes(final String methodDescriptor) { + int argumentsSize = 1; + // Skip the first character, which is always a '('. + int currentOffset = 1; + int currentChar = methodDescriptor.charAt(currentOffset); + // Parse the argument types and compute their size, one at a each loop iteration. + while (currentChar != ')') { + if (currentChar == 'J' || currentChar == 'D') { + currentOffset++; + argumentsSize += 2; + } else { + while (methodDescriptor.charAt(currentOffset) == '[') { + currentOffset++; + } + if (methodDescriptor.charAt(currentOffset++) == 'L') { + // Skip the argument descriptor content. + int semiColumnOffset = methodDescriptor.indexOf(';', currentOffset); + currentOffset = Math.max(currentOffset, semiColumnOffset + 1); + } + argumentsSize += 1; + } + currentChar = methodDescriptor.charAt(currentOffset); + } + currentChar = methodDescriptor.charAt(currentOffset + 1); + if (currentChar == 'V') { + return argumentsSize << 2; + } else { + int returnSize = (currentChar == 'J' || currentChar == 'D') ? 2 : 1; + return argumentsSize << 2 | returnSize; + } + } + + /** + * Returns a JVM instruction opcode adapted to this {@link Type}. This method must not be used for + * method types. + * + * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD, + * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR, IXOR and + * IRETURN. + * @return an opcode that is similar to the given opcode, but adapted to this {@link Type}. For + * example, if this type is {@code float} and {@code opcode} is IRETURN, this method returns + * FRETURN. + */ + public int getOpcode(final int opcode) { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { + switch (sort) { + case BOOLEAN: + case BYTE: + return opcode + (Opcodes.BALOAD - Opcodes.IALOAD); + case CHAR: + return opcode + (Opcodes.CALOAD - Opcodes.IALOAD); + case SHORT: + return opcode + (Opcodes.SALOAD - Opcodes.IALOAD); + case INT: + return opcode; + case FLOAT: + return opcode + (Opcodes.FALOAD - Opcodes.IALOAD); + case LONG: + return opcode + (Opcodes.LALOAD - Opcodes.IALOAD); + case DOUBLE: + return opcode + (Opcodes.DALOAD - Opcodes.IALOAD); + case ARRAY: + case OBJECT: + case INTERNAL: + return opcode + (Opcodes.AALOAD - Opcodes.IALOAD); + case METHOD: + case VOID: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } else { + switch (sort) { + case VOID: + if (opcode != Opcodes.IRETURN) { + throw new UnsupportedOperationException(); + } + return Opcodes.RETURN; + case BOOLEAN: + case BYTE: + case CHAR: + case SHORT: + case INT: + return opcode; + case FLOAT: + return opcode + (Opcodes.FRETURN - Opcodes.IRETURN); + case LONG: + return opcode + (Opcodes.LRETURN - Opcodes.IRETURN); + case DOUBLE: + return opcode + (Opcodes.DRETURN - Opcodes.IRETURN); + case ARRAY: + case OBJECT: + case INTERNAL: + if (opcode != Opcodes.ILOAD && opcode != Opcodes.ISTORE && opcode != Opcodes.IRETURN) { + throw new UnsupportedOperationException(); + } + return opcode + (Opcodes.ARETURN - Opcodes.IRETURN); + case METHOD: + throw new UnsupportedOperationException(); + default: + throw new AssertionError(); + } + } + } + + // ----------------------------------------------------------------------------------------------- + // Equals, hashCode and toString. + // ----------------------------------------------------------------------------------------------- + + /** + * Tests if the given object is equal to this type. + * + * @param object the object to be compared to this type. + * @return {@literal true} if the given object is equal to this type. + */ + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof Type)) { + return false; + } + Type other = (Type) object; + if ((sort == INTERNAL ? OBJECT : sort) != (other.sort == INTERNAL ? OBJECT : other.sort)) { + return false; + } + int begin = valueBegin; + int end = valueEnd; + int otherBegin = other.valueBegin; + int otherEnd = other.valueEnd; + // Compare the values. + if (end - begin != otherEnd - otherBegin) { + return false; + } + for (int i = begin, j = otherBegin; i < end; i++, j++) { + if (valueBuffer.charAt(i) != other.valueBuffer.charAt(j)) { + return false; + } + } + return true; + } + + /** + * Returns a hash code value for this type. + * + * @return a hash code value for this type. + */ + @Override + public int hashCode() { + int hashCode = 13 * (sort == INTERNAL ? OBJECT : sort); + if (sort >= ARRAY) { + for (int i = valueBegin, end = valueEnd; i < end; i++) { + hashCode = 17 * (hashCode + valueBuffer.charAt(i)); + } + } + return hashCode; + } + + /** + * Returns a string representation of this type. + * + * @return the descriptor of this type. + */ + @Override + public String toString() { + return getDescriptor(); + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/TypePath.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypePath.class new file mode 100644 index 00000000..878b1915 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypePath.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/TypePath.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypePath.java new file mode 100644 index 00000000..ea1025dd --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypePath.java @@ -0,0 +1,232 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * The path to a type argument, wildcard bound, array element type, or static inner type within an + * enclosing type. + * + * @author Eric Bruneton + */ +public final class TypePath { + + /** A type path step that steps into the element type of an array type. See {@link #getStep}. */ + public static final int ARRAY_ELEMENT = 0; + + /** A type path step that steps into the nested type of a class type. See {@link #getStep}. */ + public static final int INNER_TYPE = 1; + + /** A type path step that steps into the bound of a wildcard type. See {@link #getStep}. */ + public static final int WILDCARD_BOUND = 2; + + /** A type path step that steps into a type argument of a generic type. See {@link #getStep}. */ + public static final int TYPE_ARGUMENT = 3; + + /** + * The byte array where the 'type_path' structure - as defined in the Java Virtual Machine + * Specification (JVMS) - corresponding to this TypePath is stored. The first byte of the + * structure in this array is given by {@link #typePathOffset}. + * + * @see JVMS + * 4.7.20.2 + */ + private final byte[] typePathContainer; + + /** The offset of the first byte of the type_path JVMS structure in {@link #typePathContainer}. */ + private final int typePathOffset; + + /** + * Constructs a new TypePath. + * + * @param typePathContainer a byte array containing a type_path JVMS structure. + * @param typePathOffset the offset of the first byte of the type_path structure in + * typePathContainer. + */ + TypePath(final byte[] typePathContainer, final int typePathOffset) { + this.typePathContainer = typePathContainer; + this.typePathOffset = typePathOffset; + } + + /** + * Returns the length of this path, i.e. its number of steps. + * + * @return the length of this path. + */ + public int getLength() { + // path_length is stored in the first byte of a type_path. + return typePathContainer[typePathOffset]; + } + + /** + * Returns the value of the given step of this path. + * + * @param index an index between 0 and {@link #getLength()}, exclusive. + * @return one of {@link #ARRAY_ELEMENT}, {@link #INNER_TYPE}, {@link #WILDCARD_BOUND}, or {@link + * #TYPE_ARGUMENT}. + */ + public int getStep(final int index) { + // Returns the type_path_kind of the path element of the given index. + return typePathContainer[typePathOffset + 2 * index + 1]; + } + + /** + * Returns the index of the type argument that the given step is stepping into. This method should + * only be used for steps whose value is {@link #TYPE_ARGUMENT}. + * + * @param index an index between 0 and {@link #getLength()}, exclusive. + * @return the index of the type argument that the given step is stepping into. + */ + public int getStepArgument(final int index) { + // Returns the type_argument_index of the path element of the given index. + return typePathContainer[typePathOffset + 2 * index + 2]; + } + + /** + * Converts a type path in string form, in the format used by {@link #toString()}, into a TypePath + * object. + * + * @param typePath a type path in string form, in the format used by {@link #toString()}. May be + * {@literal null} or empty. + * @return the corresponding TypePath object, or {@literal null} if the path is empty. + */ + public static TypePath fromString(final String typePath) { + if (typePath == null || typePath.length() == 0) { + return null; + } + int typePathLength = typePath.length(); + ByteVector output = new ByteVector(typePathLength); + output.putByte(0); + int typePathIndex = 0; + while (typePathIndex < typePathLength) { + char c = typePath.charAt(typePathIndex++); + if (c == '[') { + output.put11(ARRAY_ELEMENT, 0); + } else if (c == '.') { + output.put11(INNER_TYPE, 0); + } else if (c == '*') { + output.put11(WILDCARD_BOUND, 0); + } else if (c >= '0' && c <= '9') { + int typeArg = c - '0'; + while (typePathIndex < typePathLength) { + c = typePath.charAt(typePathIndex++); + if (c >= '0' && c <= '9') { + typeArg = typeArg * 10 + c - '0'; + } else if (c == ';') { + break; + } else { + throw new IllegalArgumentException(); + } + } + output.put11(TYPE_ARGUMENT, typeArg); + } else { + throw new IllegalArgumentException(); + } + } + output.data[0] = (byte) (output.length / 2); + return new TypePath(output.data, 0); + } + + /** + * Returns a string representation of this type path. {@link #ARRAY_ELEMENT} steps are represented + * with '[', {@link #INNER_TYPE} steps with '.', {@link #WILDCARD_BOUND} steps with '*' and {@link + * #TYPE_ARGUMENT} steps with their type argument index in decimal form followed by ';'. + */ + @Override + public String toString() { + int length = getLength(); + StringBuilder result = new StringBuilder(length * 2); + for (int i = 0; i < length; ++i) { + switch (getStep(i)) { + case ARRAY_ELEMENT: + result.append('['); + break; + case INNER_TYPE: + result.append('.'); + break; + case WILDCARD_BOUND: + result.append('*'); + break; + case TYPE_ARGUMENT: + result.append(getStepArgument(i)).append(';'); + break; + default: + throw new AssertionError(); + } + } + return result.toString(); + } + + /** + * Puts the type_path JVMS structure corresponding to the given TypePath into the given + * ByteVector. + * + * @param typePath a TypePath instance, or {@literal null} for empty paths. + * @param output where the type path must be put. + */ + static void put(final TypePath typePath, final ByteVector output) { + if (typePath == null) { + output.putByte(0); + } else { + int length = typePath.typePathContainer[typePath.typePathOffset] * 2 + 1; + output.putByteArray(typePath.typePathContainer, typePath.typePathOffset, length); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/TypeReference.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypeReference.class new file mode 100644 index 00000000..d0886027 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypeReference.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/TypeReference.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypeReference.java new file mode 100644 index 00000000..512dc8fc --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/TypeReference.java @@ -0,0 +1,467 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm; + +/** + * A reference to a type appearing in a class, field or method declaration, or on an instruction. + * Such a reference designates the part of the class where the referenced type is appearing (e.g. an + * 'extends', 'implements' or 'throws' clause, a 'new' instruction, a 'catch' clause, a type cast, a + * local variable declaration, etc). + * + * @author Eric Bruneton + */ +public class TypeReference { + + /** + * The sort of type references that target a type parameter of a generic class. See {@link + * #getSort}. + */ + public static final int CLASS_TYPE_PARAMETER = 0x00; + + /** + * The sort of type references that target a type parameter of a generic method. See {@link + * #getSort}. + */ + public static final int METHOD_TYPE_PARAMETER = 0x01; + + /** + * The sort of type references that target the super class of a class or one of the interfaces it + * implements. See {@link #getSort}. + */ + public static final int CLASS_EXTENDS = 0x10; + + /** + * The sort of type references that target a bound of a type parameter of a generic class. See + * {@link #getSort}. + */ + public static final int CLASS_TYPE_PARAMETER_BOUND = 0x11; + + /** + * The sort of type references that target a bound of a type parameter of a generic method. See + * {@link #getSort}. + */ + public static final int METHOD_TYPE_PARAMETER_BOUND = 0x12; + + /** The sort of type references that target the type of a field. See {@link #getSort}. */ + public static final int FIELD = 0x13; + + /** The sort of type references that target the return type of a method. See {@link #getSort}. */ + public static final int METHOD_RETURN = 0x14; + + /** + * The sort of type references that target the receiver type of a method. See {@link #getSort}. + */ + public static final int METHOD_RECEIVER = 0x15; + + /** + * The sort of type references that target the type of a formal parameter of a method. See {@link + * #getSort}. + */ + public static final int METHOD_FORMAL_PARAMETER = 0x16; + + /** + * The sort of type references that target the type of an exception declared in the throws clause + * of a method. See {@link #getSort}. + */ + public static final int THROWS = 0x17; + + /** + * The sort of type references that target the type of a local variable in a method. See {@link + * #getSort}. + */ + public static final int LOCAL_VARIABLE = 0x40; + + /** + * The sort of type references that target the type of a resource variable in a method. See {@link + * #getSort}. + */ + public static final int RESOURCE_VARIABLE = 0x41; + + /** + * The sort of type references that target the type of the exception of a 'catch' clause in a + * method. See {@link #getSort}. + */ + public static final int EXCEPTION_PARAMETER = 0x42; + + /** + * The sort of type references that target the type declared in an 'instanceof' instruction. See + * {@link #getSort}. + */ + public static final int INSTANCEOF = 0x43; + + /** + * The sort of type references that target the type of the object created by a 'new' instruction. + * See {@link #getSort}. + */ + public static final int NEW = 0x44; + + /** + * The sort of type references that target the receiver type of a constructor reference. See + * {@link #getSort}. + */ + public static final int CONSTRUCTOR_REFERENCE = 0x45; + + /** + * The sort of type references that target the receiver type of a method reference. See {@link + * #getSort}. + */ + public static final int METHOD_REFERENCE = 0x46; + + /** + * The sort of type references that target the type declared in an explicit or implicit cast + * instruction. See {@link #getSort}. + */ + public static final int CAST = 0x47; + + /** + * The sort of type references that target a type parameter of a generic constructor in a + * constructor call. See {@link #getSort}. + */ + public static final int CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT = 0x48; + + /** + * The sort of type references that target a type parameter of a generic method in a method call. + * See {@link #getSort}. + */ + public static final int METHOD_INVOCATION_TYPE_ARGUMENT = 0x49; + + /** + * The sort of type references that target a type parameter of a generic constructor in a + * constructor reference. See {@link #getSort}. + */ + public static final int CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT = 0x4A; + + /** + * The sort of type references that target a type parameter of a generic method in a method + * reference. See {@link #getSort}. + */ + public static final int METHOD_REFERENCE_TYPE_ARGUMENT = 0x4B; + + /** + * The target_type and target_info structures - as defined in the Java Virtual Machine + * Specification (JVMS) - corresponding to this type reference. target_type uses one byte, and all + * the target_info union fields use up to 3 bytes (except localvar_target, handled with the + * specific method {@link MethodVisitor#visitLocalVariableAnnotation}). Thus, both structures can + * be stored in an int. + * + *

This int field stores target_type (called the TypeReference 'sort' in the public API of this + * class) in its most significant byte, followed by the target_info fields. Depending on + * target_type, 1, 2 or even 3 least significant bytes of this field are unused. target_info + * fields which reference bytecode offsets are set to 0 (these offsets are ignored in ClassReader, + * and recomputed in MethodWriter). + * + * @see JVMS + * 4.7.20 + * @see JVMS + * 4.7.20.1 + */ + private final int targetTypeAndInfo; + + /** + * Constructs a new TypeReference. + * + * @param typeRef the int encoded value of the type reference, as received in a visit method + * related to type annotations, such as {@link ClassVisitor#visitTypeAnnotation}. + */ + public TypeReference(final int typeRef) { + this.targetTypeAndInfo = typeRef; + } + + /** + * Returns a type reference of the given sort. + * + * @param sort one of {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link + * #LOCAL_VARIABLE}, {@link #RESOURCE_VARIABLE}, {@link #INSTANCEOF}, {@link #NEW}, {@link + * #CONSTRUCTOR_REFERENCE}, or {@link #METHOD_REFERENCE}. + * @return a type reference of the given sort. + */ + public static TypeReference newTypeReference(final int sort) { + return new TypeReference(sort << 24); + } + + /** + * Returns a reference to a type parameter of a generic class or method. + * + * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}. + * @param paramIndex the type parameter index. + * @return a reference to the given generic class or method type parameter. + */ + public static TypeReference newTypeParameterReference(final int sort, final int paramIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to a type parameter bound of a generic class or method. + * + * @param sort one of {@link #CLASS_TYPE_PARAMETER} or {@link #METHOD_TYPE_PARAMETER}. + * @param paramIndex the type parameter index. + * @param boundIndex the type bound index within the above type parameters. + * @return a reference to the given generic class or method type parameter bound. + */ + public static TypeReference newTypeParameterBoundReference( + final int sort, final int paramIndex, final int boundIndex) { + return new TypeReference((sort << 24) | (paramIndex << 16) | (boundIndex << 8)); + } + + /** + * Returns a reference to the super class or to an interface of the 'implements' clause of a + * class. + * + * @param itfIndex the index of an interface in the 'implements' clause of a class, or -1 to + * reference the super class of the class. + * @return a reference to the given super type of a class. + */ + public static TypeReference newSuperTypeReference(final int itfIndex) { + return new TypeReference((CLASS_EXTENDS << 24) | ((itfIndex & 0xFFFF) << 8)); + } + + /** + * Returns a reference to the type of a formal parameter of a method. + * + * @param paramIndex the formal parameter index. + * @return a reference to the type of the given method formal parameter. + */ + public static TypeReference newFormalParameterReference(final int paramIndex) { + return new TypeReference((METHOD_FORMAL_PARAMETER << 24) | (paramIndex << 16)); + } + + /** + * Returns a reference to the type of an exception, in a 'throws' clause of a method. + * + * @param exceptionIndex the index of an exception in a 'throws' clause of a method. + * @return a reference to the type of the given exception. + */ + public static TypeReference newExceptionReference(final int exceptionIndex) { + return new TypeReference((THROWS << 24) | (exceptionIndex << 8)); + } + + /** + * Returns a reference to the type of the exception declared in a 'catch' clause of a method. + * + * @param tryCatchBlockIndex the index of a try catch block (using the order in which they are + * visited with visitTryCatchBlock). + * @return a reference to the type of the given exception. + */ + public static TypeReference newTryCatchReference(final int tryCatchBlockIndex) { + return new TypeReference((EXCEPTION_PARAMETER << 24) | (tryCatchBlockIndex << 8)); + } + + /** + * Returns a reference to the type of a type argument in a constructor or method call or + * reference. + * + * @param sort one of {@link #CAST}, {@link #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link + * #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link + * #METHOD_REFERENCE_TYPE_ARGUMENT}. + * @param argIndex the type argument index. + * @return a reference to the type of the given type argument. + */ + public static TypeReference newTypeArgumentReference(final int sort, final int argIndex) { + return new TypeReference((sort << 24) | argIndex); + } + + /** + * Returns the sort of this type reference. + * + * @return one of {@link #CLASS_TYPE_PARAMETER}, {@link #METHOD_TYPE_PARAMETER}, {@link + * #CLASS_EXTENDS}, {@link #CLASS_TYPE_PARAMETER_BOUND}, {@link #METHOD_TYPE_PARAMETER_BOUND}, + * {@link #FIELD}, {@link #METHOD_RETURN}, {@link #METHOD_RECEIVER}, {@link + * #METHOD_FORMAL_PARAMETER}, {@link #THROWS}, {@link #LOCAL_VARIABLE}, {@link + * #RESOURCE_VARIABLE}, {@link #EXCEPTION_PARAMETER}, {@link #INSTANCEOF}, {@link #NEW}, + * {@link #CONSTRUCTOR_REFERENCE}, {@link #METHOD_REFERENCE}, {@link #CAST}, {@link + * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}. + */ + public int getSort() { + return targetTypeAndInfo >>> 24; + } + + /** + * Returns the index of the type parameter referenced by this type reference. This method must + * only be used for type references whose sort is {@link #CLASS_TYPE_PARAMETER}, {@link + * #METHOD_TYPE_PARAMETER}, {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link + * #METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter index. + */ + public int getTypeParameterIndex() { + return (targetTypeAndInfo & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the type parameter bound, within the type parameter {@link + * #getTypeParameterIndex}, referenced by this type reference. This method must only be used for + * type references whose sort is {@link #CLASS_TYPE_PARAMETER_BOUND} or {@link + * #METHOD_TYPE_PARAMETER_BOUND}. + * + * @return a type parameter bound index. + */ + public int getTypeParameterBoundIndex() { + return (targetTypeAndInfo & 0x0000FF00) >> 8; + } + + /** + * Returns the index of the "super type" of a class that is referenced by this type reference. + * This method must only be used for type references whose sort is {@link #CLASS_EXTENDS}. + * + * @return the index of an interface in the 'implements' clause of a class, or -1 if this type + * reference references the type of the super class. + */ + public int getSuperTypeIndex() { + return (short) ((targetTypeAndInfo & 0x00FFFF00) >> 8); + } + + /** + * Returns the index of the formal parameter whose type is referenced by this type reference. This + * method must only be used for type references whose sort is {@link #METHOD_FORMAL_PARAMETER}. + * + * @return a formal parameter index. + */ + public int getFormalParameterIndex() { + return (targetTypeAndInfo & 0x00FF0000) >> 16; + } + + /** + * Returns the index of the exception, in a 'throws' clause of a method, whose type is referenced + * by this type reference. This method must only be used for type references whose sort is {@link + * #THROWS}. + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getExceptionIndex() { + return (targetTypeAndInfo & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the try catch block (using the order in which they are visited with + * visitTryCatchBlock), whose 'catch' type is referenced by this type reference. This method must + * only be used for type references whose sort is {@link #EXCEPTION_PARAMETER} . + * + * @return the index of an exception in the 'throws' clause of a method. + */ + public int getTryCatchBlockIndex() { + return (targetTypeAndInfo & 0x00FFFF00) >> 8; + } + + /** + * Returns the index of the type argument referenced by this type reference. This method must only + * be used for type references whose sort is {@link #CAST}, {@link + * #CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT}, {@link #METHOD_INVOCATION_TYPE_ARGUMENT}, {@link + * #CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or {@link #METHOD_REFERENCE_TYPE_ARGUMENT}. + * + * @return a type parameter index. + */ + public int getTypeArgumentIndex() { + return targetTypeAndInfo & 0xFF; + } + + /** + * Returns the int encoded value of this type reference, suitable for use in visit methods related + * to type annotations, like visitTypeAnnotation. + * + * @return the int encoded value of this type reference. + */ + public int getValue() { + return targetTypeAndInfo; + } + + /** + * Puts the given target_type and target_info JVMS structures into the given ByteVector. + * + * @param targetTypeAndInfo a target_type and a target_info structures encoded as in {@link + * #targetTypeAndInfo}. LOCAL_VARIABLE and RESOURCE_VARIABLE target types are not supported. + * @param output where the type reference must be put. + */ + static void putTarget(final int targetTypeAndInfo, final ByteVector output) { + switch (targetTypeAndInfo >>> 24) { + case CLASS_TYPE_PARAMETER: + case METHOD_TYPE_PARAMETER: + case METHOD_FORMAL_PARAMETER: + output.putShort(targetTypeAndInfo >>> 16); + break; + case FIELD: + case METHOD_RETURN: + case METHOD_RECEIVER: + output.putByte(targetTypeAndInfo >>> 24); + break; + case CAST: + case CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT: + case METHOD_INVOCATION_TYPE_ARGUMENT: + case CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT: + case METHOD_REFERENCE_TYPE_ARGUMENT: + output.putInt(targetTypeAndInfo); + break; + case CLASS_EXTENDS: + case CLASS_TYPE_PARAMETER_BOUND: + case METHOD_TYPE_PARAMETER_BOUND: + case THROWS: + case EXCEPTION_PARAMETER: + case INSTANCEOF: + case NEW: + case CONSTRUCTOR_REFERENCE: + case METHOD_REFERENCE: + output.put12(targetTypeAndInfo >>> 24, (targetTypeAndInfo & 0xFFFF00) >> 8); + break; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AdviceAdapter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AdviceAdapter.class new file mode 100644 index 00000000..6aa63488 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AdviceAdapter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AdviceAdapter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AdviceAdapter.java new file mode 100644 index 00000000..0d57c484 --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AdviceAdapter.java @@ -0,0 +1,702 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm.commons; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import jdk.internal.org.objectweb.asm.ConstantDynamic; +import jdk.internal.org.objectweb.asm.Handle; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; + +/** + * A {@link MethodVisitor} to insert before, after and around advices in methods and constructors. + * For constructors, the code keeps track of the elements on the stack in order to detect when the + * super class constructor is called (note that there can be multiple such calls in different + * branches). {@code onMethodEnter} is called after each super class constructor call, because the + * object cannot be used before it is properly initialized. + * + * @author Eugene Kuleshov + * @author Eric Bruneton + */ +public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { + + /** The "uninitialized this" value. */ + private static final Object UNINITIALIZED_THIS = new Object(); + + /** Any value other than "uninitialized this". */ + private static final Object OTHER = new Object(); + + /** Prefix of the error message when invalid opcodes are found. */ + private static final String INVALID_OPCODE = "Invalid opcode "; + + /** The access flags of the visited method. */ + protected int methodAccess; + + /** The descriptor of the visited method. */ + protected String methodDesc; + + /** Whether the visited method is a constructor. */ + private final boolean isConstructor; + + /** + * Whether the super class constructor has been called (if the visited method is a constructor), + * at the current instruction. There can be multiple call sites to the super constructor (e.g. for + * Java code such as {@code super(expr ? value1 : value2);}), in different branches. When scanning + * the bytecode linearly, we can move from one branch where the super constructor has been called + * to another where it has not been called yet. Therefore, this value can change from false to + * true, and vice-versa. + */ + private boolean superClassConstructorCalled; + + /** + * The values on the current execution stack frame (long and double are represented by two + * elements). Each value is either {@link #UNINITIALIZED_THIS} (for the uninitialized this value), + * or {@link #OTHER} (for any other value). This field is only maintained for constructors, in + * branches where the super class constructor has not been called yet. + */ + private List stackFrame; + + /** + * The stack map frames corresponding to the labels of the forward jumps made *before* the super + * class constructor has been called (note that the Java Virtual Machine forbids backward jumps + * before the super class constructor is called). Note that by definition (cf. the 'before'), when + * we reach a label from this map, {@link #superClassConstructorCalled} must be reset to false. + * This field is only maintained for constructors. + */ + private Map> forwardJumpStackFrames; + + /** + * Constructs a new {@link AdviceAdapter}. + * + * @param api the ASM API version implemented by this visitor. Must be one of the {@code + * ASM}x values in {@link Opcodes}. + * @param methodVisitor the method visitor to which this adapter delegates calls. + * @param access the method's access flags (see {@link Opcodes}). + * @param name the method's name. + * @param descriptor the method's descriptor (see {@link Type Type}). + */ + protected AdviceAdapter( + final int api, + final MethodVisitor methodVisitor, + final int access, + final String name, + final String descriptor) { + super(api, methodVisitor, access, name, descriptor); + methodAccess = access; + methodDesc = descriptor; + isConstructor = "".equals(name); + } + + @Override + public void visitCode() { + super.visitCode(); + if (isConstructor) { + stackFrame = new ArrayList<>(); + forwardJumpStackFrames = new HashMap<>(); + } else { + onMethodEnter(); + } + } + + @Override + public void visitLabel(final Label label) { + super.visitLabel(label); + if (isConstructor && forwardJumpStackFrames != null) { + List labelStackFrame = forwardJumpStackFrames.get(label); + if (labelStackFrame != null) { + stackFrame = labelStackFrame; + superClassConstructorCalled = false; + forwardJumpStackFrames.remove(label); + } + } + } + + @Override + public void visitInsn(final int opcode) { + if (isConstructor && !superClassConstructorCalled) { + int stackSize; + switch (opcode) { + case IRETURN: + case FRETURN: + case ARETURN: + case LRETURN: + case DRETURN: + throw new IllegalArgumentException("Invalid return in constructor"); + case RETURN: // empty stack + onMethodExit(opcode); + endConstructorBasicBlockWithoutSuccessor(); + break; + case ATHROW: // 1 before n/a after + popValue(); + onMethodExit(opcode); + endConstructorBasicBlockWithoutSuccessor(); + break; + case NOP: + case LALOAD: // remove 2 add 2 + case DALOAD: // remove 2 add 2 + case LNEG: + case DNEG: + case FNEG: + case INEG: + case L2D: + case D2L: + case F2I: + case I2B: + case I2C: + case I2S: + case I2F: + case ARRAYLENGTH: + break; + case ACONST_NULL: + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + case FCONST_0: + case FCONST_1: + case FCONST_2: + case F2L: // 1 before 2 after + case F2D: + case I2L: + case I2D: + pushValue(OTHER); + break; + case LCONST_0: + case LCONST_1: + case DCONST_0: + case DCONST_1: + pushValue(OTHER); + pushValue(OTHER); + break; + case IALOAD: // remove 2 add 1 + case FALOAD: // remove 2 add 1 + case AALOAD: // remove 2 add 1 + case BALOAD: // remove 2 add 1 + case CALOAD: // remove 2 add 1 + case SALOAD: // remove 2 add 1 + case POP: + case IADD: + case FADD: + case ISUB: + case LSHL: // 3 before 2 after + case LSHR: // 3 before 2 after + case LUSHR: // 3 before 2 after + case L2I: // 2 before 1 after + case L2F: // 2 before 1 after + case D2I: // 2 before 1 after + case D2F: // 2 before 1 after + case FSUB: + case FMUL: + case FDIV: + case FREM: + case FCMPL: // 2 before 1 after + case FCMPG: // 2 before 1 after + case IMUL: + case IDIV: + case IREM: + case ISHL: + case ISHR: + case IUSHR: + case IAND: + case IOR: + case IXOR: + case MONITORENTER: + case MONITOREXIT: + popValue(); + break; + case POP2: + case LSUB: + case LMUL: + case LDIV: + case LREM: + case LADD: + case LAND: + case LOR: + case LXOR: + case DADD: + case DMUL: + case DSUB: + case DDIV: + case DREM: + popValue(); + popValue(); + break; + case IASTORE: + case FASTORE: + case AASTORE: + case BASTORE: + case CASTORE: + case SASTORE: + case LCMP: // 4 before 1 after + case DCMPL: + case DCMPG: + popValue(); + popValue(); + popValue(); + break; + case LASTORE: + case DASTORE: + popValue(); + popValue(); + popValue(); + popValue(); + break; + case DUP: + pushValue(peekValue()); + break; + case DUP_X1: + stackSize = stackFrame.size(); + stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); + break; + case DUP_X2: + stackSize = stackFrame.size(); + stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1)); + break; + case DUP2: + stackSize = stackFrame.size(); + stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); + stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); + break; + case DUP2_X1: + stackSize = stackFrame.size(); + stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1)); + stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1)); + break; + case DUP2_X2: + stackSize = stackFrame.size(); + stackFrame.add(stackSize - 4, stackFrame.get(stackSize - 1)); + stackFrame.add(stackSize - 4, stackFrame.get(stackSize - 1)); + break; + case SWAP: + stackSize = stackFrame.size(); + stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); + stackFrame.remove(stackSize); + break; + default: + throw new IllegalArgumentException(INVALID_OPCODE + opcode); + } + } else { + switch (opcode) { + case RETURN: + case IRETURN: + case FRETURN: + case ARETURN: + case LRETURN: + case DRETURN: + case ATHROW: + onMethodExit(opcode); + break; + default: + break; + } + } + super.visitInsn(opcode); + } + + @Override + public void visitVarInsn(final int opcode, final int varIndex) { + super.visitVarInsn(opcode, varIndex); + if (isConstructor && !superClassConstructorCalled) { + switch (opcode) { + case ILOAD: + case FLOAD: + pushValue(OTHER); + break; + case LLOAD: + case DLOAD: + pushValue(OTHER); + pushValue(OTHER); + break; + case ALOAD: + pushValue(varIndex == 0 ? UNINITIALIZED_THIS : OTHER); + break; + case ASTORE: + case ISTORE: + case FSTORE: + popValue(); + break; + case LSTORE: + case DSTORE: + popValue(); + popValue(); + break; + case RET: + endConstructorBasicBlockWithoutSuccessor(); + break; + default: + throw new IllegalArgumentException(INVALID_OPCODE + opcode); + } + } + } + + @Override + public void visitFieldInsn( + final int opcode, final String owner, final String name, final String descriptor) { + super.visitFieldInsn(opcode, owner, name, descriptor); + if (isConstructor && !superClassConstructorCalled) { + char firstDescriptorChar = descriptor.charAt(0); + boolean longOrDouble = firstDescriptorChar == 'J' || firstDescriptorChar == 'D'; + switch (opcode) { + case GETSTATIC: + pushValue(OTHER); + if (longOrDouble) { + pushValue(OTHER); + } + break; + case PUTSTATIC: + popValue(); + if (longOrDouble) { + popValue(); + } + break; + case PUTFIELD: + popValue(); + popValue(); + if (longOrDouble) { + popValue(); + } + break; + case GETFIELD: + if (longOrDouble) { + pushValue(OTHER); + } + break; + default: + throw new IllegalArgumentException(INVALID_OPCODE + opcode); + } + } + } + + @Override + public void visitIntInsn(final int opcode, final int operand) { + super.visitIntInsn(opcode, operand); + if (isConstructor && !superClassConstructorCalled && opcode != NEWARRAY) { + pushValue(OTHER); + } + } + + @Override + public void visitLdcInsn(final Object value) { + super.visitLdcInsn(value); + if (isConstructor && !superClassConstructorCalled) { + pushValue(OTHER); + if (value instanceof Double + || value instanceof Long + || (value instanceof ConstantDynamic && ((ConstantDynamic) value).getSize() == 2)) { + pushValue(OTHER); + } + } + } + + @Override + public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { + super.visitMultiANewArrayInsn(descriptor, numDimensions); + if (isConstructor && !superClassConstructorCalled) { + for (int i = 0; i < numDimensions; i++) { + popValue(); + } + pushValue(OTHER); + } + } + + @Override + public void visitTypeInsn(final int opcode, final String type) { + super.visitTypeInsn(opcode, type); + // ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack. + if (isConstructor && !superClassConstructorCalled && opcode == NEW) { + pushValue(OTHER); + } + } + + @Override + public void visitMethodInsn( + final int opcodeAndSource, + final String owner, + final String name, + final String descriptor, + final boolean isInterface) { + if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) { + // Redirect the call to the deprecated version of this method. + super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface); + return; + } + super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface); + int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK; + + doVisitMethodInsn(opcode, name, descriptor); + } + + private void doVisitMethodInsn(final int opcode, final String name, final String descriptor) { + if (isConstructor && !superClassConstructorCalled) { + for (Type argumentType : Type.getArgumentTypes(descriptor)) { + popValue(); + if (argumentType.getSize() == 2) { + popValue(); + } + } + switch (opcode) { + case INVOKEINTERFACE: + case INVOKEVIRTUAL: + popValue(); + break; + case INVOKESPECIAL: + Object value = popValue(); + if (value == UNINITIALIZED_THIS + && !superClassConstructorCalled + && name.equals("")) { + superClassConstructorCalled = true; + onMethodEnter(); + } + break; + default: + break; + } + + Type returnType = Type.getReturnType(descriptor); + if (returnType != Type.VOID_TYPE) { + pushValue(OTHER); + if (returnType.getSize() == 2) { + pushValue(OTHER); + } + } + } + } + + @Override + public void visitInvokeDynamicInsn( + final String name, + final String descriptor, + final Handle bootstrapMethodHandle, + final Object... bootstrapMethodArguments) { + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + doVisitMethodInsn(Opcodes.INVOKEDYNAMIC, name, descriptor); + } + + @Override + public void visitJumpInsn(final int opcode, final Label label) { + super.visitJumpInsn(opcode, label); + if (isConstructor && !superClassConstructorCalled) { + switch (opcode) { + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case IFNULL: + case IFNONNULL: + popValue(); + break; + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + popValue(); + popValue(); + break; + case JSR: + pushValue(OTHER); + break; + case GOTO: + endConstructorBasicBlockWithoutSuccessor(); + break; + default: + break; + } + addForwardJump(label); + } + } + + @Override + public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { + super.visitLookupSwitchInsn(dflt, keys, labels); + if (isConstructor && !superClassConstructorCalled) { + popValue(); + addForwardJumps(dflt, labels); + endConstructorBasicBlockWithoutSuccessor(); + } + } + + @Override + public void visitTableSwitchInsn( + final int min, final int max, final Label dflt, final Label... labels) { + super.visitTableSwitchInsn(min, max, dflt, labels); + if (isConstructor && !superClassConstructorCalled) { + popValue(); + addForwardJumps(dflt, labels); + endConstructorBasicBlockWithoutSuccessor(); + } + } + + @Override + public void visitTryCatchBlock( + final Label start, final Label end, final Label handler, final String type) { + super.visitTryCatchBlock(start, end, handler, type); + // By definition of 'forwardJumpStackFrames', 'handler' should be pushed only if there is an + // instruction between 'start' and 'end' at which the super class constructor is not yet + // called. Unfortunately, try catch blocks must be visited before their labels, so we have no + // way to know this at this point. Instead, we suppose that the super class constructor has not + // been called at the start of *any* exception handler. If this is wrong, normally there should + // not be a second super class constructor call in the exception handler (an object can't be + // initialized twice), so this is not issue (in the sense that there is no risk to emit a wrong + // 'onMethodEnter'). + if (isConstructor && !forwardJumpStackFrames.containsKey(handler)) { + List handlerStackFrame = new ArrayList<>(); + handlerStackFrame.add(OTHER); + forwardJumpStackFrames.put(handler, handlerStackFrame); + } + } + + private void addForwardJumps(final Label dflt, final Label[] labels) { + addForwardJump(dflt); + for (Label label : labels) { + addForwardJump(label); + } + } + + private void addForwardJump(final Label label) { + if (forwardJumpStackFrames.containsKey(label)) { + return; + } + forwardJumpStackFrames.put(label, new ArrayList<>(stackFrame)); + } + + private void endConstructorBasicBlockWithoutSuccessor() { + // The next instruction is not reachable from this instruction. If it is dead code, we + // should not try to simulate stack operations, and there is no need to insert advices + // here. If it is reachable with a backward jump, the only possible case is that the super + // class constructor has already been called (backward jumps are forbidden before it is + // called). If it is reachable with a forward jump, there are two sub-cases. Either the + // super class constructor has already been called when reaching the next instruction, or + // it has not been called. But in this case there must be a forwardJumpStackFrames entry + // for a Label designating the next instruction, and superClassConstructorCalled will be + // reset to false there. We can therefore always reset this field to true here. + superClassConstructorCalled = true; + } + + private Object popValue() { + return stackFrame.remove(stackFrame.size() - 1); + } + + private Object peekValue() { + return stackFrame.get(stackFrame.size() - 1); + } + + private void pushValue(final Object value) { + stackFrame.add(value); + } + + /** + * Generates the "before" advice for the visited method. The default implementation of this method + * does nothing. Subclasses can use or change all the local variables, but should not change state + * of the stack. This method is called at the beginning of the method or after super class + * constructor has been called (in constructors). + */ + protected void onMethodEnter() {} + + /** + * Generates the "after" advice for the visited method. The default implementation of this method + * does nothing. Subclasses can use or change all the local variables, but should not change state + * of the stack. This method is called at the end of the method, just before return and athrow + * instructions. The top element on the stack contains the return value or the exception instance. + * For example: + * + *
+      * public void onMethodExit(final int opcode) {
+      *   if (opcode == RETURN) {
+      *     visitInsn(ACONST_NULL);
+      *   } else if (opcode == ARETURN || opcode == ATHROW) {
+      *     dup();
+      *   } else {
+      *     if (opcode == LRETURN || opcode == DRETURN) {
+      *       dup2();
+      *     } else {
+      *       dup();
+      *     }
+      *     box(Type.getReturnType(this.methodDesc));
+      *   }
+      *   visitIntInsn(SIPUSH, opcode);
+      *   visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V");
+      * }
+      *
+      * // An actual call back method.
+      * public static void onExit(final Object exitValue, final int opcode) {
+      *   ...
+      * }
+      * 
+ * + * @param opcode one of {@link Opcodes#RETURN}, {@link Opcodes#IRETURN}, {@link Opcodes#FRETURN}, + * {@link Opcodes#ARETURN}, {@link Opcodes#LRETURN}, {@link Opcodes#DRETURN} or {@link + * Opcodes#ATHROW}. + */ + protected void onMethodExit(final int opcode) {} +} diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AnalyzerAdapter.class b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AnalyzerAdapter.class new file mode 100644 index 00000000..9e834397 Binary files /dev/null and b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AnalyzerAdapter.class differ diff --git a/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AnalyzerAdapter.java b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AnalyzerAdapter.java new file mode 100644 index 00000000..36a5a06d --- /dev/null +++ b/tests/test_data/std/jdk/internal/org/objectweb/asm/commons/AnalyzerAdapter.java @@ -0,0 +1,942 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file: + * + * ASM: a very small and fast Java bytecode manipulation framework + * Copyright (c) 2000-2011 INRIA, France Telecom + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jdk.internal.org.objectweb.asm.commons; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import jdk.internal.org.objectweb.asm.ConstantDynamic; +import jdk.internal.org.objectweb.asm.Handle; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.Opcodes; +import jdk.internal.org.objectweb.asm.Type; + +/** + * A {@link MethodVisitor} that keeps track of stack map frame changes between {@link + * #visitFrame(int, int, Object[], int, Object[])} calls. This adapter must be used with the {@link + * jdk.internal.org.objectweb.asm.ClassReader#EXPAND_FRAMES} option. Each visitX instruction delegates to + * the next visitor in the chain, if any, and then simulates the effect of this instruction on the + * stack map frame, represented by {@link #locals} and {@link #stack}. The next visitor in the chain + * can get the state of the stack map frame before each instruction by reading the value of + * these fields in its visitX methods (this requires a reference to the AnalyzerAdapter that + * is before it in the chain). If this adapter is used with a class that does not contain stack map + * table attributes (i.e., pre Java 6 classes) then this adapter may not be able to compute the + * stack map frame for each instruction. In this case no exception is thrown but the {@link #locals} + * and {@link #stack} fields will be null for these instructions. + * + * @author Eric Bruneton + */ +public class AnalyzerAdapter extends MethodVisitor { + + /** + * The local variable slots for the current execution frame. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or {@link Opcodes#UNINITIALIZED_THIS} (long and + * double are represented by two elements, the second one being TOP). Reference types are + * represented by String objects (representing internal names, see {@link + * Type#getInternalName()}), and uninitialized types by Label objects (this label designates the + * NEW instruction that created this uninitialized value). This field is {@literal null} for + * unreachable instructions. + */ + public List locals; + + /** + * The operand stack slots for the current execution frame. Primitive types are represented by + * {@link Opcodes#TOP}, {@link Opcodes#INTEGER}, {@link Opcodes#FLOAT}, {@link Opcodes#LONG}, + * {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or {@link Opcodes#UNINITIALIZED_THIS} (long and + * double are represented by two elements, the second one being TOP). Reference types are + * represented by String objects (representing internal names, see {@link + * Type#getInternalName()}), and uninitialized types by Label objects (this label designates the + * NEW instruction that created this uninitialized value). This field is {@literal null} for + * unreachable instructions. + */ + public List stack; + + /** The labels that designate the next instruction to be visited. May be {@literal null}. */ + private List