diff --git a/actors.ts b/actors.ts index a1471b14..b0438d23 100644 --- a/actors.ts +++ b/actors.ts @@ -448,29 +448,20 @@ module J2ME { * are stored for this class. */ getClassObject(): java.lang.Class { - return getRuntimeKlass($, this.klass).classObject; + return $.getRuntimeKlass(this.klass).classObject; } /** * Object that holds static properties for this class. */ getStaticObject(ctx: Context): java.lang.Object { - return getRuntimeKlass(ctx.runtime, this.klass); + return ctx.runtime.getRuntimeKlass(this.klass); } getField(fieldKey: string) : FieldInfo { return CLASSES.getField(this, fieldKey); } - getClassInitLockObject(ctx: Context) { - if (!(this.className in ctx.runtime.classInitLockObjects)) { - ctx.runtime.classInitLockObjects[this.className] = { - classInfo: this - }; - } - return ctx.runtime.classInitLockObjects[this.className]; - } - toString() { return "[class " + this.className + "]"; } diff --git a/aot-methods.txt b/aot-methods.txt index e1ea69d3..29ddfd0f 100644 --- a/aot-methods.txt +++ b/aot-methods.txt @@ -320,6 +320,7 @@ com/sun/midp/lcdui/DisplayDevice.isPrimaryDisplay.()Z com/sun/midp/main/MIDletProxy.getClassName.()Ljava/lang/String; com/sun/midp/rms/RmsEnvironment.getSecureFilenameBase.(I)Ljava/lang/String; com/sun/midp/midletsuite/SuiteContainerAdapter.getSecureFilenameBase.(I)Ljava/lang/String; +java/lang/Class.initialize.()V com/nokia/mid/ui/gestures/GestureEventImpl.getType.()I com/nokia/mid/ui/gestures/GestureEventImpl.getDragDistanceX.()I com/nokia/mid/ui/gestures/GestureEventImpl.getDragDistanceY.()I diff --git a/bindings.ts b/bindings.ts index 22aa95ae..00674039 100644 --- a/bindings.ts +++ b/bindings.ts @@ -11,6 +11,18 @@ module J2ME { } } }, + "java/lang/Class": { + fields: { + instanceSymbols: { + "status.I": "status" + } + }, + methods: { + instanceSymbols: { + "initialize.()V": "initialize" + } + } + }, "java/lang/String": { fields: { instanceSymbols: { @@ -195,6 +207,8 @@ module J2ME { * RuntimeKlass associated with this Class object. */ runtimeKlass: RuntimeKlass; + status: number; + initialize(): void; } export interface String extends java.lang.Object { diff --git a/interpreter.ts b/interpreter.ts index 5a29803b..0da70a47 100644 --- a/interpreter.ts +++ b/interpreter.ts @@ -26,6 +26,14 @@ module J2ME { traceWriter.writeLn(toDebugString(array) + "[" + index + "] (" + toDebugString(array[index]) + ")"); } + function classInitAndUnwindCheck(classInfo: ClassInfo, pc: number) { + classInitCheck(classInfo); + if (U) { + $.ctx.current().pc = pc; + return; + } + } + /** * Optimize method bytecode. */ @@ -937,7 +945,7 @@ module J2ME { case Bytecodes.ANEWARRAY: index = frame.read16(); classInfo = resolveClass(index, mi.classInfo, false); - classInitCheck(classInfo, frame.pc - 3); + classInitAndUnwindCheck(classInfo, frame.pc - 3); size = stack.pop(); stack.push(newArray(classInfo.klass, size)); break; @@ -992,7 +1000,7 @@ module J2ME { case Bytecodes.GETSTATIC: index = frame.read16(); fieldInfo = resolveField(index, mi.classInfo, true); - classInitCheck(fieldInfo.classInfo, frame.pc - 3); + classInitAndUnwindCheck(fieldInfo.classInfo, frame.pc - 3); if (U) { return; } @@ -1002,7 +1010,7 @@ module J2ME { case Bytecodes.PUTSTATIC: index = frame.read16(); fieldInfo = resolveField(index, mi.classInfo, true); - classInitCheck(fieldInfo.classInfo, frame.pc - 3); + classInitAndUnwindCheck(fieldInfo.classInfo, frame.pc - 3); if (U) { return; } @@ -1011,7 +1019,7 @@ module J2ME { case Bytecodes.NEW: index = frame.read16(); classInfo = resolveClass(index, mi.classInfo, false); - classInitCheck(classInfo, frame.pc - 3); + classInitAndUnwindCheck(classInfo, frame.pc - 3); if (U) { return; } @@ -1140,7 +1148,7 @@ module J2ME { } if (isStatic) { - classInitCheck(calleeMethodInfo.classInfo, lastPC); + classInitAndUnwindCheck(calleeMethodInfo.classInfo, lastPC); if (U) { return; } diff --git a/java/custom/com/sun/cldc/isolate/Isolate.java b/java/custom/com/sun/cldc/isolate/Isolate.java new file mode 100644 index 00000000..4c58dfa0 --- /dev/null +++ b/java/custom/com/sun/cldc/isolate/Isolate.java @@ -0,0 +1,1114 @@ +/* + * + * + * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 only, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License version 2 for more details (a copy is + * included at /legal/license.txt). + * + * You should have received a copy of the GNU General Public License + * version 2 along with this work; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa + * Clara, CA 95054 or visit www.sun.com if you need additional + * information or have any questions. + */ + +package com.sun.cldc.isolate; +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; +import java.io.IOException; +import com.sun.cldchi.jvm.JVM; + +/** + *
+ *
+ * Last modified: 05/03/31 13:22:49.

+ * Note: this document is still a draft. Details in the API are + * subject to change.

+ *


+ *
+ * The Isolate class provides the means of creating and managing + * isolated computations and arranging for their communication with each + * other. + *

+ *

Terminology

+ * + * Each isolated computation is called a Task. An Isolate + * object is a Java representation of the task. Multiple + * Isolate objects may be created to represent the same task. Where + * the context is clear, the words "task", "isolate" and "isolate + * object" may be used interchangeably. When a distinction needs to be + * made, the word "task" is used to describe the underlying + * computation, and the word "isolate" is used to describe the Java + * object(s) that represent the computation.

+ * + * When two Isolate objects represent the same task, they are said to + * be equivalent to each other. Equivalent Isolate objects are + * created to avoid the sharing of Isolate objects across task + * boundaries. For example, in the following program, task A launches + * task B using the object b1, and task B gets a + * reference to itself using the object + * b2. b1 and b2 are two + * distinct objects:

+ * + *

+ *class TaskA {
+ *    void launchB() {
+ *        Isolate b1 = new Isolate("TaskB", ....);
+ *        b1.start();
+ *    }
+ *}
+ *class TaskB {
+ *    public static void main(String args[]) {
+ *        Isolate b2 = Isolate.currentIsolate();
+ *    }
+ *}
+ * + *

Degree of Isolation

+ * + * Tasks in the CLDC environment are isolated in the following sense: + *
    + *
  • Each task has a separate namespace for loading Java classes. + *
  • Each task has a separate set of static variables for Java classes + *
  • A synchronized static method uses a different + * monitor object inside each task. + *
  • Typically no Java objects are shared across task boundaries. + * (see Object Sharing below for + * exceptions). + *
  • Resource quotas are controlled by the runtime environment to + * prevent tasks to use excessive amount of resources. The + * implementation currently supports resource control for + * memory or CPU cycles. + *
+ * + *

Class path

+ * + *

Part of the definition of an isolate is its + * classpath, where the basic classes for the isolate are found. + * The CLDC runtime searches for classes in three sets of + * locations: + *

    + *
  • The romized system classes. + *
  • The isolate system class path. + *
  • The isolate application class path. + *
+ * + *

System and application class paths are specified separately + * for an isolate when it is created. Classes on system and application class + * paths have different access rights: + *

    + *
  • Only classes loaded from the system class path can access + * hidden classes. + *
  • Only classes on the system class path can be loaded to restricted + * packages. + *
+ * For the definition of hidden and restricted packages, see + * doc/misc/Romizer.html. + * + *

When an isolate requests a class, the class is first looked up the + * romized system classes, then searched in the isolate system class path, + * and then searched in the isolate application class path. + * + *

User application classes should be put on the application class path, + * system class path can contain only trusted system classes. + * + *

WARNING: UNTRUSTED USER APPLICATION CLASSES MUST NEVER BE PUT ON THE + * SYSTEM CLASS PATH, AS IT GRANTS THEM ACCESS TO SYSTEM INTERNALS AND BREAKS + * SYSTEM SECURITY. + * + *

Object Sharing

+ * + * The Isolate API in CLDC does not support the sharing of arbitrary + * Java object across Isolate boundaries. The only exception is String + * objects: String objects may be passed as arguments from a parent + * isolate to a child isolate's main() method. Such + * Strings are passed by reference instead of by value in order to + * conserve resource. Also, interned Strings (such as literal Strings + * that appear inside Java source code) may be shared across Isolate + * boundaries.

+ * + * Even though String objects may be shared across isolates, different + * isolates should not attempt to coordinate their activities by + * synchronizing on these Strings. Specifically, an interned Strings cannot + * be synchronized across isolate boundaries because it uses a different + * monitor object in each Isolate.

+ * + *

Inter-Isolate Communication

+ * + * Isolates may need to communicate with each to coordinate their + * activities. The recommended method of inter-isolate communication + * is a native event queue. On many CLDC/MIDP environments a native + * event queue already exists. Such event queues can be extended for + * one isolate to send events to another isolate.

+ * + * Some CLDC/MIDP implementors may be tempted to use native code to + * pass shared objects from one isolate to another, and use + * traditional Java object-level synchronization to perform + * inter-isolate communication. In our experience this could easily + * lead to inter-isolate deadlock that could be exploited by + * downloaded malicious Midlets. For example, if a shared object is + * used to synchronize screen painting, a malicious Midlet may stop + * other Midlets from painting by not returning from its + * paint() method.

+ * + * In our experience, with shared objects, it would take significant + * effort to design a system that can prevent such attacks. In + * contrast, event queues are much more easily understood to design + * a safe environment. Thus, we strongly recommend against using + * shared objects for inter-isolate communication.

+ * + * The following code is an example of how an isolate can create other + * isolates: + * + *

import com.sun.cldc.isolate.*;
+ *
+ *class HelloWorld {
+ * 
+ *    // Usage: cldc_vm -classpath  HelloWorld HelloWorld2 
+ *    public static void main(String [] argv) {
+ *        System.out.println("HelloWorld");
+ *        for (int i = 0; i < 6; i++) {
+ *            try {
+ *                // pass i as an argument to the isolate just for fun 
+ *               String[] isoArgs = {Integer.toString(i)};
+ *                Isolate iso = new Isolate(argv[0], isoArgs);
+ *                iso.start();
+ *             } catch (Exception e) {
+ *                System.out.println("caught exception " + e);
+ *                e.printStackTrace();
+ *            }
+ *            System.out.println("HelloWorld: Iso " + i + " started.");
+ *
+ *        }
+ *    }
+ *}
+ *
+ *
+ *class HelloWorld2 {
+ *    static String st = "HelloWorld2[";
+ *
+ *    public static void main(String [] argv) {
+ *        st = st.concat(argv[0]);
+ *        st = st.concat("]");
+ *        System.out.println("st is " + st);
+ *        System.exit(42);
+ *    }
+ *} 
+ * + * @see javax.isolate.IsolateStartupException + **/ +public final class Isolate { + /** + * Controls access to each public API entry point. The + * isolate creator can grant API access to the child. + * Note this is a static so it is private to each Isolate + */ + private static int _API_access_ok = 1; + + /** + * Priority level of this Isolate that was set using setPriority before + * the isolate has started. + */ + private int _priority; + + /** + * Links to the next Isolate in a task's _seen_isolates list. See + * Task.cpp for more information. + */ + private Isolate _next; + + /** + * A number that uniquely identifies the task represented by this + * Isolate object. + */ + private long _uniqueId; + + /** + * Called by native code when the task corresponding to this Isolate + * has terminated + */ + private int _terminated; + + /** + * If this isolate has terminated, this variable saves the exit code -- + * Normally the exitCode() method would retrieve the exit code from + * the Task. _saved_exit_code is used only if this Isolate object + * has been dis-associated from the Task (i.e., the Task has + * exited). + */ + private int _saved_exit_code; + + /** + * Saves the mainClass parameter passed to Isolate() constructor. + */ + private String _mainClass; + + /** + * Saves the mainArgs parameter passed to Isolate() constructor. + */ + private String[] _mainArgs; + + /** + * Saves app_classpath[] parameter passed to Isolate() constructor. + */ + private String[] _app_classpath; + + /** + * Saves sys_classpath[] parameter passed to Isolate() constructor. + */ + private String[] _sys_classpath; + + /** + * Packages we want to be hidden in this Isolate. See definition of hidden package in + * doc/misc/Romizer.html + */ + private String[] _hidden_packages; + /** + * Packages we want to be restricted in this Isolate. See definition of restricted package in + * doc/misc/Romizer.html + */ + private String[] _restricted_packages; + + /** + * Amount of memory reserved for this isolate + * The isolate cannot get OutOfMemory exception until it + * allocates at least memoryReserve bytes. + * If the system cannot reserve the requested amount, + * the isolate will not start. + */ + private int _memoryReserve = 0; + + /** + * Memory allocation limit for this isolate + * OutOfMemoryError exception is thrown if + * - the isolate exceeds its memory allocation limit + * - excess over the reserved amount for this isolate + * cannot be allocated in the heap or + * conflicts with reserves of other isolates + */ + private int _memoryLimit = Integer.MAX_VALUE; + + /** + * Used during bootstrap of new Isolate to store API access value + */ + private int _APIAccess = 0; + + + private int _ConnectDebugger = 0; + + + private int _UseVerifier = 1; + + private int _profileId = DEFAULT_PROFILE_ID; + + private int _UseProfiler = 1; + + /** + * ID of default profile. + */ + private final static int DEFAULT_PROFILE_ID = -1; + /** + * A special priority level to indicate that an Isolate is suspended. + */ + private final static int SUSPEND = 0; + + /** + * The minimum priority that an Isolate can have. + */ + public final static int MIN_PRIORITY = 1; + + /** + * The default priority that is assigned to an Isolate. + */ + public final static int NORM_PRIORITY = 2; + + /** + * The maximum priority that an Isolate can have. + */ + public final static int MAX_PRIORITY = 3; + + /** + * Creates a new isolated java application with a default configuration. + * + *

This constructor has the same effect as invoking + * {@link #Isolate(String,String[],String[])} + * and passing null for the app_classpath + * and sys_classpath parameters. + * See the long constructor documentation for more details. + * + * @param mainClass fully qualified name of the main method class + * @param mainArgs the arguments of the main method in the new isolate + * @throws IsolateStartupException if an error occurs in the configuration + * or startup of the new isolate before any application code is invoked + **/ + public Isolate(String mainClass, String[] mainArgs) + throws IsolateStartupException { + this(mainClass, mainArgs, (String[])null); + } + + /** + * Creates a new isolated java application with a default configuration. + * + *

This constructor has the same effect as invoking + * {@link #Isolate(String,String[],String[], String[])} + * and passing null for the sys_classpath + * parameter. + * See the long constructor documentation for more details. + * + * @param mainClass fully qualified name of the main method class + * @param mainArgs the arguments of the main method in the new isolate + * @param app_classpath the application classpath(s) for the isolate + * (see Class path) + * @throws IsolateStartupException if an error occurs in the configuration + * or startup of the new isolate before any application code is invoked + **/ + public Isolate(String mainClass, String[] mainArgs, String[] app_classpath) + throws IsolateStartupException { + this(mainClass, mainArgs, app_classpath, (String[])null); + } + + + /** + * Creates a new Isolate with the specified arguments and + * classpath.

+ * + * The new isolate will execute the main method of + * class mainClass with arguments + * mainArgs. The mainClass parameter + * must reference a class present in the romized system classes, + * or in one of the classpath elements specified by the + * sys_classpath and app_classpath parameters. + * + *

When the constructor returns, the new isolate is not yet + * running. The new isolate does not start execution until the + * {@link #start start} method is invoked. The {@link #halt halt} + * and {@link #exit exit} methods will fail if before + * {@link #start start} is invoked. + * + *

Class resolution and loading are performed in the new task + * represented by this new isolate. Any loading exceptions (such as + * ClassNotFoundException), including the loading + * exception of the specified mainClass, will occur + * inside the new task when it is started, not the creator task. + * + *

Changes made to any of the constructor's parameters after + * control returns from this constructor will have no effect on the + * newly created isolate. + * + *

If mainArgs is null, a zero-length + * String array will be provided to the main method + * of mainClass. + * + *

User application classes should be put on app_classpath, + * while sys_classpath can contain only trusted system + * classes. + * + *

WARNING: UNTRUSTED USER APPLICATION CLASSES MUST NEVER BE PUT ON THE + * SYSTEM CLASS PATH, AS IT GRANTS THEM ACCESS TO SYSTEM INTERNALS AND + * BREAKS SYSTEM SECURITY. + * + * @param mainClass fully qualified name of the main method class + * @param mainArgs the arguments of the main method in the new isolate + * @param app_classpath the application classpath(s) for the isolate + * @param sys_classpath the system classpath(s) for the isolate + * (see Class path) + * @throws IsolateStartupException if an error occurs in the configuration + * of the new isolate before any application code is invoked + * @throws IllegalArgumentException if any parameters are found to be + * invalid. + **/ + public Isolate(String mainClass, String[] mainArgs, + String[] app_classpath, String[] sys_classpath) + throws IsolateStartupException + { + securityCheck(); + if (mainClass == null) { + throw new IllegalArgumentException("specified class name is null"); + } + registerNewIsolate(); + _priority = NORM_PRIORITY; + _mainClass = mainClass; + _mainArgs = argCopy(mainArgs); + _app_classpath = argCopy(app_classpath); + _sys_classpath = argCopy(sys_classpath); + + /* + *

WARNING: DO NOT REMOVE THIS MESSAGE UNLESS YOU HAVE READ AND + * UNDERSTOOD THE SECURITY IMPLICATIONS: HAVING UNTRUSTED USER + * APPLICATION CLASSES ON THE SYSTEM CLASS PATH GRANTS THEM ACCESS TO + * SYSTEM INTERNALS AND BREAKS SYSTEM SECURITY. + */ + if (_sys_classpath.length != 0) { + System.err.println(); + System.err.println("****warning****"); + System.err.println("****Untrusted user classes must never be put"); + System.err.println("****on the system class path"); + System.err.println("****warning****"); + System.err.println(); + } + } + + /** + * Start execution of this Isolate. Any code that belongs + * to this Isolate (including static initializers) + * is executed only after this method is called. + *

+ * Control will return from this method when the new isolate's + * first user level thread starts executing, or if an error occurs + * during the initialization of the new isolate. + * + *

If any exception is thrown by this method, no code in the + * Isolate will have executed. + * + *

Errors such as the main class being invalid or not visible in + * the classpath will occur handled within the new isolate. + * + * @throws IsolateStartupException if an error occurs in the + * initialization or configuration of the new isolate + * before any application code is invoked, or if this + * Isolate was already started or is terminated. + * @throws IsolateResourceError if systems exceeds maximum Isolate count + * @throws OutOfMemoryError if the reserved memory cannot be allocated + */ + public synchronized void start() throws IsolateStartupException + { + if (getStatus() > NEW) { + throw new IsolateStartupException("Isolate has already started"); + } + + try { + nativeStart(); + } catch (IsolateResourceError e) { + throw e; + } catch (OutOfMemoryError e) { + throw e; + } catch (Throwable t) { + // To be somewhat compilant to JSR-121, we do not pass any + // other errors back to the caller of start(). Instead, + // the caller can use Isolate.exitCode() to discover that + // the isolate has exited. See CR 6270554. + } + + // Wait till the fate of the started isolate is known + // (STARTED or STOPPED...) + while (getStatus() <= NEW) { + try { + // Note: do NOT use wait(). See comments inside waitForExit(). + waitStatus(NEW); + } catch (InterruptedException e) { + throw new IsolateStartupException( + "Exception was thrown while Isolate was starting"); + } + } + } + + /** + * Requests normal termination of this Isolate. + * Invocation of this method is equivalent to causing the isolate + * to invoke {@link java.lang.Runtime#exit(int)}. If this method + * invocation is, in fact, the cause of the isolate's termination, + * the status supplied will be the isolate's + * termination status. + * + *

No exception is thrown if this isolate is already + * terminated. Even if {@link #isTerminated()} returns false prior + * to invoking exit, an invocation of exit may + * occur after the isolate exits on its own or is terminated by + * another isolate. In these cases, the actual exit code reported by + * the isolate may be different from status. + * + *

If this isolate is not yet started, it will be marked as + * already terminated. A subsequent invocation to {@link #start()} would + * result in an IsolateStartupException. + * + *

If this isolate is suspended, it will be terminated without + * being resumed. + * + * @param status Termination status. By convention, a nonzero status + * code indicates abnormal termination. + **/ + public void exit(int status) { + try { + stop(status, this == currentIsolate() ? + EXIT_REASON_SELF_EXIT : + EXIT_REASON_OTHER_EXIT); + } catch (SecurityException se) { + stop(status, EXIT_REASON_SELF_EXIT); + } + } + + /** + * Forces termination of this Isolate. + * + * If this method invocation is in fact the cause of the isolate's + * termination, the status supplied will be the + * isolate's termination status. + * + *

No exception is thrown if this isolate is already + * terminated. Even if {@link #isTerminated()} returns false prior + * to invoking halt, an invocation of halt may + * occur after the isolate exits on its own or is terminated by + * another isolate. In these cases, the actual exit code reported by + * the isolate may be different from status. + * + *

If this isolate is not yet started, it will be marked as + * already terminated. A subsequent invocation to {@link #start()} would + * result in an IsolateStartupException. + * + *

If this isolate is suspended, it will be terminated without + * being resumed. + * + *

Implementation Note

+ * + * Implementations should strive to implement "quick" termination + * with as little coordination with the target isolate as possible. + * The only information required of a terminated isolate is the exit + * code it was terminated with. + * + * @param status Termination status. By convention, a nonzero status code + * indicates abnormal termination. + **/ + public void halt(int status) { + try { + stop(status, this == currentIsolate() ? + EXIT_REASON_SELF_HALT : + EXIT_REASON_OTHER_HALT); + } catch (SecurityException se) { + stop(status, EXIT_REASON_SELF_HALT); + } + } + + /** + * Returns true if this Isolate is terminated. + */ + public boolean isTerminated() { + int state = getStatus(); + return (state >= STOPPED); + } + + /** + * Returns the Isolate object corresponding to the currently executing + * task. + * + *

This method never returns null. + * + * @return the Isolate object for the current task + **/ + public static Isolate currentIsolate() { + securityCheck(); + return currentIsolate0(); + } + + private native static Isolate currentIsolate0(); + + /** + * Returns an array of Isolate objects representing + * all tasks that have been started but have not terminated. + * New tasks may have been constructed or existing ones + * terminated by the time this method returns. + * + * @return the active Isolate objects at the time + * of the call + **/ + public static Isolate[] getIsolates() { + securityCheck(); + return getIsolates0(); + } + private native static Isolate[] getIsolates0(); + + + /////////////////////////////////////////////////////////////////////////// + // Valid state transitions are: + // NEW -> { STARTED, STOPPED } + // STARTED -> { STOPPING, STOPPED } + // STOPPING -> { STOPPED } + // { STOPPED } : final states. + // { NEW} : initial states. + // + // + // Note: only Isolate created by the current isolate can be in the NEW + // state. + // Hence, knowing if an isolate is started only consists of testing if + // its state is > NEW. + + static final int INVALID_TASK_ID = -1; // invalid task id. + + static final int NEW = 1; // created by the current isolate + static final int STARTED = 2; // start() method has been called. + static final int STOPPING = 3; // isolate is stopping -- + // see IsolateEvent.STOPPING + static final int STOPPED = 4; // isolate was terminated -- + // see IsolateEvent.TERMINATED + + /** + * Returns a small integer ID that uniquely identifies this + * Isolate among the current set of active Isolates. The returned + * ID will remain unchanged and reserved for this Isolate during its + * entire lifetime. However, after this Isolate is terminated, the ID may + * be resumed for a new Isolate. + * + * @return -1 if the task has not been started or it has been terminated, + * + */ + public int id() { + return id0(); + } + private native int id0(); + + /** + * Returns a 64-bit ID that uniquely identifies this Isolate. + * The ID is assigned when the Isolate is created and will remain + * unchanged and reserved for this Isolate during the entire + * lifetime of the VM. + */ + public long uniqueId() { + return _uniqueId; + } + + /** + * @return the amount of object heap memory reserved for this Isolate. + */ + public int reservedMemory() { + return _memoryReserve; + } + + /** + * @return the maximum amount of object heap memory that can be + * allocated by this Isolate. + */ + public int totalMemory() { + return _memoryLimit; + } + + /** + * This function returns the approximate amount of object heap + * memory currently used by this Isolate. The approximate value + * may not be accurate: it may not include recent allocations + * made by the Isolate, and it may count objects allocated by the + * Isolate that have since become unreachable.

+ * + * @return the approximate amount of object heap memory currently + * used by this Isolate. + */ + public int usedMemory() { + return usedMemory0(); + } + private native int usedMemory0(); + + /** + * Sets the object heap memory reserved and maximum limits to the + * same value. Note that if the system does not have sufficient + * resources to guaranteed the reserved amount, the start() method + * of this Isolate would fail. This method should only be called + * before the Isolate is started. Calling it after the isolate + * has started will cause undetermined behavior.

+ * + * @param reserved The minimum amount of memory guaranteed to be + * available to the isolate at any time. Also the total + * amount of memory that the isolate can reserve. + */ + public void setMemoryQuota(int reserved) { + setMemoryQuota(reserved, reserved); + } + + /** + * Sets the object heap memory quota for this Isolate. Note that + * if the system does not have sufficient resources to guaranteed + * the reserved amount, the start() method of this Isolate would + * fail. + * This method should only be called before the Isolate is + * started. Calling it after the isolate has started will cause + * undetermined behavior.

+ * + * @param reserved The minimum amount of memory guaranteed to be + * available to the isolate at any time. + * @param total The total amount of memory that the isolate can + * reserve. + */ + public void setMemoryQuota(int reserved, int total) { + if (reserved < 0 || reserved > total) { + throw new IllegalArgumentException(); + } + _memoryReserve = reserved; + _memoryLimit = total; + } + + /* Return true if isolate has been started. + */ + synchronized boolean isStarted() { + return getStatus() <= NEW; + } + + private String[] argCopy(String[] args) { + if (args == null) { + return new String[0]; + } + String[] result = new String[args.length]; + JVM.unchecked_obj_arraycopy(args, 0, result, 0, args.length); + return result; + } + + /** + * Add this Isolate to the TaskDesc::_seen_isolates list of the + * current task and return the globally unique isolate identifier. + */ + private native void registerNewIsolate(); + + /** + * Stopping execution of an Isolate. Used by implementation of exit + * and halt. + * + *

If this isolate is not yet started, it will be marked as + * already terminated. A subsequent invocation to {@link #start()} would + * result in an IsolateStartupException. + * + *

If this isolate is suspended, it will be terminated without + * being resumed. + */ + private native void stop(int exit_code, int exit_reason); + + /** + * Adjust the priority of this Isolate. The priority controls the + * amount of CPU time that VM allocates to execute threads in this + * Isolate. + * + * Note: thread scheduling and task scheduling use separate mechanisms. + * In the current imeplentation, each task is guaranteed execution time + * relative to its priority. + * + * + * @param new_priority must be between MIN_PRIORITY + * and MAX_PRIORITY, or else this method call will + * have no effect. + */ + public void setPriority(int new_priority) { + if (new_priority >= MIN_PRIORITY && new_priority <= MAX_PRIORITY) { + _priority = new_priority; + setPriority0(new_priority); + } + } + + private native void setPriority0(int new_priority); + + /** + * Returns the priority of this isolate. + * + * @return the priority of this isolate. If the isolate has already + * terminated, the returned value is undefined. + */ + public int getPriority() { + return _priority; + } + + /** + * Returns if this isolate has been suspended. + * @return true iff the isolate has been suspended. + */ + public boolean isSuspended() { + return (isSuspended0() != 0 ? true : false); + } + + private native int isSuspended0(); + + /** + * Suspends all threads in this isolate from execution. This + * method should be used carefully if objects shared between isolates + * (passed via native methods) are used for synchornization. A + * suspended isolate holding a lock on such an object will stop other + * tasks from ever receiving that lock. + * See introduction for better ways of communicating between isolates. + * + * This method will suspend the isolate only if the isolate is currently + * started, not suspended and not terminated. Otherwise this method + * has no effect. + */ + public void suspend() { + suspend0(); + } + + private native void suspend0(); + + /** + * The opposite of the suspend method. + * + * This method will resume the isolate only if the isolate is + * currently started, suspended and not terminated. Otherwise this + * method has no effect. + */ + public void resume() { + resume0(); + } + + private native void resume0(); + + /** + * Returns the exit code of the isolate. If this Isolate has terminated, + * this method returns the exit code parameter to the first invocation of + * System.exit(), Isolate.exit() or Isolate.halt() that caused the Isolate + * to terminate. If this Isolate has terminated without calling + * System.exit(), Isolate.exit() or Isolate.halt(), then 0 is returned. + * + * If this Isolate has not started or has not terminated, 0 is returned. + * + * @return the exit code of the isolate. + */ + public int exitCode() { + return exitCode0(); + } + + private native int exitCode0(); + + /** + * Blocks the execution of the calling thread until this Isolate + * has exited. If waitForExit() is called on the + * current Isolate, the result is undefined. + * + * @throws InterruptedException (unimplemented yet): if CLDC + * Specification 1.1 is enabled, when a thread is blocked + * inside this method, it may be interrupted by an + * invocation of Thread.interrupt, in which case an + * InterruptedException is thrown regardless of the + * termination status of this Isolate. + */ + public synchronized void waitForExit() /* throws InterruptedException */ { + while (getStatus() <= STOPPING) { + try { + // Note: do NOT use wait(): When notifyStatus() is + // called, the calling thread may not hold the monitor + // of this object, so if we wrote the code like this + // we may get into a race condition + // while (getStatus() <= STOPPING) { + // + // wait(); + // } + // waitStatus() performs the getStatus() <= STOPPING check in + // native code again, where thread switch is guaranteed to + // not happen. Hence we won't have a race condition. + waitStatus(STOPPED); + } catch (InterruptedException e) { + // IMPL_NOTE: this method should throw InterruptedException! + throw new Error(); + } + } + } + + /** + * Returns the classpath the Isolate was started with. + * + * @return String[] that is equal to classpath argument passed to + * Isolate constructor + */ + public String[] getClassPath() { + return argCopy(_app_classpath); + } + + /** + * Determine if this isolate has permission to access the API + * If not, throw runtime exception + */ + private static void securityCheck() { + if (_API_access_ok == 0) { + throw new SecurityException("Access to Isolate API not allowed"); + } + } + + /** + * Sets the access to Isolate API for this Isolate. This method + * should be used by the AMS, before the Isolate is started, to + * control whether or not a created Isolate is able to call the + * Isolate API. The default for all but the first Isolate is + * false. If the AMS calls this method after the Isolate + * has started, it has no effect.

+ * + * In additional, after an Isolate has started, if it has access + * to the Isolate API, it can call this method to disable + * it. However, once it loses the access, attempts to call this + * method would result in a SecurityException. + */ + public void setAPIAccess(boolean access) { + _APIAccess = (access == true ? 1 : 0); + + // Only allow access to be degraded after starting. + if (!access && equals(currentIsolate())) { + _API_access_ok = 0; + } + } + + public void setDebug(boolean mode) { + _ConnectDebugger = (mode == true ? 1 : 0); + } + + public void attachDebugger() { + securityCheck(); + attachDebugger0(this); + } + + /** + * Indicates if debugger connection is established with the VM. + * + * @return true if debugger is connected, otherwise returns false. + */ + public native boolean isDebuggerConnected(); + + /** + * Controls whether or not classes for this isolate need to be + * verified. When creating a new Isolate, the AMS may waive + * verification for classes that have already been verified. The + * default is false. This method should be called + * before the Isolate is started. + */ + public void setUseVerifier(boolean verify) { + _UseVerifier = (verify == true ? 1 : 0); + } + + /** + * Returns the current status of the task represented by this Isolate. + * + * @return one of NEW, STARTED, STOPPING or STOPPED + */ + private native int getStatus(); + + /** + * Notify all threads that are waiting on the status of any Isolate object + * that represent the same task as this Isolate object. + * + * To simplify VM design, this method does NOT need to be called + * while holding a lock of such Isolate objects. To avert race conditions, + * the waiting threads must be blocked using waitStatus() instead + * of wait(). See comments inside waitForExit() for details. + */ + private native void notifyStatus(); + + /** + * Blocks the current thread until getStatus() would return a value + * greater than maxStatus, or (CLDC Spec 1.1 only) until this + * thread is interrupted.

+ * + * See comments inside waitForExit() to see why this method method + * must be used instead of wait() to avert race conditions. + */ + private native void waitStatus(int maxStatus) throws InterruptedException; + + /* For now, ignore the links argument. + * Native method will use the JNI invocation API to start a new in-process + * JVM to execute the new Isolate. + */ + private native void nativeStart() throws IsolateStartupException; + + /** + * The last non-daemon thread returned from main. + */ + private static final int EXIT_REASON_IMPLICIT_EXIT = 1; + + /** + * The last non-daemon thread exited due to an uncaught exception. + * + *

Note that if a daemon thread dies with an uncaught exception, + * that will not cause the containing isolate to die. Additionally, + * only if the last non-daemon thread dies with + * an uncaught exception will this reason be noted. Uncaught exceptions + * in shutdown hooks do not count, either. + */ + private static final int EXIT_REASON_UNCAUGHT_EXCEPT = 6; + + /** + * The isolate invoked {@link System#exit System.exit}, + * {@link Runtime#exit Runtime.exit}, or + * {@link Isolate#exit Isolate.exit} on itself. + */ + private static final int EXIT_REASON_SELF_EXIT = 2; + + /** + * The isolate invoked + * {@link Runtime#halt Runtime.halt} or + * {@link Isolate#halt Isolate.halt} on itself. + */ + private static final int EXIT_REASON_SELF_HALT = 3; + + /** + * Some other isolate invoked {@link Isolate#exit Isolate.exit} + * on the isolate. + */ + private static final int EXIT_REASON_OTHER_EXIT = 4; + + /** + * Some other isolate invoked {@link Isolate#halt Isolate.halt} + * on the isolate. + */ + private static final int EXIT_REASON_OTHER_HALT = 5; + + /** + * Sets active profile name for isolate. This method must be + * called before the isolate is started. + * + * If isolate is already started the method throws an + * IllegalIsolateStateException. + * + * The method also determines if profile + * is a name of existing profile which is defined in ROM + * configuration file. If not, throws runtime + * IllegalArgumentException. + * + * @param profile The new active profile name. + */ + public native void setProfile(String profile) throws + IllegalArgumentException, + IllegalIsolateStateException; + + /** + * Sets the packages which will be hidden. See definition of hidden package in + * doc/misc/Romizer.html. Note, that this function call overrides previous settings. + * + * If isolate is already started the method throws an + * IllegalIsolateStateException. + * + * @param package_name. The name of package for marking. + */ + public void setHiddenPackages(String[] package_names) throws + IllegalIsolateStateException { + if (getStatus() > NEW) { + throw new IllegalIsolateStateException("Can only set hidden packages before Isolate starts"); + } + _hidden_packages = package_names; + } + + /** + * Sets the packages which will be restricted. See definition of restricted package in + * doc/misc/Romizer.html. Note, that this function call overrides previous settings. + * + * If isolate is already started the method throws an + * IllegalIsolateStateException. + * + * @param package_name The name of package for marking. + */ + public void setRestrictedPackages(String[] package_names) throws + IllegalIsolateStateException { + if (getStatus() > NEW) { + throw new IllegalIsolateStateException("Can only set restricted packages before Isolate starts"); + } + _restricted_packages = package_names; + } + + /** + * Sets whether isolate should be profiled or not. By default all isolates are profiled. + * Should be set before task for isolate is created. + * + */ + public void setUseProfiler(boolean useProfiler) { + _UseProfiler = (useProfiler == true ? 1 : 0); + } + + private native void attachDebugger0(Isolate obj); +} diff --git a/java/custom/java/lang/Class.java b/java/custom/java/lang/Class.java new file mode 100644 index 00000000..bb936e71 --- /dev/null +++ b/java/custom/java/lang/Class.java @@ -0,0 +1,425 @@ +/* + * + * + * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 only, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License version 2 for more details (a copy is + * included at /legal/license.txt). + * + * You should have received a copy of the GNU General Public License + * version 2 along with this work; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa + * Clara, CA 95054 or visit www.sun.com if you need additional + * information or have any questions. + */ + +package java.lang; + +/** + * Instances of the class Class represent classes and interfaces + * in a running Java application. Every array also belongs to a class that is + * reflected as a Class object that is shared by all arrays with + * the same element type and number of dimensions. + * + *

Class has no public constructor. Instead Class + * objects are constructed automatically by the Java Virtual Machine as classes + * are loaded. + * + *

The following example uses a Class object to print the + * class name of an object: + * + *

+ *     void printClassName(Object obj) {
+ *         System.out.println("The class of " + obj +
+ *                            " is " + obj.getClass().getName());
+ *     }
+ * 
+ * + * @version 12/17/01 (CLDC 1.1) + * @since JDK1.0, CLDC 1.0 + */ +public final +class Class { + + /* + * Constructor. Only the Java Virtual Machine creates Class + * objects. + */ + private Class() {} + + /** + * Converts the object to a string. The string representation is the + * string "class" or "interface", followed by a space, and then by the + * fully qualified name of the class in the format returned by + * getName. If this Class object represents a + * primitive type, this method returns the name of the primitive type. If + * this Class object represents void this method returns + * "void". + * + * @return a string representation of this class object. + */ + public String toString() { + return (isInterface() ? "interface " : "class ") + getName(); + } + + /** + * Returns the Class object associated with the class + * with the given string name. Given the fully-qualified name for + * a class or interface, this method attempts to locate, load and + * link the class. + *

+ * For example, the following code fragment returns the runtime + * Class descriptor for the class named + * java.lang.Thread: + *

    + * Class t = Class.forName("java.lang.Thread") + *
+ * + * @param className the fully qualified name of the desired class. + * @return the Class object for the class with the + * specified name. + * @exception ClassNotFoundException if the class could not be found. + * @exception Error if the function fails for any other reason. + * @since JDK1.0 + */ + public static Class forName(String className) throws ClassNotFoundException { + // forName is broken into two parts since forName0 may unwind. + forName0(className); + return forName1(className); + } + + private static native void forName0(String className) throws ClassNotFoundException; + private static native Class forName1(String className); + + /** + * Creates a new instance of a class. + * + * @return a newly allocated instance of the class represented by this + * object. This is done exactly as if by a new + * expression with an empty argument list. + * @exception IllegalAccessException if the class or initializer is + * not accessible. + * @exception InstantiationException if an application tries to + * instantiate an abstract class or an interface, or if the + * instantiation fails for some other reason. + * @since JDK1.0 + */ + public native Object newInstance() + throws InstantiationException, IllegalAccessException; + + /** + * Determines if the specified Object is assignment-compatible + * with the object represented by this Class. This method is + * the dynamic equivalent of the Java language instanceof + * operator. The method returns true if the specified + * Object argument is non-null and can be cast to the + * reference type represented by this Class object without + * raising a ClassCastException. It returns false + * otherwise. + * + *

Specifically, if this Class object represents a + * declared class, this method returns true if the specified + * Object argument is an instance of the represented class (or + * of any of its subclasses); it returns false otherwise. If + * this Class object represents an array class, this method + * returns true if the specified Object argument + * can be converted to an object of the array class by an identity + * conversion or by a widening reference conversion; it returns + * false otherwise. If this Class object + * represents an interface, this method returns true if the + * class or any superclass of the specified Object argument + * implements this interface; it returns false otherwise. If + * this Class object represents a primitive type, this method + * returns false. + * + * @param obj the object to check + * @return true if obj is an instance of this class + * + * @since JDK1.1 + */ + public native boolean isInstance(Object obj); + + /** + * Determines if the class or interface represented by this + * Class object is either the same as, or is a superclass or + * superinterface of, the class or interface represented by the specified + * Class parameter. It returns true if so; + * otherwise it returns false. If this Class + * object represents a primitive type, this method returns + * true if the specified Class parameter is + * exactly this Class object; otherwise it returns + * false. + * + *

Specifically, this method tests whether the type represented by the + * specified Class parameter can be converted to the type + * represented by this Class object via an identity conversion + * or via a widening reference conversion. See The Java Language + * Specification, sections 5.1.1 and 5.1.4 , for details. + * + * @param cls the Class object to be checked + * @return the boolean value indicating whether objects of the + * type cls can be assigned to objects of this class + * @exception NullPointerException if the specified Class parameter is + * null. + * @since JDK1.1 + */ + public native boolean isAssignableFrom(Class cls); + + /** + * Determines if the specified Class object represents an + * interface type. + * + * @return true if this object represents an interface; + * false otherwise. + */ + public native boolean isInterface(); + + /** + * Determines if this Class object represents an array class. + * + * @return true if this object represents an array class; + * false otherwise. + * @since JDK1.1 + */ + public native boolean isArray(); + + /** + * Returns the fully-qualified name of the entity (class, interface, array + * class, primitive type, or void) represented by this Class + * object, as a String. + * + *

If this Class object represents a class of arrays, then + * the internal form of the name consists of the name of the element type + * in Java signature format, preceded by one or more "[" + * characters representing the depth of array nesting. Thus: + * + *

+     * (new Object[3]).getClass().getName()
+     * 
+ * + * returns "[Ljava.lang.Object;" and: + * + *
+     * (new int[3][4][5][6][7][8][9]).getClass().getName()
+     * 
+ * + * returns "[[[[[[[I". The encoding of element type names + * is as follows: + * + *
+     * B            byte
+     * C            char
+     * D            double
+     * F            float
+     * I            int
+     * J            long
+     * Lclassname;  class or interface
+     * S            short
+     * Z            boolean
+     * 
+ * + * The class or interface name classname is given in fully + * qualified form as shown in the example above. + * + * @return the fully qualified name of the class or interface + * represented by this object. + */ + public native String getName(); + + /** + * Finds a resource with a given name in the application's + * JAR file. This method returns + * null if no resource with this name is found + * in the application's JAR file. + *

+ * The resource names can be represented in two + * different formats: absolute or relative. + *

+ * Absolute format: + *

    /packagePathName/resourceName
+ *

+ * Relative format: + *

    resourceName
+ *

+ * In the absolute format, the programmer provides a fully + * qualified name that includes both the full path and the + * name of the resource inside the JAR file. In the path names, + * the character "/" is used as the separator. + *

+ * In the relative format, the programmer provides only + * the name of the actual resource. Relative names are + * converted to absolute names by the system by prepending + * the resource name with the fully qualified package name + * of class upon which the getResourceAsStream + * method was called. + * + * @param name name of the desired resource + * @return a java.io.InputStream object. + */ + public java.io.InputStream getResourceAsStream(String name) { + try { + if (name.length() > 0 && name.charAt(0) == '/') { + /* Absolute format */ + name = name.substring(1); + } else { + /* Relative format */ + String className = this.getName(); + int dotIndex = className.lastIndexOf('.'); + if (dotIndex >= 0) { + name = className.substring(0, dotIndex + 1).replace('.', '/') + + name; + } + } + return new com.sun.cldc.io.ResourceInputStream(name); + } catch (java.io.IOException x) { + return null; + } + } + + /* + * This private function is used during virtual machine initialization. + * The user does not normally see this function. + */ +// private static void runCustomCode() {} + + /* The code below is specific to this VM */ + + /** + * Returns the Class representing the superclass of the entity + * (class, interface, primitive type or void) represented by this + * Class. If this Class represents either the + * Object class, an interface, a primitive type, or void, then + * null is returned. If this object represents an array class then the + * Class object representing the Object class is + * returned. + * + * Note that this method is not supported by CLDC. + * We have made the method private, since it is + * needed by our implementation. + * + * @return the superclass of the class represented by this object. + */ + private native Class getSuperclass(); + + /* + * This private variable is used by the VM. + * Users never see it. + */ + private transient Object vmClass; + + private int status; + private Thread thread; + + private static final int IN_PROGRESS = 1; + private static final int VERIFIED = 2; + private static final int INITIALIZED = 4; + private static final int ERROR = 8; + + // Native for invoking + private native void invoke_clinit(); + + /** + * Initialization at step 9: + * If ENABLE_ISOLATES == false + * Remove the method after the class is initialized. + * If ENABLE_ISOLATES == true, clear class initialization + * barrier. + */ + private native void init9(); + + private native void invoke_verify(); + + /* + * Implements the 11 step program detailed in Java Language Specification + * 12.4.2 + */ + void initialize() throws Throwable { + // Step 1 + synchronized (this) { + // Step 2 + while ((status & IN_PROGRESS) != 0 && thread != Thread.currentThread()) { + try{ + wait(); + } catch (InterruptedException e) { + } + } + + // Step 3 + if ((status & IN_PROGRESS) != 0 && thread == Thread.currentThread()) { + return; + } + + // Step 4 + if ((status & INITIALIZED) != 0) { + return; + } + + // Step 5 + if (status == ERROR) { + throw new NoClassDefFoundError(getName()); + } + /* Note: CLDC 1.0 does not have NoClassDefFoundError class */ + + // Step 6 + status |= IN_PROGRESS; + thread = Thread.currentThread(); + } + + try { + // Step 7 + invoke_verify(); + Class s = getSuperclass(); + if (s != null && (s.status & INITIALIZED) == 0) { + // The test of s.status is not part of the spec, but + // it saves us doing a lot of work in the most common + // case. + s.initialize(); + } + + // Step 8 + invoke_clinit(); + + // Step 9 + synchronized (this) { + status &= ~IN_PROGRESS; + status |= INITIALIZED; + thread = null; + init9(); + notifyAll(); + } + } catch(Throwable e) { + // Step 10 and 11 + // CR 6224346, The cldc_vm threading mechanism is such that + // we can just jam these values in without fear of another + // thread doing the same since only this thread can be + // executing the initialize() method and the scheduler is + // non-preemptive. We do this here in case the monitorenter + // fails due to OOME because some other thread holds the lock, + // memory is low and we need to allocate a ConditionDesc to + // wait for the lock. + status = ERROR; + thread = null; + synchronized (this) { + notifyAll(); + throwError(e); + } + } + } + + private Error throwError(Throwable e) throws Error { + throw (e instanceof Error) ? (Error)e + : new Error("Static initializer: " + e.getClass().getName() + + ", " + e.getMessage()); + } +} diff --git a/jit/analyze.ts b/jit/analyze.ts index 50048c5e..1be307ae 100644 --- a/jit/analyze.ts +++ b/jit/analyze.ts @@ -63,6 +63,7 @@ module J2ME { "java/lang/Class.newInstance.()Ljava/lang/Object;": YieldReason.Root, "java/lang/Thread.yield.()V": YieldReason.Root, "java/lang/Thread.start0.()V": YieldReason.Root, + "java/lang/Class.forName0.(Ljava/lang/String;)V": YieldReason.Root, // Test Files: "gnu/testlet/vm/NativeTest.throwExceptionAfterPause.()V": YieldReason.Root, "gnu/testlet/vm/NativeTest.returnAfterPause.()I": YieldReason.Root, @@ -192,6 +193,22 @@ module J2ME { } + export function canStaticInitializerYield(classInfo: ClassInfo): YieldReason { + var result = YieldReason.None; + while (classInfo) { + var staticInitializer = classInfo.staticInitializer; + classInfo = classInfo.superClass; + if (!staticInitializer) { + continue; + } + result = canYield(staticInitializer); + if (result !== YieldReason.None) { + return result; + } + } + return result; + } + export function canYield(methodInfo: MethodInfo): YieldReason { yieldWriter && yieldWriter.enter("> " + methodInfo.implKey); if (yieldMap[methodInfo.implKey] !== undefined) { @@ -221,6 +238,14 @@ module J2ME { while (stream.currentBCI < methodInfo.code.length) { var op: Bytecodes = stream.currentBC(); switch (op) { + case Bytecodes.NEW: + case Bytecodes.GETSTATIC: + case Bytecodes.PUTSTATIC: + var cpi = stream.readCPI(); + var fieldInfo = methodInfo.classInfo.resolve(cpi, true); + var classInfo = fieldInfo.classInfo; + result = canStaticInitializerYield(classInfo); + break; case Bytecodes.MONITORENTER: case Bytecodes.MONITOREXIT: result = YieldReason.MonitorEnterExit; @@ -246,6 +271,13 @@ module J2ME { } } + if (op === Bytecodes.INVOKESTATIC) { + result = canStaticInitializerYield(methodInfo.classInfo); + if (result !== YieldReason.None) { + break; + } + } + if (!isStaticallyBound(op, callee)) { var callees = []; result = YieldReason.Virtual; diff --git a/jit/baseline.ts b/jit/baseline.ts index 7f3dcc1d..d9946da9 100644 --- a/jit/baseline.ts +++ b/jit/baseline.ts @@ -671,12 +671,18 @@ module J2ME { } emitGetField(fieldInfo: FieldInfo, isStatic: boolean) { + if (isStatic) { + this.emitClassInitializationCheck(fieldInfo.classInfo); + } var signature = TypeDescriptor.makeTypeDescriptor(fieldInfo.signature); var object = isStatic ? this.runtimeClass(fieldInfo.classInfo) : this.pop(Kind.Reference); this.emitPush(signature.kind, object + "." + fieldInfo.mangledName); } emitPutField(fieldInfo: FieldInfo, isStatic: boolean) { + if (isStatic) { + this.emitClassInitializationCheck(fieldInfo.classInfo); + } var signature = TypeDescriptor.makeTypeDescriptor(fieldInfo.signature); var value = this.pop(signature.kind); var object = isStatic ? this.runtimeClass(fieldInfo.classInfo) : this.pop(Kind.Reference); @@ -740,14 +746,14 @@ module J2ME { var message = "Optimized ClassInitializationCheck: " + classInfo.className + ", self access."; emitDebugInfoComments && this.blockEmitter.writeLn("// " + message); baselineCounter && baselineCounter.count(message); - } else if (this.methodInfo.classInfo.isAssignableTo(classInfo)) { + } else if (!classInfo.isInterface && this.methodInfo.classInfo.isAssignableTo(classInfo)) { var message = "Optimized ClassInitializationCheck: " + classInfo.className + ", base access."; emitDebugInfoComments && this.blockEmitter.writeLn("// " + message); baselineCounter && baselineCounter.count(message); } else { baselineCounter && baselineCounter.count("ClassInitializationCheck: " + classInfo.className); - this.blockEmitter.writeLn(this.runtimeClass(classInfo) + ";"); - if (classInfo.staticInitializer && canYield(classInfo.staticInitializer)) { + this.blockEmitter.writeLn("if ($.initialized[\"" + classInfo.className + "\"] === undefined) { " + this.runtimeClassObject(classInfo) + ".initialize(); }"); + if (canStaticInitializerYield(classInfo)) { this.emitUnwind(this.blockEmitter, String(this.pc), String(this.pc)); } else { emitCompilerAssertions && this.emitNoUnwindAssertion(); @@ -800,6 +806,7 @@ module J2ME { if (calleeCanYield) { this.emitUnwind(this.blockEmitter, String(this.pc), String(nextPC)); } else { + emitCompilerAssertions && this.emitUndefinedReturnAssertion(); emitCompilerAssertions && this.emitNoUnwindAssertion(); } if (types[0].kind !== Kind.Void) { @@ -947,6 +954,10 @@ module J2ME { this.blockEmitter.writeLn("if (U) { J2ME.Debug.assert(false, 'Unexpected unwind.'); }"); } + emitUndefinedReturnAssertion() { + this.blockEmitter.writeLn("if (U && re !== undefined) { J2ME.Debug.assert(false, 'Unexpected return value during unwind.'); }"); + } + private emitMonitorEnter(emitter: Emitter, nextPC: number, object: string) { this.hasMonitorEnter = true; diff --git a/native.js b/native.js index 421c9fb6..c25943ab 100644 --- a/native.js +++ b/native.js @@ -260,7 +260,7 @@ Native["com/sun/cldchi/jvm/JVM.monotonicTimeMillis.()J"] = function() { }; Native["java/lang/Object.getClass.()Ljava/lang/Class;"] = function() { - return J2ME.getRuntimeKlass($.ctx.runtime, this.klass).classObject; + return $.getRuntimeKlass(this.klass).classObject; }; Native["java/lang/Object.wait.(J)V"] = function(timeout) { @@ -275,65 +275,56 @@ Native["java/lang/Object.notifyAll.()V"] = function() { $.ctx.notify(this, true); }; -Native["java/lang/Class.invoke_clinit.()V"] = function() { - var classInfo = this.classInfo; - var className = classInfo.className; - var runtime = $.ctx.runtime; - if (runtime.initialized[className] || runtime.pending[className]) - return; - runtime.pending[className] = true; - if (className === "com/sun/cldc/isolate/Isolate") { - // The very first isolate is granted access to the isolate API. - var isolate = classInfo.getStaticObject($.ctx); - CLASSES.getField(classInfo, "S._API_access_ok.I").set(isolate, 1); +Native["java/lang/Class.getSuperclass.()Ljava/lang/Class;"] = function() { + var superKlass = this.runtimeKlass.templateKlass.superKlass; + if (!superKlass) { + return null; } - var clinit = CLASSES.getMethod(classInfo, "S..()V"); + return superKlass.classInfo.getClassObject(); +}; - var frames = []; +Native["java/lang/Class.invoke_clinit.()V"] = function() { + var classInfo = this.runtimeKlass.templateKlass.classInfo; + var className = classInfo.className; + var clinit = CLASSES.getMethod(classInfo, "S..()V"); if (clinit && clinit.classInfo.className === className) { - frames.push(Frame.create(clinit, [], 0)); - } - if (classInfo.superClass) { - var classInitFrame = $.ctx.getClassInitFrame(classInfo.superClass); - if (classInitFrame) { - frames.push(classInitFrame); - } - } - if (frames.length) { - $.ctx.executeFrames(frames); + $.ctx.executeFrames([Frame.create(clinit, [], 0)]); } }; +Native["java/lang/Class.invoke_verify.()V"] = function() { + // There is currently no verification. +}; + Native["java/lang/Class.init9.()V"] = function() { - var classInfo = this.classInfo; - var className = classInfo.className; - var runtime = $.ctx.runtime; - if (runtime.initialized[className]) - return; - runtime.pending[className] = false; - runtime.initialized[className] = true; + $.setClassInitialized(this.runtimeKlass); }; Native["java/lang/Class.getName.()Ljava/lang/String;"] = function() { return J2ME.newString(this.runtimeKlass.templateKlass.classInfo.className.replace(/\//g, ".")); }; -Native["java/lang/Class.forName.(Ljava/lang/String;)Ljava/lang/Class;"] = function(name) { - try { - if (!name) - throw new J2ME.ClassNotFoundException(); - var className = util.fromJavaString(name).replace(/\./g, "/"); - var classInfo = null; - classInfo = CLASSES.getClass(className); - } catch (e) { - if (e instanceof (J2ME.ClassNotFoundException)) - throw $.newClassNotFoundException("'" + e.message + "' not found."); - throw e; - } - J2ME.linkKlass(classInfo); - var classObject = classInfo.getClassObject(); - J2ME.Debug.assert(!U, "Unwinding isn't currently supported here."); - return classObject; +Native["java/lang/Class.forName0.(Ljava/lang/String;)V"] = function(name) { + var classInfo = null; + try { + if (!name) + throw new J2ME.ClassNotFoundException(); + var className = util.fromJavaString(name).replace(/\./g, "/"); + classInfo = CLASSES.getClass(className); + } catch (e) { + if (e instanceof (J2ME.ClassNotFoundException)) + throw $.newClassNotFoundException("'" + e.message + "' not found."); + throw e; + } + // The following can trigger an unwind. + J2ME.classInitCheck(classInfo); +}; + +Native["java/lang/Class.forName1.(Ljava/lang/String;)Ljava/lang/Class;"] = function(name) { + var className = util.fromJavaString(name).replace(/\./g, "/"); + var classInfo = CLASSES.getClass(className); + var classObject = classInfo.getClassObject(); + return classObject; }; Native["java/lang/Class.newInstance.()Ljava/lang/Object;"] = function() { @@ -564,7 +555,7 @@ Native["java/lang/Thread.start0.()V"] = function() { ]) }); - newCtx.start(new Frame(syntheticMethod, [ this ], 0)); + newCtx.start([new Frame(syntheticMethod, [ this ], 0)]); }; Native["java/lang/Thread.internalExit.()V"] = function() { diff --git a/tests/automation.js b/tests/automation.js index e4bf5018..1dc02194 100644 --- a/tests/automation.js +++ b/tests/automation.js @@ -162,29 +162,29 @@ casper.test.begin("unit tests", 19 + gfxTests.length, function(test) { casper.waitForText("DONE", function() { test.assertTextExists("I m\n" + "I a ma\n" + - "I 2\n" + + "I 3\n" + "I ma\n" + - "I 2\n" + + "I 3\n" + "I 1 isolate\n" + "I Isolate ID correct\n" + - "I 4\n" + "I 5\n" + + "I 6\n" + "I 1 isolate\n" + "I ma\n" + "I ma\n" + "I 3 isolates\n" + "I 1 m1\n" + - "I 4\n" + - "I 2 m2\n" + "I 5\n" + + "I 2 m2\n" + + "I 6\n" + "I ma\n" + "I 1 isolate\n" + "I Isolates terminated\n" + "I r mar\n" + - "I 2\n" + + "I 3\n" + "I mar\n" + "I c marc\n" + - "I 2\n" + + "I 3\n" + "I marc\n" + "I Main isolate still running"); }); diff --git a/tests/java/lang/TestStaticInitRaceCondition.java b/tests/java/lang/TestStaticInitRaceCondition.java new file mode 100644 index 00000000..5155a266 --- /dev/null +++ b/tests/java/lang/TestStaticInitRaceCondition.java @@ -0,0 +1,38 @@ +package java.lang; + +import gnu.testlet.Testlet; +import gnu.testlet.TestHarness; + +class Monkey { + public static final String name; + + static { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + } + name = "Monkey"; + } +} + +public class TestStaticInitRaceCondition extends Thread implements Testlet { + private TestHarness th; + public int getExpectedPass() { return 2; } + public int getExpectedFail() { return 0; } + public int getExpectedKnownFail() { return 0; } + + public void test(TestHarness th) { + this.th = th; + start(); + run(); + try { + this.join(); + } catch (InterruptedException e) { + th.fail(); + } + } + + public void run() { + th.check(Monkey.name, "Monkey"); + } +} diff --git a/vm/classRegistry.ts b/vm/classRegistry.ts index 1fcadbd9..b8f86190 100644 --- a/vm/classRegistry.ts +++ b/vm/classRegistry.ts @@ -58,6 +58,7 @@ module J2ME { * because they don't have any static state. */ var classNames = [ + "com/sun/cldc/isolate/Isolate", // Not used frequently, but needs setup before we start the isolates. "java/lang/Integer", "java/lang/Character", "java/lang/Math", diff --git a/vm/context.ts b/vm/context.ts index c66377be..b60f26ef 100644 --- a/vm/context.ts +++ b/vm/context.ts @@ -439,85 +439,14 @@ module J2ME { return returnValue; } - getClassInitFrame(classInfo: ClassInfo) { - if (this.runtime.initialized[classInfo.className]) { - return; - } - classInfo.thread = this.thread; - var syntheticMethod = new MethodInfo({ - name: "ClassInitSynthetic", - signature: "()V", - isStatic: false, - classInfo: ClassInfo.createFromObject({ - className: {value: classInfo.className}, - vmc: {value: {}}, - vfc: {value: {}}, - constant_pool: {value: [ - null, - {tag: TAGS.CONSTANT_Methodref, class_index: 2, name_and_type_index: 4}, - {tag: TAGS.CONSTANT_Class, name_index: 3}, - {bytes: "java/lang/Class"}, - {name_index: 5, signature_index: 6}, - {bytes: "invoke_clinit"}, - {bytes: "()V"}, - {tag: TAGS.CONSTANT_Methodref, class_index: 2, name_and_type_index: 8}, - {name_index: 9, signature_index: 10}, - {bytes: "init9"}, - {bytes: "()V"}, - ]}, - }), - code: new Uint8Array([ - 0x2a, // aload_0 - 0x59, // dup - 0x59, // dup - 0x59, // dup - 0xc2, // monitorenter - 0xb7, 0x00, 0x01, // invokespecial - 0xb7, 0x00, 0x07, // invokespecial - 0xc3, // monitorexit - 0xb1, // return - ]) - }); - return Frame.create(syntheticMethod, [classInfo.getClassInitLockObject(this)], 0); - } - - pushClassInitFrame(classInfo: ClassInfo) { - if (this.runtime.initialized[classInfo.className] || - this.runtime.pending[classInfo.className]) { - return; - } - var needsInitialization = true; - if (!classInfo.staticInitializer) { - needsInitialization = false; - // Special case Isolate. - if (classInfo.className === "com/sun/cldc/isolate/Isolate") { - needsInitialization = true; - } - var superClass = classInfo.superClass; - while (superClass) { - if (!this.runtime.initialized[superClass.className] && - superClass.staticInitializer) { - needsInitialization = true; - break; - } - superClass = superClass.superClass; - } - } - linkKlass(classInfo); - if (!needsInitialization) { - this.runtime.initialized[classInfo.className] = true; - return; - } - var classInitFrame = this.getClassInitFrame(classInfo); - this.executeFrames([classInitFrame]); - } - createException(className: string, message?: string) { if (!message) { message = ""; } message = "" + message; var classInfo = CLASSES.loadAndLinkClass(className); + classInitCheck(classInfo); + release || Debug.assert(!U, "Unexpected unwind during createException."); runtimeCounter && runtimeCounter.count("createException " + className); var exception = new classInfo.klass(); var methodInfo = CLASSES.getMethod(classInfo, "I..(Ljava/lang/String;)V"); @@ -547,8 +476,9 @@ module J2ME { Context.setWriters(Context.writer); } - start(frame: Frame) { - this.frames = [Frame.Start, frame]; + start(frames: Frame[]) { + frames.unshift(Frame.Start); + this.frames = frames; this.resume(); } diff --git a/vm/jvm.ts b/vm/jvm.ts index ecab526e..7362e9c7 100644 --- a/vm/jvm.ts +++ b/vm/jvm.ts @@ -8,42 +8,49 @@ module J2ME { constructor() { // ... } - - startIsolate0(className: string, args: string []) { + + private createIsolateCtx(): Context { var runtime = new Runtime(this); var ctx = new Context(runtime); - ctx.setAsCurrentContext(); + ctx.thread = runtime.mainThread = newObject(CLASSES.java_lang_Thread.klass); + ctx.thread.pid = util.id(); + ctx.thread.alive = true; + // The constructor will set the real priority, however one is needed for the scheduler. + ctx.thread.priority = NORMAL_PRIORITY; + runtime.preInitializeClasses(ctx); + return ctx; + } + + startIsolate0(className: string, args: string []) { + var ctx = this.createIsolateCtx(); var isolateClassInfo = CLASSES.getClass("com/sun/cldc/isolate/Isolate"); - - linkKlass(isolateClassInfo); var isolate: Isolate = newObject(isolateClassInfo.klass); - isolate.id = util.id(); var array = newStringArray(args.length); for (var n = 0; n < args.length; ++n) array[n] = args[n] ? J2ME.newString(args[n]) : null; - ctx.executeFrames([ + // The frames go at the end of the array so they are executed first to initialize the thread and isolate. + ctx.start([ + Frame.create(CLASSES.getMethod(isolateClassInfo, "I.start.()V"), [ isolate ], 0), Frame.create(CLASSES.getMethod(isolateClassInfo, "I..(Ljava/lang/String;[Ljava/lang/String;)V"), - [ isolate, J2ME.newString(className.replace(/\./g, "/")), array ], 0) + [ isolate, J2ME.newString(className.replace(/\./g, "/")), array ], 0) ]); - - ctx.start(Frame.create(CLASSES.getMethod(isolateClassInfo, "I.start.()V"), [ isolate ], 0)); + release || Debug.assert(!U, "Unexpected unwind during isolate initialization."); } startIsolate(isolate: Isolate) { - var mainClass = util.fromJavaString(isolate.klass.classInfo.getField("I._mainClass.Ljava/lang/String;").get(isolate)).replace(/\./g, "/"); - var mainArgs = isolate.klass.classInfo.getField("I._mainArgs.[Ljava/lang/String;").get(isolate); - var runtime = new J2ME.Runtime(this); - var ctx = new Context(runtime); - + var ctx = this.createIsolateCtx(); + var runtime = ctx.runtime; isolate.runtime = runtime; runtime.isolate = isolate; runtime.updateStatus(RuntimeStatus.Started); + var mainClass = util.fromJavaString(isolate.klass.classInfo.getField("I._mainClass.Ljava/lang/String;").get(isolate)).replace(/\./g, "/"); + var mainArgs = isolate.klass.classInfo.getField("I._mainArgs.[Ljava/lang/String;").get(isolate); var classInfo = CLASSES.getClass(mainClass); linkKlass(classInfo); if (!classInfo) @@ -53,22 +60,17 @@ module J2ME { if (!entryPoint) throw new Error("Could not find main method in class " + mainClass); - ctx.thread = runtime.mainThread = newObject(CLASSES.java_lang_Thread.klass); - ctx.thread.pid = util.id(); - ctx.thread.alive = true; - - var oldCtx = $.ctx; - ctx.setAsCurrentContext(); - ctx.executeFrames([Frame.create(CLASSES.getMethod(CLASSES.java_lang_Thread, "I..(Ljava/lang/String;)V"), - [ runtime.mainThread, J2ME.newString("main") ], 0)]) - oldCtx.setAsCurrentContext(); - var args = J2ME.newStringArray(mainArgs.length); for (var n = 0; n < mainArgs.length; ++n) { args[n] = mainArgs[n]; } - ctx.start(Frame.create(entryPoint, [ args ], 0)); + ctx.start([ + Frame.create(entryPoint, [ args ], 0), + Frame.create(CLASSES.getMethod(CLASSES.java_lang_Thread, "I..(Ljava/lang/String;)V"), + [ runtime.mainThread, J2ME.newString("main") ], 0) + ]); + release || Debug.assert(!U, "Unexpected unwind during isolate initialization."); } } diff --git a/vm/runtime.ts b/vm/runtime.ts index 6fa33ebb..4a274d17 100644 --- a/vm/runtime.ts +++ b/vm/runtime.ts @@ -441,7 +441,6 @@ module J2ME { pending: any; staticFields: any; classObjects: any; - classInitLockObjects: any; ctx: Context; isolate: com.sun.cldc.isolate.Isolate; @@ -461,10 +460,42 @@ module J2ME { this.staticFields = {}; this.classObjects = {}; this.ctx = null; - this.classInitLockObjects = {}; this._runtimeId = RuntimeTemplate._nextRuntimeId ++; this._nextHashCode = this._runtimeId << 24; } + + preInitializeClasses(ctx: Context) { + var prevCtx = $ ? $.ctx : null; + var preInit = CLASSES.preInitializedClasses; + ctx.setAsCurrentContext(); + for (var i = 0; i < preInit.length; i++) { + var runtimeKlass = this.getRuntimeKlass(preInit[i].klass); + runtimeKlass.classObject.initialize(); + release || Debug.assert(!U, "Unexpected unwind during preInitializeClasses."); + } + ctx.clearCurrentContext(); + if (prevCtx) { + prevCtx.setAsCurrentContext(); + } + } + + /** + * After class intialization is finished the init9 method will invoke this so + * any further initialize calls can be avoided. This isn't set on the first call + * to a class initializer because there can be multiple calls into initialize from + * different threads that need trigger the Class.initialize() code so they block. + */ + setClassInitialized(runtimeKlass: RuntimeKlass) { + var className = runtimeKlass.templateKlass.classInfo.className; + this.initialized[className] = true; + } + + getRuntimeKlass(klass: Klass): RuntimeKlass { + release || assert(!(klass instanceof RuntimeKlass)); + release || assert(klass.classInfo.mangledName); + var runtimeKlass = this[klass.classInfo.mangledName]; + return runtimeKlass; + } /** * Generates a new hash code for the specified |object|. @@ -854,6 +885,15 @@ module J2ME { release || assert(!runtimeKlass.classObject); runtimeKlass.classObject = new Klasses.java.lang.Class(); runtimeKlass.classObject.runtimeKlass = runtimeKlass; + var className = runtimeKlass.templateKlass.classInfo.className; + if (className === "java/lang/Object" || + className === "java/lang/Class" || + className === "java/lang/String" || + className === "java/lang/Thread") { + (runtimeKlass.classObject).status = 4; + $.setClassInitialized(runtimeKlass); + return; + } var fields = runtimeKlass.templateKlass.classInfo.fields; for (var i = 0; i < fields.length; i++) { var field = fields[i]; @@ -892,10 +932,6 @@ module J2ME { Object.defineProperty(this, classInfo.mangledName, { value: runtimeKlass }); - initWriter && initWriter.writeLn("Running Static Constructor: " + classInfo.className); - $.ctx.pushClassInitFrame(classInfo); - release || assert(!U, "Unwinding during static initializer not supported."); - return runtimeKlass; } }); @@ -952,14 +988,6 @@ module J2ME { } } - export function getRuntimeKlass(runtime: Runtime, klass: Klass): RuntimeKlass { - release || assert(!(klass instanceof RuntimeKlass)); - release || assert(klass.classInfo.mangledName); - var runtimeKlass = runtime[klass.classInfo.mangledName]; - // assert(runtimeKlass instanceof RuntimeKlass); - return runtimeKlass; - } - function setKlassSymbol(mangledName: string, klass: Klass) { Object.defineProperty(jsGlobal, mangledName, { value: klass @@ -1340,6 +1368,7 @@ module J2ME { function linkKlassMethods(klass: Klass) { linkWriter && linkWriter.enter("Link Klass Methods: " + klass); var methods = klass.classInfo.methods; + var classBindings = Bindings[klass.classInfo.className]; for (var i = 0; i < methods.length; i++) { var methodInfo = methods[i]; if (methodInfo.isAbstract) { @@ -1389,6 +1418,12 @@ module J2ME { } if (!methodInfo.isStatic) { klass.prototype[methodInfo.mangledName] = fn; + if (classBindings && classBindings.methods && classBindings.methods.instanceSymbols) { + var methodKey = classBindings.methods.instanceSymbols[methodInfo.name + "." + methodInfo.signature]; + if (methodKey) { + klass.prototype[methodKey] = fn; + } + } } } @@ -1843,15 +1878,13 @@ module J2ME { return e; } - export function classInitCheck(classInfo: ClassInfo, pc: number) { - if (classInfo.isArrayClass) { - return; - } - $.ctx.pushClassInitFrame(classInfo); - if (U) { - $.ctx.current().pc = pc; + export function classInitCheck(classInfo: ClassInfo) { + if (classInfo.isArrayClass || $.initialized[classInfo.className]) { return; } + linkKlass(classInfo); + var runtimeKlass = $.getRuntimeKlass(classInfo.klass); + runtimeKlass.classObject.initialize(); } /**