8179531: JShell: fails to provide bytecode for dynamically created lambdas
Summary: Adding support for getResource(s) to the JShell's ClassLoader
Reviewed-by: psandoz, rfield
/*
* 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.jshell;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* A Snippet represents a snippet of Java source code as passed to
* {@link jdk.jshell.JShell#eval}. It is associated only with the
* {@link jdk.jshell.JShell JShell} instance that created it.
* An instance of Snippet (including its subclasses) is immutable: an access to
* any of its methods will always return the same result.
* For information about the current state of the snippet within the JShell
* state engine, query {@code JShell} passing the Snippet.
* <p>
* Because it is immutable, {@code Snippet} (and subclasses) is thread-safe.
*
* @author Robert Field
* @since 9
* @see jdk.jshell.JShell#status
*/
public abstract class Snippet {
/**
* Describes the general kind of snippet.
* The {@code Kind} is an immutable property of a Snippet.
* It is accessed with {@link jdk.jshell.Snippet#kind()}.
* The {@code Kind} can be used to determine which
* subclass of Snippet it is. For example,
* {@link jdk.jshell.JShell#eval eval("int three() { return 3; }")} will
* return a snippet creation event. The {@code Kind} of that Snippet
* will be {@code METHOD}, from which you know that the subclass
* of {@code Snippet} is {@code MethodSnippet} and it can be
* cast as such.
*/
public enum Kind {
/**
* An import declaration: {@code import} ...
* The snippet is an instance of {@link jdk.jshell.ImportSnippet}.
* <P>
* An import can be a single type import
* ({@link jdk.jshell.Snippet.SubKind#SINGLE_TYPE_IMPORT_SUBKIND}),
* a static single import
* ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}),
* an on-demand type import
* ({@link jdk.jshell.Snippet.SubKind#TYPE_IMPORT_ON_DEMAND_SUBKIND}),
* or a static on-demand type import
* ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}) --
* use {@link jdk.jshell.Snippet#subKind()} to distinguish.
* <P>
* @jls 8.3: importDeclaration.
* <P>
* An import declaration is {@linkplain Kind#isPersistent() persistent}.
*/
IMPORT(true),
/**
* A type declaration.
* Which includes: NormalClassDeclaration, EnumDeclaration,
* NormalInterfaceDeclaration, and AnnotationTypeDeclaration.
* The snippet is an instance of {@link jdk.jshell.TypeDeclSnippet}.
* <P>
* A type declaration may be an interface
* {@link jdk.jshell.Snippet.SubKind#INTERFACE_SUBKIND},
* classes {@link jdk.jshell.Snippet.SubKind#CLASS_SUBKIND}, enums, and
* annotation interfaces -- see {@link jdk.jshell.Snippet.SubKind} to
* differentiate.
* <P>
* @jls 7.6: TypeDeclaration.
* <P>
* A type declaration is {@linkplain Kind#isPersistent() persistent}.
*/
TYPE_DECL(true),
/**
* A method declaration.
* The snippet is an instance of {@link jdk.jshell.MethodSnippet}.
* <P>
* @jls 8.4: MethodDeclaration.
* <P>
* A method declaration is {@linkplain Kind#isPersistent() persistent}.
*/
METHOD(true),
/**
* One variable declaration.
* Corresponding to one <i>VariableDeclarator</i>.
* The snippet is an instance of {@link jdk.jshell.VarSnippet}.
* <P>
* The variable may be with or without initializer, or be a temporary
* variable representing an expression -- see
* {@link jdk.jshell.Snippet.SubKind}to differentiate.
* <P>
* @jls 8.3: FieldDeclaration.
* <P>
* A variable declaration is {@linkplain Kind#isPersistent() persistent}.
*/
VAR(true),
/**
* An expression, with or without side-effects.
* The snippet is an instance of {@link jdk.jshell.ExpressionSnippet}.
* <P>
* The expression is currently either a simple named reference to a
* variable ({@link jdk.jshell.Snippet.SubKind#VAR_VALUE_SUBKIND}) or an
* assignment (both of which have natural referencing
* names) -- see {@link jdk.jshell.Snippet.SubKind} to differentiate.
* All other expression forms (operators, method calls, ...) generate a
* scratch variable and so are instead of the VAR Kind.
* <P>
* @jls 15: Expression.
*/
EXPRESSION(false),
/**
* A statement.
* The snippet is an instance of {@link jdk.jshell.StatementSnippet}.
* <P>
* @jls 14.5: Statement.
*/
STATEMENT(false),
/**
* A syntactically incorrect input for which the specific
* kind could not be determined.
* The snippet is an instance of {@link jdk.jshell.ErroneousSnippet}.
*/
ERRONEOUS(false);
private final boolean isPersistent;
Kind(boolean isPersistent) {
this.isPersistent = isPersistent;
}
/**
* Indicates whether this {@code Kind} of Snippet is persistent. Only
* declarations are persistent because they influence future Snippets.
* <p>
* Note that though the {@code Kind} of
* a Snippet may be persistent, that does not mean that the Snippet will
* persist; For example it may be invalid or have been dropped. See:
* {@link jdk.jshell.Snippet.Status#isDefined()}.
*
* @return {@code true} if this {@code Kind} of {@code Snippet} is
* visible to subsequent evaluations; otherwise {@code false}
*/
public boolean isPersistent() {
return isPersistent;
}
}
/**
* The detailed variety of a snippet. This is a sub-classification of the
* Kind. The Kind of a SubKind is accessible with
* {@link jdk.jshell.Snippet.SubKind#kind}.
*/
public enum SubKind {
/**
* Single-Type-Import Declaration.
* An import declaration of a single type.
* @jls 7.5.1 SingleTypeImportDeclaration.
*/
SINGLE_TYPE_IMPORT_SUBKIND(Kind.IMPORT),
/**
* Type-Import-on-Demand Declaration.
* A non-static "star" import.
* @jls 7.5.2. TypeImportOnDemandDeclaration.
*/
TYPE_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
/**
* Single-Static-Import Declaration.
* An import of a static member.
* @jls 7.5.3 Single-Static-Import.
*/
SINGLE_STATIC_IMPORT_SUBKIND(Kind.IMPORT),
/**
* Static-Import-on-Demand Declaration.
* A static "star" import of all static members of a named type.
* @jls 7.5.4. Static-Import-on-Demand Static "star" import.
*/
STATIC_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
/**
* A class declaration.
* A {@code SubKind} of {@link Kind#TYPE_DECL}.
* @jls 8.1. NormalClassDeclaration.
*/
CLASS_SUBKIND(Kind.TYPE_DECL),
/**
* An interface declaration.
* A {@code SubKind} of {@link Kind#TYPE_DECL}.
* @jls 9.1. NormalInterfaceDeclaration.
*/
INTERFACE_SUBKIND(Kind.TYPE_DECL),
/**
* An enum declaration.
* A {@code SubKind} of {@link Kind#TYPE_DECL}.
* @jls 8.9. EnumDeclaration.
*/
ENUM_SUBKIND(Kind.TYPE_DECL),
/**
* An annotation interface declaration. A {@code SubKind} of
* {@link Kind#TYPE_DECL}.
* @jls 9.6. AnnotationTypeDeclaration.
*/
ANNOTATION_TYPE_SUBKIND(Kind.TYPE_DECL),
/**
* A method. The only {@code SubKind} for {@link Kind#METHOD}.
* @jls 8.4. MethodDeclaration.
*/
METHOD_SUBKIND(Kind.METHOD),
/**
* A variable declaration without initializer.
* A {@code SubKind} of {@link Kind#VAR}.
* @jls 8.3. VariableDeclarator without VariableInitializer in
* FieldDeclaration.
*/
VAR_DECLARATION_SUBKIND(Kind.VAR),
/**
* A variable declaration with an initializer expression. A
* {@code SubKind} of {@link Kind#VAR}.
* @jls 8.3. VariableDeclarator with VariableInitializer in
* FieldDeclaration.
*/
VAR_DECLARATION_WITH_INITIALIZER_SUBKIND(Kind.VAR, true, true),
/**
* An expression whose value has been stored in a temporary variable. A
* {@code SubKind} of {@link Kind#VAR}.
* @jls 15. Primary.
*/
TEMP_VAR_EXPRESSION_SUBKIND(Kind.VAR, true, true),
/**
* A simple variable reference expression. A {@code SubKind} of
* {@link Kind#EXPRESSION}.
* @jls 15.11. Field Access as 3.8. Identifier.
*/
VAR_VALUE_SUBKIND(Kind.EXPRESSION, true, true),
/**
* An assignment expression. A {@code SubKind} of
* {@link Kind#EXPRESSION}.
* @jls 15.26. Assignment.
*/
ASSIGNMENT_SUBKIND(Kind.EXPRESSION, true, true),
/**
* An expression which has not been wrapped in a temporary variable
* (reserved). A {@code SubKind} of {@link Kind#EXPRESSION}.
*/
OTHER_EXPRESSION_SUBKIND(Kind.EXPRESSION, true, true),
/**
* A statement. The only {@code SubKind} for {@link Kind#STATEMENT}.
* @jls 14.5. Statement.
*/
STATEMENT_SUBKIND(Kind.STATEMENT, true, false),
/**
* An unknown snippet. The only {@code SubKind} for
* {@link Kind#ERRONEOUS}.
*/
UNKNOWN_SUBKIND(Kind.ERRONEOUS, false, false);
private final boolean isExecutable;
private final boolean hasValue;
private final Kind kind;
SubKind(Kind kind) {
this.kind = kind;
this.isExecutable = false;
this.hasValue = false;
}
SubKind(Kind kind, boolean isExecutable, boolean hasValue) {
this.kind = kind;
this.isExecutable = isExecutable;
this.hasValue = hasValue;
}
/**
* Indicates whether this {@code SubKind} is executable.
*
* @return {@code true} if this {@code SubKind} can
* be executed; otherwise {@code false}
*/
public boolean isExecutable() {
return isExecutable;
}
/**
* Indicates whether this {@code SubKind} is executable and
* is non-{@code void}.
*
* @return {@code true} if this {@code SubKind} has
* a value; otherwise {@code false}
*/
public boolean hasValue() {
return hasValue;
}
/**
* The {@link Snippet.Kind} that corresponds to this {@code SubKind}.
*
* @return the fixed {@code Kind} for this {@code SubKind}
*/
public Kind kind() {
return kind;
}
}
/**
* Describes the current state of a Snippet.
* This is a dynamic property of a Snippet within the JShell state --
* thus is retrieved with a {@linkplain
* jdk.jshell.JShell#status(jdk.jshell.Snippet) query on {@code JShell}}.
* <p>
* The {@code Status} changes as the state changes.
* For example, creation of another snippet with
* {@link jdk.jshell.JShell#eval(java.lang.String) eval}
* may resolve dependencies of this Snippet (or invalidate those dependencies), or
* {@linkplain jdk.jshell.Snippet.Status#OVERWRITTEN overwrite}
* this Snippet changing its
* {@code Status}.
* <p>
* Important properties associated with {@code Status} are:
* {@link jdk.jshell.Snippet.Status#isDefined()}, if it is visible to other
* existing and new snippets; and
* {@link jdk.jshell.Snippet.Status#isActive()}, if, as the
* JShell state changes, the snippet will update, possibly
* changing {@code Status}.
* An executable Snippet can only be executed if it is in the the
* {@link jdk.jshell.Snippet.Status#VALID} {@code Status}.
* @see JShell#status(jdk.jshell.Snippet)
*/
public enum Status {
/**
* The snippet is a valid snippet
* (in the context of current {@code JShell} state).
* Only snippets with {@code VALID}
* {@code Status} can be executed (though not all
* {@code VALID} snippets have executable code).
* <p>
* The snippet is defined
* ({@link Status#isDefined() isDefined() == true}).
* If the snippet is a declaration or import
* ({@link Snippet.Kind#isPersistent()}),
* it is visible to other snippets
* <p>
* The snippet will update as dependents change
* ({@link Status#isActive() isActive() == true}), its
* status could become {@code RECOVERABLE_DEFINED}, {@code RECOVERABLE_NOT_DEFINED},
* {@code DROPPED}, or {@code OVERWRITTEN}.
*/
VALID(true, true),
/**
* The snippet is a declaration snippet with potentially recoverable
* unresolved references or other issues in its body
* (in the context of current {@code JShell} state).
* Only a {@link jdk.jshell.DeclarationSnippet} can have this
* {@code Status}.
* <p>
* The snippet has a valid signature and it is visible to other
* snippets
* ({@link Status#isDefined() isDefined() == true})
* and thus can be referenced in existing or new snippets
* but the snippet cannot be executed.
* An {@link UnresolvedReferenceException} will be thrown on an attempt
* to execute it.
* <p>
* The snippet will update as dependents change
* ({@link Status#isActive() isActive() == true}), its
* status could become {@code VALID}, {@code RECOVERABLE_NOT_DEFINED},
* {@code DROPPED}, or {@code OVERWRITTEN}.
* <p>
* Note: both {@code RECOVERABLE_DEFINED} and {@code RECOVERABLE_NOT_DEFINED}
* indicate potentially recoverable errors, they differ in that, for
* {@code RECOVERABLE_DEFINED}, the snippet is
* {@linkplain Status#isDefined() defined}.
*/
RECOVERABLE_DEFINED(true, true),
/**
* The snippet is a declaration snippet with potentially recoverable
* unresolved references or other issues
* (in the context of current {@code JShell} state).
* Only a {@link jdk.jshell.DeclarationSnippet} can have this
* {@code Status}.
* <p>
* The snippet has an invalid signature or the implementation is
* otherwise unable to define it.
* The snippet it is not visible to other snippets
* ({@link Status#isDefined() isDefined() == false})
* and thus cannot be referenced or executed.
* <p>
* The snippet will update as dependents change
* ({@link Status#isActive() isActive() == true}), its
* status could become {@code VALID}, {@code RECOVERABLE_DEFINED},
* {@code DROPPED}, or {@code OVERWRITTEN}.
* <p>
* Note: both {@code RECOVERABLE_DEFINED} and {@code RECOVERABLE_NOT_DEFINED}
* indicate potentially recoverable errors, they differ in that, for
* {@code RECOVERABLE_DEFINED}, the snippet is
* {@linkplain Status#isDefined() defined}.
*/
RECOVERABLE_NOT_DEFINED(true, false),
/**
* The snippet is inactive because of an explicit call to
* the {@link JShell#drop(Snippet)}.
* <p>
* The snippet is not visible to other snippets
* ({@link Status#isDefined() isDefined() == false})
* and thus cannot be referenced or executed.
* <p>
* The snippet will not update as dependents change
* ({@link Status#isActive() isActive() == false}), its
* {@code Status} will never change again.
*/
DROPPED(false, false),
/**
* The snippet is inactive because it has been replaced by a new
* snippet. This occurs when the new snippet added with
* {@link jdk.jshell.JShell#eval} matches a previous snippet.
* A {@code TypeDeclSnippet} will match another
* {@code TypeDeclSnippet} if the names match.
* For example {@code class X { }} will overwrite
* {@code class X { int ii; }} or
* {@code interface X { }}.
* A {@code MethodSnippet} will match another
* {@code MethodSnippet} if the names and parameter types
* match.
* For example {@code void m(int a) { }} will overwrite
* {@code int m(int a) { return a+a; }}.
* A {@code VarSnippet} will match another
* {@code VarSnippet} if the names match.
* For example {@code double z;} will overwrite
* {@code long z = 2L;}.
* Only a {@link jdk.jshell.PersistentSnippet} can have this
* {@code Status}.
* <p>
* The snippet is not visible to other snippets
* ({@link Status#isDefined() isDefined() == false})
* and thus cannot be referenced or executed.
* <p>
* The snippet will not update as dependents change
* ({@link Status#isActive() isActive() == false}), its
* {@code Status} will never change again.
*/
OVERWRITTEN(false, false),
/**
* The snippet is inactive because it failed compilation on initial
* evaluation and it is not capable of becoming valid with further
* changes to the JShell state.
* <p>
* The snippet is not visible to other snippets
* ({@link Status#isDefined() isDefined() == false})
* and thus cannot be referenced or executed.
* <p>
* The snippet will not update as dependents change
* ({@link Status#isActive() isActive() == false}), its
* {@code Status} will never change again.
*/
REJECTED(false, false),
/**
* The snippet is inactive because it does not yet exist.
* Used only in {@link SnippetEvent#previousStatus} for new
* snippets.
* {@link jdk.jshell.JShell#status(jdk.jshell.Snippet) JShell.status(Snippet)}
* will never return this {@code Status}.
* <p>
* Vacuously, {@link Status#isDefined() isDefined()} and
* {@link Status#isActive() isActive()} are both defined {@code false}.
*/
NONEXISTENT(false, false);
private final boolean isActive;
private final boolean isDefined;
Status(boolean isActive, boolean isDefined) {
this.isActive = isActive;
this.isDefined = isDefined;
}
/**
* Indicates whether the Snippet is active, that is,
* will a {@linkplain jdk.jshell.PersistentSnippet persistent}
* snippet be re-evaluated when a new
* {@link JShell#eval(java.lang.String) JShell.eval(String)} or
* {@link JShell#drop(jdk.jshell.Snippet)
* JShell.drop(Snippet)} that could change
* its status is invoked. This is more broad than
* {@link Status#isDefined()} since a Snippet which is
* {@link Status#RECOVERABLE_NOT_DEFINED}
* will be updated.
*
* @return {@code true} if the Snippet is active; otherwise {@code false}
*/
public boolean isActive() {
return isActive;
}
/**
* Indicates whether the snippet is currently part of the defined state
* of the JShell. Is it visible to compilation of other snippets?
* @return {@code true} if the Snippet is defined; otherwise
* {@code false}
*/
public boolean isDefined() {
return isDefined;
}
}
private final Key key;
private final String source;
private final Wrap guts;
final String unitName;
private final SubKind subkind;
private int seq;
private String id;
private OuterWrap outer;
private Status status;
private List<String> unresolved;
private DiagList diagnostics;
private final DiagList syntheticDiags;
Snippet(Key key, String userSource, Wrap guts, String unitName,
SubKind subkind, DiagList syntheticDiags) {
this.key = key;
this.source = userSource;
this.guts = guts;
this.unitName = unitName;
this.subkind = subkind;
this.syntheticDiags = syntheticDiags==null
? new DiagList()
: syntheticDiags;
this.status = Status.NONEXISTENT;
setSequenceNumber(0);
}
/**** public access ****/
/**
* The unique identifier for the snippet. No two active snippets will have
* the same id(). Value of id has no prescribed meaning. The details of
* how the id is generated and the mechanism to change it is documented in
* {@link JShell.Builder#idGenerator(BiFunction)}.
* @return the snippet id string.
*/
public String id() {
return id;
}
/**
* The {@link jdk.jshell.Snippet.Kind} for the snippet.
* Indicates the subclass of Snippet.
* @return the Kind of the snippet
* @see Snippet.Kind
*/
public Kind kind() {
return subkind.kind();
}
/**
* Return the {@link SubKind} of snippet.
* The SubKind is useful for feedback to users.
* @return the SubKind corresponding to this snippet
*/
public SubKind subKind() {
return subkind;
}
/**
* Return the source code of the snippet.
* @return the source code corresponding to this snippet
*/
public String source() {
return source;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Snippet:");
if (key() != null) {
sb.append(key().toString());
}
sb.append('-');
sb.append(source);
return sb.toString();
}
//**** internal access ****
String name() {
return unitName;
}
Key key() {
return key;
}
List<String> unresolved() {
return Collections.unmodifiableList(unresolved);
}
DiagList diagnostics() {
return diagnostics;
}
DiagList syntheticDiags() {
return syntheticDiags;
}
/**
* @return the corralled guts
*/
Wrap corralled() {
return null;
}
Collection<String> declareReferences() {
return null;
}
Collection<String> bodyReferences() {
return null;
}
String importLine(JShell state) {
return "";
}
void setId(String id) {
this.id = id;
}
final void setSequenceNumber(int seq) {
this.seq = seq;
}
void setOuterWrap(OuterWrap outer) {
this.outer = outer;
}
void setCompilationStatus(Status status, List<String> unresolved, DiagList diagnostics) {
this.status = status;
this.unresolved = unresolved;
this.diagnostics = diagnostics;
}
void setDiagnostics(DiagList diagnostics) {
this.diagnostics = diagnostics;
}
void setFailed(DiagList diagnostics) {
this.seq = -1;
this.outer = null;
this.status = Status.REJECTED;
this.unresolved = Collections.emptyList();
this.diagnostics = diagnostics;
}
void setDropped() {
this.status = Status.DROPPED;
}
void setOverwritten() {
this.status = Status.OVERWRITTEN;
}
Status status() {
return status;
}
String className() {
return outer.className();
}
String classFullName() {
return outer.classFullName();
}
/**
* Top-level wrap
* @return
*/
OuterWrap outerWrap() {
return outer;
}
/**
* Basically, class version for this Key.
* @return int
*/
int sequenceNumber() {
return seq;
}
Wrap guts() {
return guts;
}
boolean isExecutable() {
return subkind.isExecutable();
}
}